From 7ebc595ed0977209d08ba68d69ef3cb81edd862b Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 16 Apr 2024 11:05:06 +0200 Subject: [PATCH 1/5] ci: fix version check --- .github/workflows/base_node_binaries.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/base_node_binaries.yml b/.github/workflows/base_node_binaries.yml index a54bf213d3..6933402fd9 100644 --- a/.github/workflows/base_node_binaries.yml +++ b/.github/workflows/base_node_binaries.yml @@ -39,7 +39,7 @@ concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') || github.ref != 'refs/heads/development' || github.ref != 'refs/heads/nextnet' || github.ref != 'refs/heads/stagenet' }} -permissions: {} +permissions: { } jobs: matrix-prep: @@ -132,7 +132,7 @@ jobs: echo "VSHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV TARI_VERSION=$(awk -F ' = ' '$1 ~ /^version/ \ { gsub(/["]/, "", $2); printf("%s",$2) }' \ - "$GITHUB_WORKSPACE/applications/minotari_node/Cargo.toml") + "$GITHUB_WORKSPACE/Cargo.toml") echo "TARI_VERSION=${TARI_VERSION}" >> $GITHUB_ENV echo "TARI_VERSION=${TARI_VERSION}" >> $GITHUB_OUTPUT if [[ "${{ matrix.builds.features }}" == "" ]]; then From b4d5ed0b969a3a75c8f07f922687b3675bbe3766 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 16 Apr 2024 11:08:22 +0200 Subject: [PATCH 2/5] v1.0.0-dan.10 --- Cargo.lock | 68 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68b1eaf77c..22fe884047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3171,7 +3171,7 @@ dependencies = [ [[package]] name = "minotari_app_grpc" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "argon2", "base64 0.13.1", @@ -3199,7 +3199,7 @@ dependencies = [ [[package]] name = "minotari_app_utilities" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "clap 3.2.25", "dialoguer", @@ -3221,7 +3221,7 @@ dependencies = [ [[package]] name = "minotari_chat_ffi" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "cbindgen", "chrono", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "minotari_console_wallet" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "blake2", "chrono", @@ -3300,7 +3300,7 @@ dependencies = [ [[package]] name = "minotari_merge_mining_proxy" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "bincode", @@ -3340,7 +3340,7 @@ dependencies = [ [[package]] name = "minotari_miner" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "base64 0.13.1", "borsh", @@ -3376,7 +3376,7 @@ dependencies = [ [[package]] name = "minotari_mining_helper_ffi" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "borsh", "cbindgen", @@ -3396,7 +3396,7 @@ dependencies = [ [[package]] name = "minotari_node" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "async-trait", @@ -3444,7 +3444,7 @@ dependencies = [ [[package]] name = "minotari_node_grpc_client" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "minotari_app_grpc", "tokio", @@ -3452,7 +3452,7 @@ dependencies = [ [[package]] name = "minotari_wallet" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "argon2", "async-trait", @@ -3502,7 +3502,7 @@ dependencies = [ [[package]] name = "minotari_wallet_ffi" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "borsh", "cbindgen", @@ -3542,7 +3542,7 @@ dependencies = [ [[package]] name = "minotari_wallet_grpc_client" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "minotari_app_grpc", "tari_common_types", @@ -5898,7 +5898,7 @@ dependencies = [ [[package]] name = "tari_chat_client" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "async-trait", @@ -5924,7 +5924,7 @@ dependencies = [ [[package]] name = "tari_common" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "blake2", @@ -5950,7 +5950,7 @@ dependencies = [ [[package]] name = "tari_common_sqlite" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "diesel", "diesel_migrations", @@ -5964,7 +5964,7 @@ dependencies = [ [[package]] name = "tari_common_types" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "base64 0.21.5", "blake2", @@ -5986,7 +5986,7 @@ dependencies = [ [[package]] name = "tari_comms" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "async-trait", @@ -6035,7 +6035,7 @@ dependencies = [ [[package]] name = "tari_comms_dht" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "bitflags 2.4.1", @@ -6079,7 +6079,7 @@ dependencies = [ [[package]] name = "tari_comms_rpc_macros" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "futures 0.3.29", "proc-macro2", @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "tari_contacts" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "chrono", "diesel", @@ -6127,7 +6127,7 @@ dependencies = [ [[package]] name = "tari_core" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "async-trait", "bincode", @@ -6224,11 +6224,11 @@ dependencies = [ [[package]] name = "tari_features" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" [[package]] name = "tari_hashing" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "blake2", "borsh", @@ -6238,7 +6238,7 @@ dependencies = [ [[package]] name = "tari_integration_tests" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "async-trait", @@ -6286,7 +6286,7 @@ dependencies = [ [[package]] name = "tari_key_manager" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "argon2", "async-trait", @@ -6321,7 +6321,7 @@ dependencies = [ [[package]] name = "tari_libtor" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "derivative", "libtor", @@ -6336,7 +6336,7 @@ dependencies = [ [[package]] name = "tari_metrics" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "futures 0.3.29", @@ -6351,7 +6351,7 @@ dependencies = [ [[package]] name = "tari_mmr" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "bincode", "blake2", @@ -6370,7 +6370,7 @@ dependencies = [ [[package]] name = "tari_p2p" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "clap 3.2.25", @@ -6406,7 +6406,7 @@ dependencies = [ [[package]] name = "tari_script" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "blake2", "borsh", @@ -6423,7 +6423,7 @@ dependencies = [ [[package]] name = "tari_service_framework" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "anyhow", "async-trait", @@ -6440,7 +6440,7 @@ dependencies = [ [[package]] name = "tari_shutdown" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "futures 0.3.29", "tokio", @@ -6448,7 +6448,7 @@ dependencies = [ [[package]] name = "tari_storage" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "bincode", "lmdb-zero", @@ -6461,7 +6461,7 @@ dependencies = [ [[package]] name = "tari_test_utils" -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" dependencies = [ "futures 0.3.29", "futures-test", diff --git a/Cargo.toml b/Cargo.toml index d1dbfa69ca..ffae678251 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.0.0-dan.9" +version = "1.0.0-dan.10" edition = "2021" [workspace] From bd03f623a0c016390a146a0e788170615ce74c87 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 16 Apr 2024 13:06:52 +0200 Subject: [PATCH 3/5] ci: remove unnecessary build platforms --- .github/workflows/base_node_binaries.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/base_node_binaries.json b/.github/workflows/base_node_binaries.json index c636da8df7..3efa71f7c7 100644 --- a/.github/workflows/base_node_binaries.json +++ b/.github/workflows/base_node_binaries.json @@ -21,7 +21,7 @@ "target": "riscv64gc-unknown-linux-gnu", "cross": true, "flags": "--workspace --exclude minotari_mining_helper_ffi --exclude tari_integration_tests", - "build_enabled": true, + "build_enabled": false, "best-effort": true }, { @@ -29,14 +29,16 @@ "runs-on": "macos-11", "rust": "stable", "target": "x86_64-apple-darwin", - "cross": false + "cross": false, + "build_enabled": false }, { "name": "macos-arm64", "runs-on": "macos-14", "rust": "stable", "target": "aarch64-apple-darwin", - "cross": false + "cross": false, + "build_enabled": false }, { "name": "windows-x64", @@ -45,7 +47,8 @@ "target": "x86_64-pc-windows-msvc", "cross": false, "features": "safe", - "flags": "--workspace --exclude tari_libtor" + "flags": "--workspace --exclude tari_libtor", + "build_enabled": false }, { "name": "windows-arm64", From 7d480c50513e93c70566b4c2ce91a7afee006f05 Mon Sep 17 00:00:00 2001 From: stringhandler Date: Tue, 16 Apr 2024 13:10:54 +0200 Subject: [PATCH 4/5] v1.0.0-dan.11 --- Cargo.lock | 68 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22fe884047..7b56eefa00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3171,7 +3171,7 @@ dependencies = [ [[package]] name = "minotari_app_grpc" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "argon2", "base64 0.13.1", @@ -3199,7 +3199,7 @@ dependencies = [ [[package]] name = "minotari_app_utilities" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "clap 3.2.25", "dialoguer", @@ -3221,7 +3221,7 @@ dependencies = [ [[package]] name = "minotari_chat_ffi" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "cbindgen", "chrono", @@ -3246,7 +3246,7 @@ dependencies = [ [[package]] name = "minotari_console_wallet" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "blake2", "chrono", @@ -3300,7 +3300,7 @@ dependencies = [ [[package]] name = "minotari_merge_mining_proxy" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "bincode", @@ -3340,7 +3340,7 @@ dependencies = [ [[package]] name = "minotari_miner" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "base64 0.13.1", "borsh", @@ -3376,7 +3376,7 @@ dependencies = [ [[package]] name = "minotari_mining_helper_ffi" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "borsh", "cbindgen", @@ -3396,7 +3396,7 @@ dependencies = [ [[package]] name = "minotari_node" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "async-trait", @@ -3444,7 +3444,7 @@ dependencies = [ [[package]] name = "minotari_node_grpc_client" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "minotari_app_grpc", "tokio", @@ -3452,7 +3452,7 @@ dependencies = [ [[package]] name = "minotari_wallet" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "argon2", "async-trait", @@ -3502,7 +3502,7 @@ dependencies = [ [[package]] name = "minotari_wallet_ffi" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "borsh", "cbindgen", @@ -3542,7 +3542,7 @@ dependencies = [ [[package]] name = "minotari_wallet_grpc_client" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "minotari_app_grpc", "tari_common_types", @@ -5898,7 +5898,7 @@ dependencies = [ [[package]] name = "tari_chat_client" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "async-trait", @@ -5924,7 +5924,7 @@ dependencies = [ [[package]] name = "tari_common" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "blake2", @@ -5950,7 +5950,7 @@ dependencies = [ [[package]] name = "tari_common_sqlite" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "diesel", "diesel_migrations", @@ -5964,7 +5964,7 @@ dependencies = [ [[package]] name = "tari_common_types" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "base64 0.21.5", "blake2", @@ -5986,7 +5986,7 @@ dependencies = [ [[package]] name = "tari_comms" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "async-trait", @@ -6035,7 +6035,7 @@ dependencies = [ [[package]] name = "tari_comms_dht" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "bitflags 2.4.1", @@ -6079,7 +6079,7 @@ dependencies = [ [[package]] name = "tari_comms_rpc_macros" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "futures 0.3.29", "proc-macro2", @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "tari_contacts" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "chrono", "diesel", @@ -6127,7 +6127,7 @@ dependencies = [ [[package]] name = "tari_core" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "async-trait", "bincode", @@ -6224,11 +6224,11 @@ dependencies = [ [[package]] name = "tari_features" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" [[package]] name = "tari_hashing" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "blake2", "borsh", @@ -6238,7 +6238,7 @@ dependencies = [ [[package]] name = "tari_integration_tests" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "async-trait", @@ -6286,7 +6286,7 @@ dependencies = [ [[package]] name = "tari_key_manager" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "argon2", "async-trait", @@ -6321,7 +6321,7 @@ dependencies = [ [[package]] name = "tari_libtor" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "derivative", "libtor", @@ -6336,7 +6336,7 @@ dependencies = [ [[package]] name = "tari_metrics" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "futures 0.3.29", @@ -6351,7 +6351,7 @@ dependencies = [ [[package]] name = "tari_mmr" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "bincode", "blake2", @@ -6370,7 +6370,7 @@ dependencies = [ [[package]] name = "tari_p2p" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "clap 3.2.25", @@ -6406,7 +6406,7 @@ dependencies = [ [[package]] name = "tari_script" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "blake2", "borsh", @@ -6423,7 +6423,7 @@ dependencies = [ [[package]] name = "tari_service_framework" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "anyhow", "async-trait", @@ -6440,7 +6440,7 @@ dependencies = [ [[package]] name = "tari_shutdown" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "futures 0.3.29", "tokio", @@ -6448,7 +6448,7 @@ dependencies = [ [[package]] name = "tari_storage" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "bincode", "lmdb-zero", @@ -6461,7 +6461,7 @@ dependencies = [ [[package]] name = "tari_test_utils" -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" dependencies = [ "futures 0.3.29", "futures-test", diff --git a/Cargo.toml b/Cargo.toml index ffae678251..1763844b08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "1.0.0-dan.10" +version = "1.0.0-dan.11" edition = "2021" [workspace] From 35573840c3b40f7e87b4021f8bed754be8e859cf Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Tue, 23 Jul 2024 12:12:50 +0400 Subject: [PATCH 5/5] chore: update feature dan2 (#6420) Description --- Merged development branch into feature-dan2 Breaking Changes --- - [ ] None - [x] Requires data directory on base node to be deleted - [x] Requires hard fork - [ ] Other - Please specify --------- Signed-off-by: dependabot[bot] Co-authored-by: SW van Heerden Co-authored-by: C.Lee Taylor <47312074+leet4tari@users.noreply.github.com> Co-authored-by: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Co-authored-by: Aaron Feickert <66188213+AaronFeickert@users.noreply.github.com> Co-authored-by: Brian Pearce Co-authored-by: stringhandler Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Richard Bertok Co-authored-by: Hansie Odendaal --- ...node_binaries.json => build_binaries.json} | 14 +- ...e_node_binaries.yml => build_binaries.yml} | 442 ++++-- .github/workflows/build_dockers.yml | 8 +- .github/workflows/build_dockers_workflow.yml | 4 +- .github/workflows/build_libffis.yml | 12 +- .github/workflows/ci.yml | 55 +- .github/workflows/coverage.yml | 2 +- .github/workflows/integration_tests.yml | 2 +- Cargo.lock | 433 +++--- Cargo.toml | 7 +- Cross.toml | 67 +- README.md | 4 +- applications/minotari_app_grpc/build.rs | 1 + .../minotari_app_grpc/proto/network.proto | 6 +- .../minotari_app_grpc/proto/p2pool.proto | 25 +- .../minotari_app_grpc/proto/transaction.proto | 2 + .../minotari_app_grpc/proto/wallet.proto | 1 + .../src/authentication/basic_auth.rs | 8 +- .../minotari_app_grpc/src/conversions/peer.rs | 5 +- .../src/conversions/transaction_output.rs | 12 +- .../src/conversions/unblinded_output.rs | 23 +- .../minotari_app_utilities/Cargo.toml | 3 +- applications/minotari_app_utilities/build.rs | 3 + .../src/identity_management.rs | 2 +- .../minotari_app_utilities/src/lib.rs | 5 + .../src/parse_miner_input.rs | 11 +- .../minotari_app_utilities/src/utilities.rs | 48 +- .../minotari_console_wallet/Cargo.toml | 10 +- .../minotari_console_wallet/README.md | 2 +- .../src/automation/commands.rs | 639 +++++++-- .../src/automation/error.rs | 3 + .../src/automation/mod.rs | 75 + .../src/automation/utils.rs | 215 +++ .../minotari_console_wallet/src/cli.rs | 60 +- .../minotari_console_wallet/src/config.rs | 2 - .../minotari_console_wallet/src/grpc/mod.rs | 8 +- .../src/grpc/wallet_grpc_server.rs | 52 +- .../minotari_console_wallet/src/init/mod.rs | 140 +- .../minotari_console_wallet/src/lib.rs | 1 - .../src/notifier/mod.rs | 9 +- .../minotari_console_wallet/src/recovery.rs | 10 +- .../minotari_console_wallet/src/ui/app.rs | 26 +- .../src/ui/components/receive_tab.rs | 54 +- .../src/ui/components/send_tab.rs | 88 +- .../src/ui/components/transactions_tab.rs | 30 +- .../minotari_console_wallet/src/ui/mod.rs | 2 +- .../src/ui/state/app_state.rs | 109 +- .../src/ui/state/tasks.rs | 62 +- .../src/ui/state/wallet_event_monitor.rs | 2 +- .../src/ui/ui_contact.rs | 2 +- .../src/utils/formatting.rs | 4 +- .../src/wallet_modes.rs | 111 +- .../minotari_ledger_wallet/Cargo.lock | 467 ------ .../minotari_ledger_wallet/Cargo.toml | 36 - .../minotari_ledger_wallet/app_nanosplus.json | 21 - .../minotari_ledger_wallet/comms/Cargo.toml | 16 + .../minotari_ledger_wallet/comms/src/error.rs | 82 +- .../comms/src/ledger_wallet.rs | 140 ++ .../minotari_ledger_wallet/comms/src/lib.rs | 24 + .../minotari_ledger_wallet/key_14x14.gif | Bin 145 -> 0 bytes .../minotari_ledger_wallet/key_16x16.gif | Bin 155 -> 0 bytes .../rust-toolchain.toml | 2 - .../minotari_ledger_wallet/rustfmt.toml | 27 - .../minotari_ledger_wallet/src/hashing.rs | 75 - .../minotari_ledger_wallet/src/main.rs | 189 --- .../minotari_ledger_wallet/src/utils.rs | 123 -- .../wallet/.cargo/config.toml | 15 + .../minotari_ledger_wallet/wallet/.gitignore | 22 + .../minotari_ledger_wallet/wallet/Cargo.lock | 909 ++++++++++++ .../minotari_ledger_wallet/wallet/Cargo.toml | 54 + .../{ => wallet}/README.md | 86 +- .../minotari_ledger_wallet/wallet/build.rs | 6 + .../minotari_ledger_wallet/wallet/key.gif | Bin 0 -> 892 bytes .../wallet/key_14x14.gif | Bin 0 -> 880 bytes .../wallet/ledger_app.toml | 4 + .../wallet/rust-toolchain.toml | 2 + .../wallet/src/app_ui/menu.rs | 47 + .../src/handlers/get_dh_shared_secret.rs | 44 + .../wallet/src/handlers/get_public_key.rs | 35 + .../src/handlers/get_public_spend_key.rs | 26 + .../wallet/src/handlers/get_script_offset.rs | 150 ++ .../src/handlers/get_script_signature.rs | 123 ++ .../wallet/src/handlers/get_version.rs | 12 + .../wallet/src/handlers/get_view_key.rs | 26 + .../wallet/src/hashing.rs | 78 + .../minotari_ledger_wallet/wallet/src/main.rs | 232 +++ .../wallet/src/utils.rs | 260 ++++ .../minotari_merge_mining_proxy/Cargo.toml | 2 +- .../src/block_template_data.rs | 3 +- .../src/block_template_protocol.rs | 16 +- .../minotari_merge_mining_proxy/src/config.rs | 5 +- .../src/monero_fail.rs | 3 + .../minotari_merge_mining_proxy/src/proxy.rs | 50 +- applications/minotari_miner/Cargo.toml | 6 +- applications/minotari_miner/README.md | 1 - applications/minotari_miner/build.rs | 23 +- .../linux/runtime/start_minotari_miner.sh | 24 + .../minotari_miner/linux/start_minotari_miner | 2 +- .../osx/runtime/start_minotari_miner.sh | 24 + .../minotari_miner/osx/start_minotari_miner | 2 +- applications/minotari_miner/src/cli.rs | 2 +- applications/minotari_miner/src/config.rs | 54 +- applications/minotari_miner/src/difficulty.rs | 32 +- applications/minotari_miner/src/errors.rs | 33 +- applications/minotari_miner/src/lib.rs | 32 +- applications/minotari_miner/src/main.rs | 32 +- applications/minotari_miner/src/miner.rs | 36 +- applications/minotari_miner/src/run_miner.rs | 268 +++- .../minotari_miner/src/stratum/controller.rs | 34 +- .../minotari_miner/src/stratum/error.rs | 33 +- .../minotari_miner/src/stratum/mod.rs | 33 +- .../stratum/stratum_controller/controller.rs | 35 +- .../src/stratum/stratum_controller/mod.rs | 33 +- .../stratum/stratum_types/client_message.rs | 33 +- .../src/stratum/stratum_types/job.rs | 33 +- .../src/stratum/stratum_types/job_params.rs | 33 +- .../src/stratum/stratum_types/login_params.rs | 34 +- .../stratum/stratum_types/login_response.rs | 33 +- .../stratum/stratum_types/miner_message.rs | 33 +- .../src/stratum/stratum_types/mod.rs | 33 +- .../src/stratum/stratum_types/rpc_error.rs | 33 +- .../src/stratum/stratum_types/rpc_request.rs | 33 +- .../src/stratum/stratum_types/rpc_response.rs | 33 +- .../stratum/stratum_types/submit_params.rs | 33 +- .../stratum/stratum_types/submit_response.rs | 33 +- .../stratum_types/worker_identifier.rs | 33 +- .../minotari_miner/src/stratum/stream.rs | 34 +- applications/minotari_node/Cargo.toml | 2 +- applications/minotari_node/src/bootstrap.rs | 14 +- applications/minotari_node/src/builder.rs | 1 - .../src/commands/command/get_peer.rs | 4 +- .../src/commands/command/status.rs | 13 +- .../minotari_node/src/commands/parser.rs | 4 - applications/minotari_node/src/config.rs | 1 - .../src/grpc/base_node_grpc_server.rs | 97 +- base_layer/chat_ffi/chat.h | 64 +- base_layer/chat_ffi/src/byte_vector.rs | 2 +- base_layer/chat_ffi/src/callback_handler.rs | 2 +- .../chat_ffi/src/contacts_liveness_data.rs | 10 +- base_layer/chat_ffi/src/conversationalists.rs | 2 +- base_layer/chat_ffi/src/error.rs | 1 + base_layer/chat_ffi/src/lib.rs | 17 +- base_layer/chat_ffi/src/message.rs | 141 +- base_layer/chat_ffi/src/message_metadata.rs | 13 +- base_layer/common_types/Cargo.toml | 5 + base_layer/common_types/src/dammsum.rs | 54 +- base_layer/common_types/src/emoji.rs | 167 ++- base_layer/common_types/src/encryption.rs | 6 +- base_layer/common_types/src/lib.rs | 3 + base_layer/common_types/src/tari_address.rs | 343 ----- .../src/tari_address/dual_address.rs | 520 +++++++ .../common_types/src/tari_address/mod.rs | 858 +++++++++++ .../src/tari_address/single_address.rs | 472 +++++++ base_layer/common_types/src/wallet_types.rs | 64 +- base_layer/contacts/Cargo.toml | 2 +- .../down.sql | 1 + .../up.sql | 1 + .../up.sql | 9 + base_layer/contacts/proto/message.proto | 7 +- .../contacts/src/chat_client/src/client.rs | 105 +- .../contacts/src/chat_client/src/config.rs | 3 +- .../src/chat_client/src/networking.rs | 2 + .../contacts/src/contacts_service/handle.rs | 18 +- .../contacts/src/contacts_service/service.rs | 26 +- .../src/contacts_service/storage/database.rs | 5 + .../src/contacts_service/storage/sqlite_db.rs | 42 +- .../storage/types/contacts.rs | 8 +- .../storage/types/messages.rs | 73 +- .../src/contacts_service/types/contact.rs | 4 +- .../src/contacts_service/types/message.rs | 11 +- .../contacts_service/types/message_builder.rs | 13 +- base_layer/contacts/src/schema.rs | 4 +- base_layer/contacts/tests/contacts_service.rs | 17 +- base_layer/core/Cargo.toml | 14 +- base_layer/core/benches/mempool.rs | 2 +- .../chain_metadata_service/service.rs | 3 +- .../comms_interface/inbound_handlers.rs | 23 +- .../core/src/base_node/proto/request.rs | 2 +- .../core/src/base_node/proto/response.rs | 2 +- .../states/events_and_states.rs | 18 +- base_layer/core/src/base_node/sync/config.rs | 11 +- .../base_node/sync/header_sync/validator.rs | 7 +- .../sync/horizon_state_sync/synchronizer.rs | 2 +- .../core/src/base_node/sync/sync_peer.rs | 1 - base_layer/core/src/blocks/block.rs | 28 +- .../src/blocks/faucets/esmeralda_faucet.json | 122 +- base_layer/core/src/blocks/faucets/mod.rs | 181 +++ base_layer/core/src/blocks/genesis_block.rs | 21 +- base_layer/core/src/blocks/mod.rs | 4 + .../src/chain_storage/blockchain_database.rs | 6 +- .../core/src/chain_storage/lmdb_db/lmdb_db.rs | 30 +- .../tests/blockchain_database.rs | 54 +- base_layer/core/src/common/mod.rs | 1 - base_layer/core/src/common/one_sided.rs | 35 +- .../core/src/consensus/consensus_constants.rs | 90 +- .../consensus/consensus_encoding/hashing.rs | 1 - base_layer/core/src/covenants/covenant.rs | 6 +- base_layer/core/src/covenants/fields.rs | 18 +- .../src/covenants/filters/absolute_height.rs | 6 +- base_layer/core/src/covenants/filters/and.rs | 2 +- .../core/src/covenants/filters/field_eq.rs | 14 +- .../src/covenants/filters/fields_hashed_eq.rs | 2 +- .../src/covenants/filters/fields_preserved.rs | 2 +- .../core/src/covenants/filters/identity.rs | 2 +- base_layer/core/src/covenants/filters/not.rs | 2 +- base_layer/core/src/covenants/filters/or.rs | 2 +- .../src/covenants/filters/output_hash_eq.rs | 2 +- base_layer/core/src/covenants/filters/xor.rs | 2 +- base_layer/core/src/iterators/chunk.rs | 12 +- base_layer/core/src/mempool/mempool.rs | 6 +- .../core/src/mempool/mempool_storage.rs | 10 +- .../priority/prioritized_transaction.rs | 6 +- .../core/src/mempool/reorg_pool/reorg_pool.rs | 6 +- .../core/src/mempool/sync_protocol/test.rs | 2 +- .../unconfirmed_pool/unconfirmed_pool.rs | 18 +- .../core/src/proof_of_work/difficulty.rs | 36 +- .../core/src/proof_of_work/monero_rx/error.rs | 12 +- .../src/proof_of_work/monero_rx/helpers.rs | 33 +- .../proof_of_work/monero_rx/merkle_tree.rs | 47 +- .../monero_rx/merkle_tree_parameters.rs | 339 +++-- .../core/src/proof_of_work/proof_of_work.rs | 1 + base_layer/core/src/proto/block.proto | 4 +- base_layer/core/src/proto/block.rs | 30 +- .../core/src/test_helpers/blockchain.rs | 4 +- base_layer/core/src/test_helpers/mod.rs | 15 +- .../core/src/transactions/aggregated_body.rs | 40 +- .../core/src/transactions/coinbase_builder.rs | 236 ++-- .../core/src/transactions/crypto_factories.rs | 2 +- .../src/transactions/key_manager/error.rs | 34 - .../transactions/key_manager/initializer.rs | 2 +- .../src/transactions/key_manager/inner.rs | 946 ++++++++++--- .../src/transactions/key_manager/interface.rs | 187 ++- .../key_manager/memory_db_key_manager.rs | 25 +- .../core/src/transactions/key_manager/mod.rs | 3 +- .../src/transactions/key_manager/wrapper.rs | 131 +- .../core/src/transactions/tari_amount.rs | 2 - .../core/src/transactions/test_helpers.rs | 130 +- .../transaction_components/encrypted_data.rs | 263 +++- .../transaction_components/error.rs | 9 +- .../transaction_components/kernel_features.rs | 2 +- .../range_proof_type.rs | 17 +- .../transaction_components/test.rs | 81 +- .../transaction_input.rs | 1 + .../transaction_output.rs | 22 +- .../unblinded_output.rs | 38 +- .../transaction_components/wallet_output.rs | 88 +- .../wallet_output_builder.rs | 149 +- .../transactions/transaction_protocol/mod.rs | 13 - .../transaction_protocol/recipient.rs | 41 +- .../transaction_protocol/sender.rs | 112 +- .../transaction_protocol/single_receiver.rs | 31 +- .../transaction_initializer.rs | 80 +- .../aggregate_body_chain_validator.rs | 9 +- .../aggregate_body_internal_validator.rs | 20 +- .../core/src/validation/block_body/test.rs | 56 +- base_layer/core/src/validation/error.rs | 9 + base_layer/core/src/validation/helpers.rs | 31 +- base_layer/core/src/validation/test.rs | 8 +- .../chain_storage_tests/chain_backend.rs | 2 +- .../core/tests/helpers/block_builders.rs | 13 +- base_layer/core/tests/helpers/nodes.rs | 2 + .../core/tests/helpers/sample_blockchains.rs | 6 +- base_layer/core/tests/helpers/sync.rs | 2 +- base_layer/core/tests/tests/base_node_rpc.rs | 2 +- .../core/tests/tests/block_validation.rs | 16 +- base_layer/core/tests/tests/mempool.rs | 24 +- .../core/tests/tests/node_comms_interface.rs | 44 +- base_layer/core/tests/tests/node_service.rs | 16 +- .../core/tests/tests/node_state_machine.rs | 4 +- base_layer/key_manager/src/cipher_seed.rs | 80 +- base_layer/key_manager/src/key_manager.rs | 7 +- .../src/key_manager_service/error.rs | 14 +- .../src/key_manager_service/handle.rs | 8 +- .../src/key_manager_service/interface.rs | 56 +- .../src/key_manager_service/mod.rs | 3 +- .../src/key_manager_service/service.rs | 60 +- .../storage/sqlite_db/imported_keys.rs | 2 +- .../storage/sqlite_db/key_manager_state.rs | 4 +- .../storage/sqlite_db/mod.rs | 13 +- base_layer/key_manager/src/lib.rs | 20 +- base_layer/key_manager/src/mnemonic.rs | 2 +- base_layer/mmr/src/backend.rs | 2 +- base_layer/mmr/src/common.rs | 2 +- base_layer/mmr/src/lib.rs | 2 +- base_layer/mmr/src/merkle_mountain_range.rs | 1 - .../mmr/src/sparse_merkle_tree/bit_utils.rs | 1 - base_layer/mmr/src/sparse_merkle_tree/mod.rs | 4 +- base_layer/mmr/src/sparse_merkle_tree/node.rs | 3 +- .../mmr/src/sparse_merkle_tree/proofs.rs | 3 +- .../mmr/tests/tests/with_blake512_hash.rs | 2 - base_layer/p2p/examples/gen_node_identity.rs | 2 +- base_layer/p2p/src/auto_update/mod.rs | 1 - .../src/comms_connector/inbound_connector.rs | 2 - base_layer/p2p/src/config.rs | 7 +- base_layer/p2p/src/dns/client.rs | 6 - base_layer/p2p/src/dns/mock.rs | 1 + base_layer/p2p/src/domain_message.rs | 2 +- base_layer/p2p/src/initialization.rs | 12 +- base_layer/p2p/src/peer_seeds.rs | 2 - base_layer/p2p/src/services/liveness/error.rs | 9 +- base_layer/p2p/src/services/liveness/mod.rs | 4 +- .../p2p/src/services/liveness/service.rs | 47 +- base_layer/p2p/src/services/liveness/state.rs | 8 +- base_layer/p2p/src/transport.rs | 2 +- base_layer/p2p/tests/services/liveness.rs | 1 + .../service_framework/src/context/handles.rs | 2 - .../src/context/lazy_service.rs | 9 +- base_layer/service_framework/src/stack.rs | 3 +- .../src/tower/service_ext.rs | 2 +- base_layer/tari_mining_helper_ffi/Cargo.toml | 3 + .../tari_mining_helper_ffi/src/error.rs | 7 + base_layer/tari_mining_helper_ffi/src/lib.rs | 15 +- .../2024-05-13-101400_payment_id/down.sql | 1 + .../2024-05-13-101400_payment_id/up.sql | 7 + .../wallet/src/base_node_service/service.rs | 3 +- base_layer/wallet/src/config.rs | 9 +- .../src/connectivity_service/service.rs | 8 +- .../wallet/src/connectivity_service/test.rs | 3 +- base_layer/wallet/src/lib.rs | 1 - .../src/output_manager_service/error.rs | 2 + .../src/output_manager_service/handle.rs | 108 +- .../wallet/src/output_manager_service/mod.rs | 13 +- .../recovery/standard_outputs_recoverer.rs | 104 +- .../src/output_manager_service/resources.rs | 12 +- .../src/output_manager_service/service.rs | 641 +++++++-- .../output_manager_service/storage/models.rs | 5 +- .../storage/output_source.rs | 8 +- .../storage/output_status.rs | 8 +- .../storage/sqlite_db/mod.rs | 6 +- .../storage/sqlite_db/new_output_sql.rs | 4 +- .../storage/sqlite_db/output_sql.rs | 27 +- base_layer/wallet/src/schema.rs | 2 + .../wallet/src/storage/sqlite_db/wallet.rs | 2 +- .../wallet/src/transaction_service/error.rs | 2 + .../wallet/src/transaction_service/handle.rs | 247 +++- .../wallet/src/transaction_service/mod.rs | 20 +- .../protocols/transaction_receive_protocol.rs | 5 +- .../protocols/transaction_send_protocol.rs | 27 +- .../wallet/src/transaction_service/service.rs | 887 +++++++++--- .../transaction_service/storage/database.rs | 22 +- .../src/transaction_service/storage/models.rs | 7 +- .../transaction_service/storage/sqlite_db.rs | 124 +- .../tasks/check_faux_transaction_status.rs | 15 +- .../tasks/send_transaction_reply.rs | 14 +- .../wallet/src/transaction_service/utc.rs | 13 +- base_layer/wallet/src/util/wallet_identity.rs | 23 +- .../src/utxo_scanner_service/initializer.rs | 49 +- .../src/utxo_scanner_service/service.rs | 5 +- .../utxo_scanner_service/utxo_scanner_task.rs | 17 +- .../uxto_scanner_service_builder.rs | 27 +- base_layer/wallet/src/wallet.rs | 133 +- .../key_manager_service_tests/service.rs | 16 +- base_layer/wallet/tests/other/mod.rs | 24 +- .../output_manager_service_tests/service.rs | 58 +- .../output_manager_service_tests/storage.rs | 10 +- base_layer/wallet/tests/support/comms_rpc.rs | 2 +- .../support/output_manager_service_mock.rs | 6 +- .../tests/support/transaction_service_mock.rs | 3 +- base_layer/wallet/tests/support/utils.rs | 20 +- .../transaction_service_tests/service.rs | 455 +++--- .../transaction_service_tests/storage.rs | 64 +- .../transaction_protocols.rs | 32 +- base_layer/wallet/tests/utxo_scanner/mod.rs | 80 +- base_layer/wallet_ffi/Cargo.toml | 4 + base_layer/wallet_ffi/README.md | 4 +- .../wallet_ffi/src/callback_handler_tests.rs | 30 +- base_layer/wallet_ffi/src/error.rs | 39 +- base_layer/wallet_ffi/src/lib.rs | 1252 +++++++++++++---- base_layer/wallet_ffi/wallet.h | 341 ++++- buildtools/build-notes.md | 67 +- buildtools/docker/base_node.Dockerfile | 4 +- changelog-development.md | 149 ++ changelog-nextnet.md | 36 + clients/nodejs/wallet_grpc_client/index.js | 2 +- common/Cargo.toml | 14 +- common/config/presets/c_base_node_c.toml | 38 +- common/config/presets/d_console_wallet.toml | 22 +- .../config/presets/f_merge_mining_proxy.toml | 2 - common/config/presets/g_miner.toml | 2 - common/src/build/application.rs | 148 ++ common/src/build/mod.rs | 5 + common/src/configuration/loader.rs | 8 +- common/src/configuration/name_server.rs | 2 +- common_sqlite/src/connection_options.rs | 6 +- common_sqlite/src/error.rs | 2 +- common_sqlite/src/sqlite_connection_pool.rs | 9 +- comms/core/Cargo.toml | 2 +- comms/core/examples/stress/node.rs | 3 +- comms/core/examples/tor.rs | 3 +- comms/core/src/bounded_executor.rs | 5 +- comms/core/src/builder/comms_node.rs | 26 +- comms/core/src/builder/mod.rs | 19 +- comms/core/src/connection_manager/common.rs | 19 +- comms/core/src/connection_manager/dialer.rs | 24 +- comms/core/src/connection_manager/error.rs | 4 +- comms/core/src/connection_manager/listener.rs | 12 +- comms/core/src/connection_manager/manager.rs | 13 +- comms/core/src/connection_manager/mod.rs | 6 +- .../src/connection_manager/peer_connection.rs | 22 +- .../{liveness.rs => self_liveness.rs} | 46 +- .../tests/listener_dialer.rs | 3 +- comms/core/src/connectivity/config.rs | 4 + .../core/src/connectivity/connection_pool.rs | 8 +- comms/core/src/connectivity/manager.rs | 108 +- comms/core/src/connectivity/requester.rs | 39 +- comms/core/src/connectivity/test.rs | 13 +- comms/core/src/lib.rs | 8 +- comms/core/src/memsocket/mod.rs | 8 +- comms/core/src/multiplexing/yamux.rs | 261 ++-- .../src/net_address/multiaddr_with_stats.rs | 166 ++- .../net_address/mutliaddresses_with_stats.rs | 49 +- comms/core/src/noise/config.rs | 2 +- comms/core/src/noise/socket.rs | 2 - comms/core/src/peer_manager/manager.rs | 49 +- comms/core/src/peer_manager/node_id.rs | 7 +- comms/core/src/peer_manager/peer.rs | 17 +- comms/core/src/peer_manager/peer_query.rs | 26 +- comms/core/src/peer_manager/peer_storage.rs | 2 +- comms/core/src/peer_validator/helpers.rs | 1 - comms/core/src/pipeline/inbound.rs | 3 - comms/core/src/pipeline/outbound.rs | 4 +- comms/core/src/protocol/error.rs | 2 +- comms/core/src/protocol/messaging/inbound.rs | 13 +- comms/core/src/protocol/messaging/protocol.rs | 1 + comms/core/src/protocol/messaging/test.rs | 101 +- comms/core/src/protocol/rpc/client/mod.rs | 55 +- comms/core/src/protocol/rpc/client/tests.rs | 7 +- comms/core/src/protocol/rpc/message.rs | 36 +- comms/core/src/protocol/rpc/mod.rs | 15 +- .../core/src/protocol/rpc/server/chunking.rs | 272 ---- comms/core/src/protocol/rpc/server/mod.rs | 13 +- comms/core/src/protocol/rpc/test/smoke.rs | 146 +- .../test_utils/mocks/connectivity_manager.rs | 5 + .../src/test_utils/mocks/peer_connection.rs | 12 +- comms/core/src/test_utils/transport.rs | 3 - comms/core/src/tor/control_client/client.rs | 2 +- comms/core/src/tor/control_client/types.rs | 5 +- .../examples/graphing_utilities/utilities.rs | 2 +- comms/dht/examples/memory_net/utilities.rs | 9 +- comms/dht/src/actor.rs | 2 - comms/dht/src/config.rs | 5 + comms/dht/src/connectivity/mod.rs | 380 +++-- comms/dht/src/connectivity/test.rs | 10 +- comms/dht/src/crypt.rs | 1 - comms/dht/src/dedup/dedup_cache.rs | 1 + comms/dht/src/dht.rs | 6 +- comms/dht/src/discovery/mod.rs | 5 +- comms/dht/src/discovery/requester.rs | 2 +- comms/dht/src/discovery/service.rs | 1 + comms/dht/src/inbound/dht_handler/task.rs | 1 + comms/dht/src/network_discovery/config.rs | 5 + .../dht/src/network_discovery/initializing.rs | 7 + comms/dht/src/network_discovery/on_connect.rs | 2 +- .../src/network_discovery/state_machine.rs | 2 +- comms/dht/src/network_discovery/test.rs | 3 + comms/dht/src/outbound/broadcast.rs | 7 +- comms/dht/src/outbound/message_params.rs | 2 +- comms/dht/src/peer_validator.rs | 29 +- comms/dht/src/rpc/test.rs | 2 +- comms/dht/src/store_forward/error.rs | 3 + .../dht/src/store_forward/saf_handler/task.rs | 7 +- comms/dht/src/store_forward/service.rs | 70 +- comms/dht/src/store_forward/store.rs | 6 +- comms/dht/tests/harness.rs | 1 + comms/rpc_macros/src/options.rs | 1 + hashing/src/borsh_hasher.rs | 17 +- hashing/src/domains.rs | 8 + hashing/src/lib.rs | 3 + infrastructure/tari_script/src/lib.rs | 21 +- infrastructure/tari_script/src/op_codes.rs | 2 +- infrastructure/tari_script/src/script.rs | 2 +- infrastructure/tari_script/src/stack.rs | 2 +- integration_tests/Cargo.toml | 2 +- integration_tests/log4rs/cucumber.yml | 2 +- integration_tests/src/base_node_process.rs | 1 - integration_tests/src/chat_client.rs | 7 +- integration_tests/src/chat_ffi.rs | 103 +- integration_tests/src/ffi/callbacks.rs | 4 +- integration_tests/src/ffi/contact.rs | 2 +- integration_tests/src/ffi/ffi_import.rs | 3 +- integration_tests/src/ffi/wallet.rs | 13 +- integration_tests/src/ffi/wallet_address.rs | 19 +- integration_tests/src/merge_mining_proxy.rs | 30 +- integration_tests/src/miner.rs | 30 +- integration_tests/src/transaction.rs | 4 +- integration_tests/src/wallet_ffi.rs | 4 +- integration_tests/src/world.rs | 21 +- .../tests/features/BlockTemplate.feature | 2 +- integration_tests/tests/features/Chat.feature | 7 + .../tests/features/ChatFFI.feature | 9 +- .../tests/features/WalletCli.feature | 16 - .../tests/features/WalletTransactions.feature | 1 + .../tests/steps/chat_ffi_steps.rs | 5 +- integration_tests/tests/steps/chat_steps.rs | 32 +- .../tests/steps/merge_mining_steps.rs | 4 +- integration_tests/tests/steps/node_steps.rs | 49 +- .../tests/steps/wallet_cli_steps.rs | 38 +- .../tests/steps/wallet_ffi_steps.rs | 4 +- integration_tests/tests/steps/wallet_steps.rs | 59 +- package-lock.json | 2 +- rust-toolchain.toml | 2 +- scripts/cross_compile_tooling.sh | 0 scripts/cross_compile_ubuntu_18-pre-build.sh | 166 +++ .../install_ubuntu_dependencies-rust-arm64.sh | 2 +- scripts/install_ubuntu_dependencies-rust.sh | 1 + scripts/install_ubuntu_dependencies.sh | 3 + scripts/test_in_docker.sh | 4 +- 507 files changed, 16902 insertions(+), 7066 deletions(-) rename .github/workflows/{base_node_binaries.json => build_binaries.json} (87%) rename .github/workflows/{base_node_binaries.yml => build_binaries.yml} (57%) rename base_layer/wallet/src/types.rs => applications/minotari_app_grpc/proto/p2pool.proto (74%) create mode 100644 applications/minotari_console_wallet/src/automation/utils.rs delete mode 100644 applications/minotari_ledger_wallet/Cargo.lock delete mode 100644 applications/minotari_ledger_wallet/Cargo.toml delete mode 100644 applications/minotari_ledger_wallet/app_nanosplus.json create mode 100644 applications/minotari_ledger_wallet/comms/Cargo.toml rename base_layer/core/src/common/limited_reader.rs => applications/minotari_ledger_wallet/comms/src/error.rs (50%) create mode 100644 applications/minotari_ledger_wallet/comms/src/ledger_wallet.rs create mode 100644 applications/minotari_ledger_wallet/comms/src/lib.rs delete mode 100644 applications/minotari_ledger_wallet/key_14x14.gif delete mode 100644 applications/minotari_ledger_wallet/key_16x16.gif delete mode 100644 applications/minotari_ledger_wallet/rust-toolchain.toml delete mode 100644 applications/minotari_ledger_wallet/rustfmt.toml delete mode 100644 applications/minotari_ledger_wallet/src/hashing.rs delete mode 100644 applications/minotari_ledger_wallet/src/main.rs delete mode 100644 applications/minotari_ledger_wallet/src/utils.rs create mode 100644 applications/minotari_ledger_wallet/wallet/.cargo/config.toml create mode 100644 applications/minotari_ledger_wallet/wallet/.gitignore create mode 100644 applications/minotari_ledger_wallet/wallet/Cargo.lock create mode 100644 applications/minotari_ledger_wallet/wallet/Cargo.toml rename applications/minotari_ledger_wallet/{ => wallet}/README.md (55%) create mode 100644 applications/minotari_ledger_wallet/wallet/build.rs create mode 100644 applications/minotari_ledger_wallet/wallet/key.gif create mode 100644 applications/minotari_ledger_wallet/wallet/key_14x14.gif create mode 100644 applications/minotari_ledger_wallet/wallet/ledger_app.toml create mode 100644 applications/minotari_ledger_wallet/wallet/rust-toolchain.toml create mode 100644 applications/minotari_ledger_wallet/wallet/src/app_ui/menu.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/handlers/get_dh_shared_secret.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/handlers/get_public_key.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/handlers/get_public_spend_key.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/handlers/get_script_offset.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/handlers/get_version.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/handlers/get_view_key.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/hashing.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/main.rs create mode 100644 applications/minotari_ledger_wallet/wallet/src/utils.rs delete mode 100644 base_layer/common_types/src/tari_address.rs create mode 100644 base_layer/common_types/src/tari_address/dual_address.rs create mode 100644 base_layer/common_types/src/tari_address/mod.rs create mode 100644 base_layer/common_types/src/tari_address/single_address.rs create mode 100644 base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/down.sql create mode 100644 base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/up.sql create mode 100644 base_layer/contacts/migrations/2024-05-27-145200_add_from_to_fields/up.sql create mode 100644 base_layer/core/src/blocks/faucets/mod.rs create mode 100644 base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql create mode 100644 base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql create mode 100644 common/src/build/application.rs rename comms/core/src/connection_manager/{liveness.rs => self_liveness.rs} (82%) delete mode 100644 comms/core/src/protocol/rpc/server/chunking.rs mode change 100644 => 100755 scripts/cross_compile_tooling.sh create mode 100755 scripts/cross_compile_ubuntu_18-pre-build.sh diff --git a/.github/workflows/base_node_binaries.json b/.github/workflows/build_binaries.json similarity index 87% rename from .github/workflows/base_node_binaries.json rename to .github/workflows/build_binaries.json index 3efa71f7c7..82c65dd94b 100644 --- a/.github/workflows/base_node_binaries.json +++ b/.github/workflows/build_binaries.json @@ -2,9 +2,10 @@ { "name": "linux-x86_64", "runs-on": "ubuntu-20.04", - "rust": "nightly-2024-02-04", + "rust": "nightly-2024-07-07", "target": "x86_64-unknown-linux-gnu", - "cross": false + "cross": false, + "build_metric": true }, { "name": "linux-arm64", @@ -12,7 +13,8 @@ "rust": "stable", "target": "aarch64-unknown-linux-gnu", "cross": true, - "flags": "--workspace --exclude minotari_mining_helper_ffi --exclude tari_integration_tests" + "flags": "--workspace --exclude minotari_mining_helper_ffi --exclude tari_integration_tests", + "build_metric": true }, { "name": "linux-riscv64", @@ -21,12 +23,12 @@ "target": "riscv64gc-unknown-linux-gnu", "cross": true, "flags": "--workspace --exclude minotari_mining_helper_ffi --exclude tari_integration_tests", - "build_enabled": false, - "best-effort": true + "build_enabled": true, + "best_effort": true }, { "name": "macos-x86_64", - "runs-on": "macos-11", + "runs-on": "macos-12", "rust": "stable", "target": "x86_64-apple-darwin", "cross": false, diff --git a/.github/workflows/base_node_binaries.yml b/.github/workflows/build_binaries.yml similarity index 57% rename from .github/workflows/base_node_binaries.yml rename to .github/workflows/build_binaries.yml index 6933402fd9..e8fc47b3d7 100644 --- a/.github/workflows/base_node_binaries.yml +++ b/.github/workflows/build_binaries.yml @@ -4,10 +4,10 @@ name: Build Matrix of Binaries 'on': push: tags: - - 'v[0-9]+.[0-9]+.[0-9]*' + - "v[0-9]+.[0-9]+.[0-9]*" branches: - - 'build-all-*' - - 'build-bins-*' + - "build-all-*" + - "build-bins-*" schedule: - cron: "05 00 * * *" workflow_dispatch: @@ -18,16 +18,16 @@ name: Build Matrix of Binaries default: "development-tag" env: - TBN_FILENAME: "tari_suite" - TBN_BUNDLE_ID_BASE: "com.tarilabs" - TBN_SIG_FN: "sha256-unsigned.txt" + TS_FILENAME: "tari_suite" + TS_BUNDLE_ID_BASE: "com.tarilabs" + TS_SIG_FN: "sha256-unsigned.txt" ## Must be a JSon string - TBN_FILES: '["minotari_node","minotari_console_wallet","minotari_miner","minotari_merge_mining_proxy"]' - TBN_FEATURES: "default, safe" - TBN_LIBRARIES: "minotari_mining_helper_ffi" + TS_FILES: '["minotari_node","minotari_console_wallet","minotari_miner","minotari_merge_mining_proxy"]' + TS_FEATURES: "default, safe" + TS_LIBRARIES: "minotari_mining_helper_ffi" TARI_NETWORK_DIR: testnet - toolchain: nightly-2024-02-04 - matrix-json-file: ".github/workflows/base_node_binaries.json" + toolchain: nightly-2024-07-07 + matrix-json-file: ".github/workflows/build_binaries.json" CARGO_HTTP_MULTIPLEXING: false CARGO_UNSTABLE_SPARSE_REGISTRY: true CARGO: cargo @@ -39,7 +39,7 @@ concurrency: group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') || github.ref != 'refs/heads/development' || github.ref != 'refs/heads/nextnet' || github.ref != 'refs/heads/stagenet' }} -permissions: { } +permissions: {} jobs: matrix-prep: @@ -56,11 +56,11 @@ jobs: run: | # # build all targets images - # matrix=$( jq -s -c .[] .github/workflows/base_node_binaries.json ) + # matrix=$( jq -s -c .[] .github/workflows/build_binaries.json ) # # build only single target image # matrix_selection=$( jq -c '.[] | select( ."name" == "windows-x64" )' ${{ env.matrix-json-file }} ) - # matrix_selection=$( jq -c '.[] | select( ."name" | contains("linux") )' ${{ env.matrix-json-file }} ) + # matrix_selection=$( jq -c '.[] | select( ."name" | contains("macos") )' ${{ env.matrix-json-file }} ) # # build select target images - build_enabled matrix_selection=$( jq -c '.[] | select( ."build_enabled" != false )' ${{ env.matrix-json-file }} ) @@ -91,13 +91,15 @@ jobs: builds: name: Building ${{ matrix.builds.name }} on ${{ matrix.builds.runs-on }} needs: matrix-prep - continue-on-error: ${{ matrix.builds.best-effort || false }} + continue-on-error: ${{ matrix.builds.best_effort || false }} outputs: TARI_NETWORK_DIR: ${{ steps.set-tari-network.outputs.TARI_NETWORK_DIR }} TARI_VERSION: ${{ steps.set-tari-vars.outputs.TARI_VERSION }} + VSHA_SHORT: ${{ steps.set-tari-vars.outputs.VSHA_SHORT }} strategy: fail-fast: false matrix: ${{ fromJson(needs.matrix-prep.outputs.matrix) }} + runs-on: ${{ matrix.builds.runs-on }} steps: @@ -129,20 +131,22 @@ jobs: shell: bash run: | echo "VBRANCH=${{ github.ref_name }}" >> $GITHUB_ENV - echo "VSHA_SHORT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + VSHA_SHORT=$(git rev-parse --short HEAD) + echo "VSHA_SHORT=${VSHA_SHORT}" >> $GITHUB_ENV + echo "VSHA_SHORT=${VSHA_SHORT}" >> $GITHUB_OUTPUT TARI_VERSION=$(awk -F ' = ' '$1 ~ /^version/ \ { gsub(/["]/, "", $2); printf("%s",$2) }' \ - "$GITHUB_WORKSPACE/Cargo.toml") + "$GITHUB_WORKSPACE/applications/minotari_node/Cargo.toml") echo "TARI_VERSION=${TARI_VERSION}" >> $GITHUB_ENV echo "TARI_VERSION=${TARI_VERSION}" >> $GITHUB_OUTPUT if [[ "${{ matrix.builds.features }}" == "" ]]; then - echo "BUILD_FEATURES=${{ env.TBN_FEATURES }}" >> $GITHUB_ENV + echo "BUILD_FEATURES=${{ env.TS_FEATURES }}" >> $GITHUB_ENV else echo "BUILD_FEATURES=${{ matrix.builds.features }}" >> $GITHUB_ENV fi TARGET_BINS="" if [[ "${{ matrix.builds.target_bins }}" == "" ]]; then - ARRAY_BINS=( $(echo ${TBN_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) + ARRAY_BINS=( $(echo ${TS_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) else ARRAY_BINS=( $(echo "${{ matrix.builds.target_bins }}" | tr ', ' '\n') ) fi @@ -153,7 +157,7 @@ jobs: echo "TARGET_BINS=${TARGET_BINS}" >> $GITHUB_ENV TARGET_LIBS="" if [[ "${{ matrix.builds.target_libs }}" == "" ]]; then - ARRAY_LIBS=( $(echo ${TBN_LIBRARIES} | tr ', ' '\n') ) + ARRAY_LIBS=( $(echo ${TS_LIBRARIES} | tr ', ' '\n') ) else ARRAY_LIBS=( $(echo "${{ matrix.builds.target_libs }}" | tr ', ' '\n') ) fi @@ -199,7 +203,8 @@ jobs: - name: Install macOS dependencies if: startsWith(runner.os,'macOS') run: | - brew install openssl cmake coreutils automake autoconf protobuf + # openssl, cmake and autoconf already installed + brew install zip coreutils automake protobuf rustup target add ${{ matrix.builds.target }} - name: Install Windows dependencies @@ -221,10 +226,10 @@ jobs: run: | echo "SHARUN=shasum --algorithm 256" >> $GITHUB_ENV echo "CC=gcc" >> $GITHUB_ENV - echo "TBN_EXT=" >> $GITHUB_ENV + echo "TS_EXT=" >> $GITHUB_ENV echo "SHELL_EXT=.sh" >> $GITHUB_ENV echo "PLATFORM_SPECIFIC_DIR=linux" >> $GITHUB_ENV - echo "TBN_DIST=/dist" >> $GITHUB_ENV + echo "TS_DIST=/dist" >> $GITHUB_ENV - name: Set environment variables - macOS if: startsWith(runner.os,'macOS') @@ -258,10 +263,10 @@ jobs: curl -v -o "$GITHUB_WORKSPACE\psutils\getopt.ps1" "https://raw.githubusercontent.com/lukesampson/psutils/master/getopt.ps1" curl -v -o "$GITHUB_WORKSPACE\psutils\shasum.ps1" "https://raw.githubusercontent.com/lukesampson/psutils/master/shasum.ps1" echo "SHARUN=pwsh $GITHUB_WORKSPACE\psutils\shasum.ps1 --algorithm 256" >> $GITHUB_ENV - echo "TBN_EXT=.exe" >> $GITHUB_ENV + echo "TS_EXT=.exe" >> $GITHUB_ENV echo "LIB_EXT=.dll" >> $GITHUB_ENV echo "SHELL_EXT=.bat" >> $GITHUB_ENV - echo "TBN_DIST=\dist" >> $GITHUB_ENV + echo "TS_DIST=\dist" >> $GITHUB_ENV echo "PLATFORM_SPECIFIC_DIR=windows" >> $GITHUB_ENV echo "SQLITE3_LIB_DIR=C:\vcpkg\installed\x64-windows\lib" >> $GITHUB_ENV echo "OPENSSL_DIR=C:\Program Files\OpenSSL-Win64" >> $GITHUB_ENV @@ -278,7 +283,8 @@ jobs: if: ${{ matrix.builds.cross }} shell: bash run: | - cargo install cross + #cargo install cross + cargo install cross --git https://github.com/cross-rs/cross echo "CARGO=cross" >> $GITHUB_ENV - name: Install and setup cargo-auditable @@ -317,28 +323,28 @@ jobs: - name: Copy binaries to folder for archiving shell: bash run: | - set -xo pipefail - mkdir -p "$GITHUB_WORKSPACE${TBN_DIST}" - cd "$GITHUB_WORKSPACE${TBN_DIST}" - BINFILE="${TBN_FILENAME}-${TARI_VERSION}-${VSHA_SHORT}-${{ matrix.builds.name }}${TBN_EXT}" + # set -xo pipefail + mkdir -p "$GITHUB_WORKSPACE${TS_DIST}" + cd "$GITHUB_WORKSPACE${TS_DIST}" + BINFILE="${TS_FILENAME}-${TARI_VERSION}-${VSHA_SHORT}-${{ matrix.builds.name }}${TS_EXT}" echo "BINFILE=${BINFILE}" >> $GITHUB_ENV echo "Copying files for ${BINFILE} to $(pwd)" echo "MTS_SOURCE=$(pwd)" >> $GITHUB_ENV ls -alht "$GITHUB_WORKSPACE/target/${{ matrix.builds.target }}/release/" - ARRAY_FILES=( $(echo ${TBN_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) + ARRAY_FILES=( $(echo ${TS_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) for FILE in "${ARRAY_FILES[@]}"; do - echo "checking for file - ${FILE}${TBN_EXT}" - if [ -f "${GITHUB_WORKSPACE}/target/${{ matrix.builds.target }}/release/${FILE}${TBN_EXT}" ]; then - cp -vf "${GITHUB_WORKSPACE}/target/${{ matrix.builds.target }}/release/${FILE}${TBN_EXT}" . + echo "checking for file - ${FILE}${TS_EXT}" + if [ -f "${GITHUB_WORKSPACE}/target/${{ matrix.builds.target }}/release/${FILE}${TS_EXT}" ]; then + cp -vf "${GITHUB_WORKSPACE}/target/${{ matrix.builds.target }}/release/${FILE}${TS_EXT}" . fi done if [[ "${{ matrix.builds.target_libs }}" == "" ]]; then - ARRAY_LIBS=( $(echo ${TBN_LIBRARIES} | tr ', ' '\n') ) + ARRAY_LIBS=( $(echo ${TS_LIBRARIES} | tr ', ' '\n') ) else ARRAY_LIBS=( $(echo "${{ matrix.builds.target_libs }}" | tr ', ' '\n') ) fi for FILE in "${ARRAY_LIBS[@]}"; do - echo "checking for file - ${FILE}${TBN_EXT}" + echo "checking for file - ${FILE}${TS_EXT}" # Check on Nix for libs if [ -f "${GITHUB_WORKSPACE}/target/${{ matrix.builds.target }}/release/lib${FILE}${LIB_EXT}" ]; then cp -vf "${GITHUB_WORKSPACE}/target/${{ matrix.builds.target }}/release/lib${FILE}${LIB_EXT}" . @@ -353,8 +359,8 @@ jobs: fi ls -alhtR ${{ env.MTS_SOURCE }} - - name: Build minotari_node metrics release binary for linux-x86_64 - if: ${{ startsWith(runner.os,'Linux') && ( ! matrix.builds.cross ) && matrix.builds.name == 'linux-x86_64' }} + - name: Build minotari_node with metrics too + if: ${{ matrix.builds.build_metric }} shell: bash run: | ${{ env.CARGO }} build ${{ env.CARGO_OPTIONS }} \ @@ -362,7 +368,8 @@ jobs: --features "${{ env.BUILD_FEATURES }}, metrics" \ --bin minotari_node \ ${{ matrix.builds.flags }} --locked - cp -vf "$GITHUB_WORKSPACE/target/${{ matrix.builds.target }}/release/minotari_node" "${{ env.MTS_SOURCE }}/minotari_node-metrics" + cp -vf "$GITHUB_WORKSPACE/target/${{ matrix.builds.target }}/release/minotari_node${TS_EXT}" \ + "${{ env.MTS_SOURCE }}/minotari_node-metrics${TS_EXT}" - name: Build targeted miners # if: ${{ ( startsWith(github.ref, 'refs/tags/v') ) && ( matrix.builds.miner_cpu_targets != '' ) }} @@ -378,7 +385,8 @@ jobs: --features "${{ env.BUILD_FEATURES }}" \ --bin minotari_miner \ ${{ matrix.builds.flags }} --locked - cp -vf "$GITHUB_WORKSPACE/target/${{ matrix.builds.target }}/release/minotari_miner" "${{ env.MTS_SOURCE }}/minotari_miner-${CPU_TARGET}" + cp -vf "$GITHUB_WORKSPACE/target/${{ matrix.builds.target }}/release/minotari_miner" \ + "${{ env.MTS_SOURCE }}/minotari_miner-${CPU_TARGET}" done - name: Pre/unsigned OSX Artifact upload for Archive @@ -388,7 +396,7 @@ jobs: continue-on-error: true uses: actions/upload-artifact@v4 with: - name: ${{ env.TBN_FILENAME }}_unsigned-archive-${{ matrix.builds.name }} + name: ${{ env.TS_FILENAME }}_unsigned-archive-${{ matrix.builds.name }} path: "${{ env.MTS_SOURCE }}/*" - name: Build the macOS pkg @@ -424,7 +432,7 @@ jobs: export tarball_parent="${{ runner.temp }}/osxpkg" export tarball_source="${{ env.TARI_NETWORK_DIR }}" ./create_osx_install_zip.sh unused nozip - ARRAY_FILES=( $(echo ${TBN_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) + ARRAY_FILES=( $(echo ${TS_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) find "${GITHUB_WORKSPACE}/${target_release}" \ -name "randomx-*" -type f -perm -+x \ -exec cp -vf {} "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/runtime/" \; @@ -437,7 +445,7 @@ jobs: ARRAY_FILES+=(${FILES_DIAG_UTILS[@]}) for FILE in "${ARRAY_FILES[@]}"; do codesign --options runtime --force --verify --verbose --timestamp ${OSX_CODESIGN_EXTRAS} \ - --prefix "${{ env.TBN_BUNDLE_ID_BASE }}.${{ env.TBN_FILENAME }}." \ + --prefix "${{ env.TS_BUNDLE_ID_BASE }}.${{ env.TS_FILENAME }}." \ --sign "Developer ID Application: $MACOS_APPLICATION_ID" \ "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/runtime/$FILE" codesign --verify --deep --display --verbose=4 \ @@ -445,88 +453,75 @@ jobs: cp -vf "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/runtime/$FILE" \ "${{ env.MTS_SOURCE }}" done - distDirPKG=$(mktemp -d -t ${{ env.TBN_FILENAME }}) + distDirPKG=$(mktemp -d -t ${{ env.TS_FILENAME }}) echo "${distDirPKG}" echo "distDirPKG=${distDirPKG}" >> $GITHUB_ENV - TBN_Temp=${{ env.TBN_FILENAME }} - TBN_BUNDLE_ID_VALID_NAME=$(echo "${TBN_Temp//_/-}") + TS_Temp=${{ env.TS_FILENAME }} + TS_BUNDLE_ID_VALID_NAME=$(echo "${TS_Temp//_/-}") # Strip apple-darwin - TBN_ARCH=$(echo "${${{ matrix.builds.target }}//-apple-darwin/}") + TS_ARCH=$(echo "${${{ matrix.builds.target }}//-apple-darwin/}") pkgbuild --root "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}" \ - --identifier "${{ env.TBN_BUNDLE_ID_BASE }}.pkg.${TBN_BUNDLE_ID_VALID_NAME}" \ + --identifier "${{ env.TS_BUNDLE_ID_BASE }}.pkg.${TS_BUNDLE_ID_VALID_NAME}" \ --version "${TARI_VERSION}" \ --install-location "/tmp/tari" \ --scripts "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/scripts" \ --sign "Developer ID Installer: ${MACOS_INSTALLER_ID}" \ - "${distDirPKG}/${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" + "${distDirPKG}/${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" echo -e "Submitting to Apple...\n\n" - xcrun altool --notarize-app \ - --primary-bundle-id "${{ env.TBN_BUNDLE_ID_BASE }}.pkg.${TBN_BUNDLE_ID_VALID_NAME}" \ - --username "${MACOS_NOTARIZE_USERNAME}" --password "${MACOS_NOTARIZE_PASSWORD}" \ - --asc-provider "${MACOS_ASC_PROVIDER}" \ - --file "${distDirPKG}/${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" &> notarisation.result - requestUUID=`grep RequestUUID notarisation.result | cut -d" " -f 3` - echo ${requestUUID} - if [[ ${requestUUID} == "" ]]; then - echo "could not upload for notarization" + xcrun notarytool submit \ + "${distDirPKG}/${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" \ + --apple-id "${MACOS_NOTARIZE_USERNAME}" \ + --password ${MACOS_NOTARIZE_PASSWORD} \ + --team-id ${MACOS_ASC_PROVIDER} \ + --verbose --wait 2>&1 | tee -a notarisation.result + # Maybe use line from with "Processing complete"? + requestUUID=$(tail -n5 notarisation.result | grep "id:" | cut -d" " -f 4) + requestSTATUS=$(tail -n5 notarisation.result | grep "\ \ status:" | cut -d" " -f 4) + if [[ ${requestUUID} == "" ]] || [[ ${requestSTATUS} != "Accepted" ]]; then + echo "## status: ${requestSTATUS} - could not notarize - ${requestUUID} - ${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" exit 1 else echo "Notarization RequestUUID: ${requestUUID}" - fi - echo -e "\n\nChecking result of notarisation..." - request_status="in progress" - while [[ "${request_status}" == "in progress" ]]; do - echo -n "waiting... " - sleep 10 - request_status=$(xcrun altool --notarization-info ${requestUUID} --username "${MACOS_NOTARIZE_USERNAME}" --password "${MACOS_NOTARIZE_PASSWORD}" 2>&1) - echo "${request_status}" - request_status=$(echo "${request_status}" | awk -F ': ' '/Status:/ { print $2; }' ) - echo "${request_status}" - done - echo "${request_status}" - if [[ ${request_status} != "success" ]]; then - echo "## could not notarize - ${request_status} - ${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" - exit 1 - else - echo -e "\nStapling package...${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg\n" - xcrun stapler staple -v "${distDirPKG}/${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" + echo -e "\nStapling package...\ + ${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg\n" + xcrun stapler staple -v \ + "${distDirPKG}/${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" fi cd ${distDirPKG} - ls -la echo "Compute pkg shasum" - ${SHARUN} "${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" \ - >> "${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg.sha256" - cat "${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg.sha256" + ${SHARUN} "${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg" \ + >> "${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg.sha256" + cat "${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg.sha256" echo "Checksum verification for pkg is " - ${SHARUN} --check "${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg.sha256" + ${SHARUN} --check "${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg.sha256" - name: Artifact upload for macOS pkg if: startsWith(runner.os,'macOS') continue-on-error: true uses: actions/upload-artifact@v4 with: - name: ${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg - path: "${{ env.distDirPKG }}/${{ env.TBN_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}*.pkg*" + name: ${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}.pkg + path: "${{ env.distDirPKG }}/${{ env.TS_FILENAME }}-${{ matrix.builds.name }}-${{ env.TARI_VERSION }}*.pkg*" - name: Build the Windows installer - shell: cmd if: startsWith(runner.os,'Windows') + shell: cmd run: | cd buildtools - "%programfiles(x86)%\Inno Setup 6\iscc.exe" "/DMyAppVersion=${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer" "/DMinotariSuite=${{ env.TBN_FILENAME }}" "/DTariSuitePath=${{ github.workspace }}${{ env.TBN_DIST }}" "windows_inno_installer.iss" + "%programfiles(x86)%\Inno Setup 6\iscc.exe" "/DMyAppVersion=${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer" "/DMinotariSuite=${{ env.TS_FILENAME }}" "/DTariSuitePath=${{ github.workspace }}${{ env.TS_DIST }}" "windows_inno_installer.iss" cd Output echo "Compute archive shasum" - ${{ env.SHARUN }} "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe" >> "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe.sha256" + ${{ env.SHARUN }} "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe" >> "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe.sha256" echo "Show the shasum" - cat "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe.sha256" + cat "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe.sha256" echo "Checksum verification archive is " - ${{ env.SHARUN }} --check "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe.sha256" + ${{ env.SHARUN }} --check "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}-${{ env.VSHA_SHORT }}-${{ matrix.builds.name }}-installer.exe.sha256" - name: Artifact upload for Windows installer - uses: actions/upload-artifact@v4 if: startsWith(runner.os,'Windows') + uses: actions/upload-artifact@v4 with: - name: "${{ env.TBN_FILENAME }}_windows_installer" + name: "${{ env.TS_FILENAME }}_windows_installer" path: "${{ github.workspace }}/buildtools/Output/*" - name: Archive and Checksum Binaries @@ -551,8 +546,8 @@ jobs: - name: Artifact upload for Archive uses: actions/upload-artifact@v4 with: - name: ${{ env.TBN_FILENAME }}_archive-${{ matrix.builds.name }} - path: "${{ github.workspace }}${{ env.TBN_DIST }}/${{ env.BINFILE }}.zip*" + name: ${{ env.TS_FILENAME }}_archive-${{ matrix.builds.name }} + path: "${{ github.workspace }}${{ env.TS_DIST }}/${{ env.BINFILE }}.zip*" - name: Prep diag-utils archive for upload continue-on-error: true @@ -562,65 +557,264 @@ jobs: cd "${{ env.MTS_SOURCE }}-diag-utils" # Find RandomX built tools for testing find "$GITHUB_WORKSPACE/target/${{ matrix.builds.target }}/release/" \ - -name "randomx-*${{ env.TBN_EXT}}" -type f -perm -+x -exec cp -vf {} . \; + -name "randomx-*${{ env.TS_EXT}}" -type f -perm -+x -exec cp -vf {} . \; echo "Compute diag utils shasum" ${SHARUN} * \ - >> "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.sha256" - cat "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.sha256" + >> "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.sha256" + cat "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.sha256" echo "Checksum verification for diag utils is " - ${SHARUN} --check "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.sha256" - 7z a "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip" * + ${SHARUN} --check "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.sha256" + 7z a "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip" * echo "Compute diag utils archive shasum" - ${SHARUN} "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip" \ - >> "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip.sha256" - cat "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip.sha256" + ${SHARUN} "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip" \ + >> "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip.sha256" + cat "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip.sha256" echo "Checksum verification for diag utils archive is " - ${SHARUN} --check "${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip.sha256" + ${SHARUN} --check "${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }}.zip.sha256" - name: Artifact upload for diag-utils continue-on-error: true uses: actions/upload-artifact@v4 with: - name: ${{ env.TBN_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }} - path: "${{ github.workspace }}${{ env.TBN_DIST }}-diag-utils/*.zip*" + name: ${{ env.TS_FILENAME }}_archive-diag-utils-${{ matrix.builds.name }} + path: "${{ github.workspace }}${{ env.TS_DIST }}-diag-utils/*.zip*" + + macOS-universal-assemble: + name: macOS universal assemble + needs: builds + + env: + TARI_VERSION: ${{ needs.builds.outputs.TARI_VERSION }} + VSHA_SHORT: ${{ needs.builds.outputs.VSHA_SHORT }} + SHARUN: "shasum --algorithm 256" + + continue-on-error: true + + runs-on: macos-14 + + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Download macOS binaries + uses: actions/download-artifact@v4 + with: + path: osxuni + # macos - x86_64 / arm64 + pattern: ${{ env.TS_FILENAME }}_archive-macos-* + merge-multiple: true + + - name: Set environment variables for macOS universal + shell: bash + run: | + BINFN="${TS_FILENAME}-${TARI_VERSION}-${VSHA_SHORT}" + echo "BINFN=${BINFN}" >> $GITHUB_ENV + + - name: Install macOS dependencies + shell: bash + run: | + brew install coreutils + + - name: Verify checksums and extract + shell: bash + working-directory: osxuni + run: | + ls -alhtR + ${SHARUN} --ignore-missing --check \ + "${{ env.BINFN }}-macos-x86_64.zip.sha256" + ${SHARUN} --ignore-missing --check \ + "${{ env.BINFN }}-macos-arm64.zip.sha256" + ls -alhtR + mkdir macos-universal macos-x86_64 macos-arm64 + cd macos-x86_64 + 7z e "../${{ env.BINFN }}-macos-x86_64.zip" + cd ../macos-arm64 + 7z e "../${{ env.BINFN }}-macos-arm64.zip" + + - name: Assemble macOS universal binaries + shell: bash + working-directory: osxuni + run: | + ls -alhtR + ARRAY_FILES=( $(echo ${TS_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) + for FILE in "${ARRAY_FILES[@]}"; do + echo "processing binary file - ${FILE}" + lipo -create -output macos-universal/${FILE} \ + macos-x86_64/${FILE} \ + macos-arm64/${FILE} + done + ARRAY_LIBS=( $(echo ${TS_LIBRARIES} | tr ', ' '\n') ) + for FILE in "${ARRAY_LIBS[@]}"; do + echo "processing library file - lib${FILE}.dylib" + lipo -create -output macos-universal/lib${FILE}.dylib \ + macos-x86_64/lib${FILE}.dylib \ + macos-arm64/lib${FILE}.dylib + done + ls -alhtR macos-universal + + - name: Build the macOS universal pkg + continue-on-error: true + env: + MACOS_KEYCHAIN_PASS: ${{ secrets.MACOS_KEYCHAIN_PASS }} + MACOS_APPLICATION_ID: ${{ secrets.MACOS_APPLICATION_ID }} + MACOS_APPLICATION_CERT: ${{ secrets.MACOS_APPLICATION_CERT }} + MACOS_APPLICATION_PASS: ${{ secrets.MACOS_APPLICATION_PASS }} + MACOS_INSTALLER_ID: ${{ secrets.MACOS_INSTALLER_ID }} + MACOS_INSTALLER_CERT: ${{ secrets.MACOS_INSTALLER_CERT }} + MACOS_INSTALLER_PASS: ${{ secrets.MACOS_INSTALLER_PASS }} + MACOS_NOTARIZE_USERNAME: ${{ secrets.MACOS_NOTARIZE_USERNAME }} + MACOS_NOTARIZE_PASSWORD: ${{ secrets.MACOS_NOTARIZE_PASSWORD }} + MACOS_ASC_PROVIDER: ${{ secrets.MACOS_ASC_PROVIDER }} + run: | + echo $MACOS_APPLICATION_CERT | base64 --decode > application.p12 + echo $MACOS_INSTALLER_CERT | base64 --decode > installer.p12 + security create-keychain -p $MACOS_KEYCHAIN_PASS build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p $MACOS_KEYCHAIN_PASS build.keychain + security import application.p12 -k build.keychain -P $MACOS_APPLICATION_PASS -T /usr/bin/codesign + security import installer.p12 -k build.keychain -P $MACOS_INSTALLER_PASS -T /usr/bin/pkgbuild + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_KEYCHAIN_PASS build.keychain + OSX_CODESIGN_EXTRAS="--entitlements ${GITHUB_WORKSPACE}/applications/minotari_node/osx-pkg/entitlements.xml" + cd buildtools + # export target_release="target/${{ matrix.builds.target }}/release" + # matrix.builds.target=macos-universal + # matrix.builds.name=macos-universal + export target_release="osxuni/macos-universal" + mkdir -p "${{ runner.temp }}/osxpkg" + export tarball_parent="${{ runner.temp }}/osxpkg" + export tarball_source="${{ env.TARI_NETWORK_DIR }}" + ./create_osx_install_zip.sh unused nozip + ARRAY_FILES=( $(echo ${TS_FILES} | jq --raw-output '.[]' | awk '{ print $1 }') ) + for FILE in "${ARRAY_FILES[@]}"; do + codesign --options runtime --force --verify --verbose --timestamp ${OSX_CODESIGN_EXTRAS} \ + --prefix "${{ env.TS_BUNDLE_ID_BASE }}.${{ env.TS_FILENAME }}." \ + --sign "Developer ID Application: $MACOS_APPLICATION_ID" \ + "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/runtime/$FILE" + codesign --verify --deep --display --verbose=4 \ + "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/runtime/$FILE" + cp -vf "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/runtime/$FILE" \ + "${{ github.workspace }}/osxuni/macos-universal/" + done + distDirPKG=$(mktemp -d -t ${{ env.TS_FILENAME }}) + echo "${distDirPKG}" + echo "distDirPKG=${distDirPKG}" >> $GITHUB_ENV + TS_Temp=${{ env.TS_FILENAME }} + TS_BUNDLE_ID_VALID_NAME=$(echo "${TS_Temp//_/-}") + TS_ARCH=universal + pkgbuild --root "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}" \ + --identifier "${{ env.TS_BUNDLE_ID_BASE }}.pkg.${TS_BUNDLE_ID_VALID_NAME}" \ + --version "${TARI_VERSION}" \ + --install-location "/tmp/tari" \ + --scripts "${{ runner.temp }}/osxpkg/${{ env.TARI_NETWORK_DIR }}/scripts" \ + --sign "Developer ID Installer: ${MACOS_INSTALLER_ID}" \ + "${distDirPKG}/${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg" + echo -e "Submitting to Apple...\n\n" + xcrun notarytool submit \ + "${distDirPKG}/${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg" \ + --apple-id "${MACOS_NOTARIZE_USERNAME}" \ + --password ${MACOS_NOTARIZE_PASSWORD} \ + --team-id ${MACOS_ASC_PROVIDER} \ + --verbose --wait 2>&1 | tee -a notarisation.result + # Maybe use line from with "Processing complete"? + requestUUID=$(tail -n5 notarisation.result | grep "id:" | cut -d" " -f 4) + requestSTATUS=$(tail -n5 notarisation.result | grep "\ \ status:" | cut -d" " -f 4) + if [[ ${requestUUID} == "" ]] || [[ ${requestSTATUS} != "Accepted" ]]; then + echo "## status: ${requestSTATUS} - could not notarize - ${requestUUID} - ${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg" + exit 1 + else + echo "Notarization RequestUUID: ${requestUUID}" + echo -e "\nStapling package...\ + ${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg\n" + xcrun stapler staple -v \ + "${distDirPKG}/${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg" + fi + cd ${distDirPKG} + echo "Compute pkg shasum" + ${SHARUN} "${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg" \ + >> "${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg.sha256" + cat "${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg.sha256" + echo "Checksum verification for pkg is " + ${SHARUN} --check "${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg.sha256" + + - name: Artifact upload for macOS universal pkg + if: startsWith(runner.os,'macOS') + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + name: ${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}.pkg + path: "${{ env.distDirPKG }}/${{ env.TS_FILENAME }}-macos-universal-${{ env.TARI_VERSION }}*.pkg*" + + - name: Archive and Checksum macOS universal Binaries + shell: bash + working-directory: osxuni/macos-universal + run: | + # set -xo pipefail + BINFILE="${BINFN}-macos-universal" + echo "BINFILE=${BINFILE}" >> $GITHUB_ENV + echo "Archive ${BINFILE} into ${BINFILE}.zip" + echo "Compute files shasum into ${BINFILE}.sha256" + ${SHARUN} * >> "${BINFILE}.sha256" + echo "Show the shasum" + cat "${BINFILE}.sha256" + echo "Checksum verification for files is " + ${SHARUN} --check "${BINFILE}.sha256" + 7z a "${BINFILE}.zip" * + echo "Compute archive shasum into ${BINFILE}.zip.sha256" + ${SHARUN} "${BINFILE}.zip" >> "${BINFILE}.zip.sha256" + echo "Show the shasum from ${BINFILE}.zip.sha256" + cat "${BINFILE}.zip.sha256" + echo "Checksum verification archive is " + ${SHARUN} --check "${BINFILE}.zip.sha256" + + - name: Artifact upload for Archive + uses: actions/upload-artifact@v4 + with: + name: ${{ env.TS_FILENAME }}_archive-macos-universal + path: "${{ github.workspace }}/osxuni/macos-universal/${{ env.BINFILE }}.zip*" + create-release: if: ${{ startsWith(github.ref, 'refs/tags/v') }} + runs-on: ubuntu-latest - needs: builds + needs: [ builds, macOS-universal-assemble ] + env: TARI_NETWORK_DIR: ${{ needs.builds.outputs.TARI_NETWORK_DIR }} TARI_VERSION: ${{ needs.builds.outputs.TARI_VERSION }} + permissions: + contents: write + steps: - name: Download binaries uses: actions/download-artifact@v4 with: - path: ${{ env.TBN_FILENAME }} - pattern: "${{ env.TBN_FILENAME }}*" + path: ${{ env.TS_FILENAME }} + pattern: "${{ env.TS_FILENAME }}*" merge-multiple: true - name: Verify checksums and Prep Uploads shell: bash - working-directory: ${{ env.TBN_FILENAME }} + working-directory: ${{ env.TS_FILENAME }} run: | # set -xo pipefail sudo apt-get update sudo apt-get --no-install-recommends --assume-yes install dos2unix ls -alhtR - if [ -f "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TBN_SIG_FN }}" ] ; then - rm -fv "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TBN_SIG_FN }}" + if [ -f "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TS_SIG_FN }}" ] ; then + rm -fv "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TS_SIG_FN }}" fi # Merge all sha256 files into one - find . -name "*.sha256" -type f -print | xargs cat >> "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TBN_SIG_FN }}" - dos2unix --quiet "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TBN_SIG_FN }}" - cat "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TBN_SIG_FN }}" - sha256sum --ignore-missing --check "${{ env.TBN_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TBN_SIG_FN }}" + find . -name "*.sha256" -type f -print | xargs cat >> "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TS_SIG_FN }}" + dos2unix --quiet "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TS_SIG_FN }}" + cat "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TS_SIG_FN }}" + sha256sum --ignore-missing --check "${{ env.TS_FILENAME }}-${{ env.TARI_VERSION }}.${{ env.TS_SIG_FN }}" ls -alhtR - name: Create release uses: ncipollo/release-action@v1 with: - artifacts: "${{ env.TBN_FILENAME }}*/**/*" + artifacts: "${{ env.TS_FILENAME }}*/**/*" token: ${{ secrets.GITHUB_TOKEN }} prerelease: true draft: true @@ -637,28 +831,28 @@ jobs: S3CMD: "cp" S3OPTIONS: '--recursive --exclude "*" --include "*.sha256*" --include "*.zip*" --include "*.pkg*" --include "*installer.exe*"' shell: bash - working-directory: ${{ env.TBN_FILENAME }} + working-directory: ${{ env.TS_FILENAME }} run: | echo "Upload processing ..." ls -alhtR echo "Clean up" # Bash check if file with wildcards, does not work as expected - # if [ -f ${{ env.TBN_FILENAME }}*diag-utils* ] ; then - if ls ${{ env.TBN_FILENAME }}*diag-utils* > /dev/null 2>&1 ; then - rm -fv ${{ env.TBN_FILENAME }}*diag-utils* + # if [ -f ${{ env.TS_FILENAME }}*diag-utils* ] ; then + if ls ${{ env.TS_FILENAME }}*diag-utils* > /dev/null 2>&1 ; then + rm -fv ${{ env.TS_FILENAME }}*diag-utils* fi echo "Folder setup" - if ls ${{ env.TBN_FILENAME }}*linux* > /dev/null 2>&1 ; then + if ls ${{ env.TS_FILENAME }}*linux* > /dev/null 2>&1 ; then mkdir -p "linux/${{ env.TARI_NETWORK_DIR }}/" - mv -v ${{ env.TBN_FILENAME }}*linux* "linux/${{ env.TARI_NETWORK_DIR }}/" + mv -v ${{ env.TS_FILENAME }}*linux* "linux/${{ env.TARI_NETWORK_DIR }}/" fi - if ls ${{ env.TBN_FILENAME }}*macos* > /dev/null 2>&1 ; then + if ls ${{ env.TS_FILENAME }}*macos* > /dev/null 2>&1 ; then mkdir -p "osx/${{ env.TARI_NETWORK_DIR }}/" - mv -v ${{ env.TBN_FILENAME }}*macos* "osx/${{ env.TARI_NETWORK_DIR }}/" + mv -v ${{ env.TS_FILENAME }}*macos* "osx/${{ env.TARI_NETWORK_DIR }}/" fi - if ls ${{ env.TBN_FILENAME }}*windows* > /dev/null 2>&1 ; then + if ls ${{ env.TS_FILENAME }}*windows* > /dev/null 2>&1 ; then mkdir -p "windows/${{ env.TARI_NETWORK_DIR }}/" - mv -v ${{ env.TBN_FILENAME }}*windows* "windows/${{ env.TARI_NETWORK_DIR }}/" + mv -v ${{ env.TS_FILENAME }}*windows* "windows/${{ env.TARI_NETWORK_DIR }}/" fi ls -alhtR aws --version diff --git a/.github/workflows/build_dockers.yml b/.github/workflows/build_dockers.yml index 001881331b..73c8dc26f0 100644 --- a/.github/workflows/build_dockers.yml +++ b/.github/workflows/build_dockers.yml @@ -48,7 +48,7 @@ name: Build docker images - xmrig env: - toolchain_default: nightly-2024-02-04 + toolchain_default: nightly-2024-07-07 concurrency: # https://docs.github.com/en/actions/examples/using-concurrency-expressions-and-a-test-matrix @@ -88,6 +88,12 @@ jobs: echo "tag_alias=latest" >> $GITHUB_OUTPUT echo "build_items=all" >> $GITHUB_OUTPUT fi + if [[ "${{ github.ref }}" =~ ^refs/heads/build-dockers-* ]] || [[ "${{ github.ref }}" =~ ^refs/heads/build-all-* ]] ; then + echo "Branch Build - limited dual arch" + echo "platforms=linux/arm64, linux/amd64" >> $GITHUB_OUTPUT + echo "tag_alias=latest-weekly" >> $GITHUB_OUTPUT + echo "build_items=minotari_all" >> $GITHUB_OUTPUT + fi if [ "${{ github.event_name }}" == "workflow_dispatch" ] ; then echo "Manual Build - selective" echo "platforms=${{ github.event.inputs.platforms }}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/build_dockers_workflow.yml b/.github/workflows/build_dockers_workflow.yml index c24e98e779..621aa77345 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-2024-02-04 + default: nightly-2024-07-07 arch: type: string default: x86-64 @@ -215,7 +215,7 @@ jobs: - name: Docker image build and push id: docker_build - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: ./tari/ file: ./tari-launchpad/docker_rig/${{ env.DOCKERFILE }}.Dockerfile diff --git a/.github/workflows/build_libffis.yml b/.github/workflows/build_libffis.yml index dcb47def4a..344df34b38 100644 --- a/.github/workflows/build_libffis.yml +++ b/.github/workflows/build_libffis.yml @@ -150,7 +150,8 @@ jobs: - name: Install macOS dependencies if: startsWith(runner.os,'macOS') run: | - brew install openssl cmake coreutils automake autoconf protobuf + # openssl and cmake already installed + brew install coreutils automake autoconf protobuf rustup target add ${{ matrix.builds.target }} - name: Setup Rust toolchain @@ -171,7 +172,8 @@ jobs: if: ${{ matrix.builds.cross }} shell: bash run: | - cargo install cross + #cargo install cross + cargo install cross --git https://github.com/cross-rs/cross echo "CARGO=cross" >> $GITHUB_ENV - name: Install rust target/toolchain for native/local cross-compile builds @@ -247,9 +249,8 @@ jobs: - name: Download iOS libffiss for ${{ matrix.libffis }} uses: actions/download-artifact@v4 with: - # wildcard downloads not supported yet ( minotari_*_ffi-ios-* ) - # name: ${{ matrix.libffis }}-ios path: libffiss + pattern: lib${{ matrix.libffis }}-ios-* - name: Verify checksums shell: bash @@ -362,6 +363,9 @@ jobs: runs-on: ubuntu-latest needs: [matrix-prep, builds, ios_assemble] + permissions: + contents: write + steps: - name: Download all ffi libraries uses: actions/download-artifact@v4 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 166f8d432c..a91c574a54 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,9 @@ name: CI 'on': workflow_dispatch: + push: + branches: + - "ci-*" pull_request: types: - opened @@ -11,22 +14,25 @@ name: CI merge_group: env: - toolchain: nightly-2024-02-04 + toolchain: nightly-2024-07-07 CARGO_HTTP_MULTIPLEXING: false CARGO_TERM_COLOR: always CARGO_UNSTABLE_SPARSE_REGISTRY: true CARGO_INCREMENTAL: 0 PROTOC: protoc TERM: unknown + ## Must be a JSon string + TS_FEATURES: '["default","safe","grpc","ledger","libtor","metrics","miner_input"]' concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + # https://docs.github.com/en/actions/examples/using-concurrency-expressions-and-a-test-matrix + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') || github.ref != 'refs/heads/development' || github.ref != 'refs/heads/nextnet' || github.ref != 'refs/heads/stagenet' }} jobs: clippy: name: clippy - runs-on: [ubuntu-20.04] + runs-on: [ubuntu-latest] steps: - name: checkout uses: actions/checkout@v4 @@ -39,7 +45,11 @@ jobs: run: | sudo apt-get update sudo bash scripts/install_ubuntu_dependencies.sh + - name: Cache cargo files and outputs + if: startsWith(runner.environment,'github-hosted') + uses: Swatinem/rust-cache@v2 - name: caching (nightly) + if: startsWith(runner.environment,'self-hosted') # Don't use rust-cache. # Rust-cache disables a key feature of actions/cache: restoreKeys. # Without restore keys, we lose the ability to get partial matches on caches, and end @@ -64,10 +74,11 @@ jobs: run: cargo install cargo-lints - name: Clippy check (with lints) run: cargo lints clippy --all-targets --all-features + machete: # Checks for unused dependencies. name: machete - runs-on: [ubuntu-20.04] + runs-on: [ubuntu-latest] steps: - name: checkout uses: actions/checkout@v4 @@ -80,7 +91,11 @@ jobs: run: | sudo apt-get update sudo bash scripts/install_ubuntu_dependencies.sh + - name: Cache cargo files and outputs + if: startsWith(runner.environment,'github-hosted') + uses: Swatinem/rust-cache@v2 - name: caching (machete) + if: startsWith(runner.environment,'self-hosted') # Don't use rust-cache. # Rust-cache disables a key feature of actions/cache: restoreKeys. # Without restore keys, we lose the ability to get partial matches on caches, and end @@ -102,6 +117,7 @@ jobs: run: | cargo install cargo-machete cargo machete + build-stable: # Runs cargo check with stable toolchain to determine whether the codebase is likely to build # on stable Rust. @@ -110,7 +126,11 @@ jobs: steps: - name: checkout uses: actions/checkout@v4 + - name: Cache cargo files and outputs + if: startsWith(runner.environment,'github-hosted') + uses: Swatinem/rust-cache@v2 - name: caching (stable) + if: startsWith(runner.environment,'self-hosted') # Don't use rust-cache. # Rust-cache disables a key feature of actions/cache: restoreKeys. # Without restore keys, we lose the ability to get partial matches on caches, and end @@ -144,10 +164,21 @@ jobs: run: rustup show - name: cargo check run: cargo check --release --all-targets --workspace --exclude tari_integration_tests --locked + - name: cargo check individual features + shell: bash + run: | + FEATURES_ARRAY=( $(echo ${TS_FEATURES} | jq --raw-output '.[]' | awk '{ print $1 }') ) + for FEATURE_TEST in "${FEATURES_ARRAY[@]}"; do + echo "Testing for feature ${FEATURE_TEST} ..." + cargo check --release --all-targets \ + --features ${FEATURE_TEST} \ + --workspace --exclude tari_integration_tests --locked + done - name: cargo check wallet ffi separately run: cargo check --release --package minotari_wallet_ffi --locked - name: cargo check chat ffi separately run: cargo check --release --package minotari_chat_ffi --locked + licenses: name: file licenses runs-on: [ubuntu-20.04] @@ -156,11 +187,13 @@ jobs: uses: actions/checkout@v4 - name: install ripgrep run: | - wget https://github.com/BurntSushi/ripgrep/releases/download/13.0.0/ripgrep_13.0.0_amd64.deb - sudo dpkg -i ripgrep_13.0.0_amd64.deb + #wget https://github.com/BurntSushi/ripgrep/releases/download/14.1.0/ripgrep_14.1.0-1_amd64.deb.sha256 + wget https://github.com/BurntSushi/ripgrep/releases/download/14.1.0/ripgrep_14.1.0-1_amd64.deb + sudo dpkg -i ripgrep_14.1.0-1_amd64.deb rg --version || exit 1 - name: run the license check run: ./scripts/file_license_check.sh + test: name: test runs-on: [self-hosted, ubuntu-high-cpu] @@ -189,7 +222,11 @@ jobs: run: | sudo apt-get update sudo bash scripts/install_ubuntu_dependencies.sh + - name: Cache cargo files and outputs + if: startsWith(runner.environment,'github-hosted') + uses: Swatinem/rust-cache@v2 - name: caching (nightly) + if: startsWith(runner.environment,'self-hosted') # Don't use rust-cache. # Rust-cache disables a key feature of actions/cache: restoreKeys. # Without restore keys, we lose the ability to get partial matches on caches, and end @@ -224,7 +261,7 @@ jobs: # Allows other workflows to know the PR number artifacts: name: pr_2_artifact - runs-on: [ubuntu-20.04] + runs-on: [ubuntu-latest] steps: - name: Save the PR number in an artifact shell: bash @@ -241,7 +278,7 @@ jobs: # needed for test results event_file: name: "Upload Event File for Test Results" - runs-on: ubuntu-latest + runs-on: [ubuntu-latest] steps: - name: Upload uses: actions/upload-artifact@v4 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 39ad2e4b91..f6761ed720 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,7 +11,7 @@ name: Source Coverage - ci-coverage-* env: - toolchain: nightly-2024-02-04 + toolchain: nightly-2024-07-07 concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 57ff669092..25257ff3e5 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -27,7 +27,7 @@ name: Integration tests type: string env: - toolchain: nightly-2024-02-04 + toolchain: nightly-2024-07-07 concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/Cargo.lock b/Cargo.lock index 7b56eefa00..0849e34b40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -197,7 +197,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -208,7 +208,7 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -496,7 +496,7 @@ dependencies = [ "proc-macro-crate 2.0.0", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", "syn_derive", ] @@ -506,6 +506,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.7.0" @@ -587,6 +596,16 @@ dependencies = [ "cipher 0.4.4", ] +[[package]] +name = "cargo_toml" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad639525b1c67b6a298f378417b060fbc04618bea559482a8484381cce27d965" +dependencies = [ + "serde", + "toml 0.8.15", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -633,6 +652,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -841,7 +861,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -898,7 +918,7 @@ dependencies = [ "rust-ini", "serde", "serde_json", - "toml 0.8.8", + "toml 0.8.15", "yaml-rust", ] @@ -1276,7 +1296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -1354,7 +1374,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.38", + "syn 2.0.72", "synthez", ] @@ -1374,17 +1394,19 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.1.1" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", - "fiat-crypto 0.2.2", - "platforms", + "fiat-crypto", + "group", + "rand_core", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -1397,7 +1419,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -1597,7 +1619,7 @@ dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -1617,7 +1639,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -1641,6 +1663,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "1.0.2" @@ -1661,6 +1692,18 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1910,12 +1953,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - [[package]] name = "fiat-crypto" version = "0.2.2" @@ -2080,7 +2117,7 @@ checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -2192,7 +2229,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.38", + "syn 2.0.72", "textwrap 0.16.0", "thiserror", "typed-builder", @@ -2204,6 +2241,19 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "git2" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf97ba92db08df386e10c8ede66a2a0369bd277090afd8710e19e38de9ec0cd" +dependencies = [ + "bitflags 2.4.1", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "globset" version = "0.4.14" @@ -2709,15 +2759,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "itertools" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f2be4da1690a039e9ae5fd575f706a63ad5a2120f161b1d653c9da3930dd21" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.10.5" @@ -2742,6 +2783,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.65" @@ -2815,7 +2865,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -2878,6 +2928,18 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "libgit2-sys" +version = "0.16.2+1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + [[package]] name = "liblmdb-sys" version = "0.2.3" @@ -3171,7 +3233,7 @@ dependencies = [ [[package]] name = "minotari_app_grpc" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "argon2", "base64 0.13.1", @@ -3199,7 +3261,7 @@ dependencies = [ [[package]] name = "minotari_app_utilities" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "clap 3.2.25", "dialoguer", @@ -3221,7 +3283,7 @@ dependencies = [ [[package]] name = "minotari_chat_ffi" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "cbindgen", "chrono", @@ -3246,7 +3308,7 @@ dependencies = [ [[package]] name = "minotari_console_wallet" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "blake2", "chrono", @@ -3255,12 +3317,14 @@ dependencies = [ "console-subscriber", "crossterm 0.25.0", "digest 0.10.7", + "dirs", "futures 0.3.29", "ledger-transport-hid", "log", "log4rs", "minotari_app_grpc", "minotari_app_utilities", + "minotari_ledger_wallet_comms", "minotari_wallet", "qrcode", "rand", @@ -3272,7 +3336,6 @@ dependencies = [ "serde_json", "sha2 0.10.8", "strum", - "strum_macros", "tari_common", "tari_common_types", "tari_comms", @@ -3285,6 +3348,7 @@ dependencies = [ "tari_key_manager", "tari_libtor", "tari_p2p", + "tari_script", "tari_shutdown", "tari_utilities", "thiserror", @@ -3298,9 +3362,23 @@ dependencies = [ "zxcvbn", ] +[[package]] +name = "minotari_ledger_wallet_comms" +version = "1.0.0-pre.16" +dependencies = [ + "ledger-transport 0.10.0 (git+https://github.com/Zondax/ledger-rs?rev=20e2a20)", + "ledger-transport-hid", + "num-derive", + "num-traits", + "serde", + "tari_common_types", + "tari_crypto", + "thiserror", +] + [[package]] name = "minotari_merge_mining_proxy" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "bincode", @@ -3340,7 +3418,7 @@ dependencies = [ [[package]] name = "minotari_miner" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "base64 0.13.1", "borsh", @@ -3376,7 +3454,7 @@ dependencies = [ [[package]] name = "minotari_mining_helper_ffi" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "borsh", "cbindgen", @@ -3396,7 +3474,7 @@ dependencies = [ [[package]] name = "minotari_node" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "async-trait", @@ -3444,7 +3522,7 @@ dependencies = [ [[package]] name = "minotari_node_grpc_client" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "minotari_app_grpc", "tokio", @@ -3452,7 +3530,7 @@ dependencies = [ [[package]] name = "minotari_wallet" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "argon2", "async-trait", @@ -3502,12 +3580,13 @@ dependencies = [ [[package]] name = "minotari_wallet_ffi" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "borsh", "cbindgen", "chacha20poly1305", "chrono", + "env_logger 0.7.1", "futures 0.3.29", "itertools 0.10.5", "libc", @@ -3542,7 +3621,7 @@ dependencies = [ [[package]] name = "minotari_wallet_grpc_client" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "minotari_app_grpc", "tari_common_types", @@ -3587,9 +3666,9 @@ dependencies = [ [[package]] name = "monero" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b205707fd34b01a547f2fe77e687b40fed05966fb82e955b86ac55cd8ee31b5" +checksum = "f25218523ad4a171ddda05251669afb788cdc2f0df94082aab856a2b09541c3f" dependencies = [ "base58-monero 2.0.0", "curve25519-dalek", @@ -3609,7 +3688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" dependencies = [ "arrayref", - "bs58", + "bs58 0.4.0", "byteorder", "data-encoding", "multihash", @@ -3775,25 +3854,20 @@ dependencies = [ ] [[package]] -name = "num-derive" -version = "0.3.3" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -3912,7 +3986,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -3943,6 +4017,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-float" version = "2.10.1" @@ -4172,7 +4252,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -4240,7 +4320,7 @@ dependencies = [ "md-5", "nom", "num-bigint-dig", - "num-derive 0.4.1", + "num-derive", "num-traits", "p256", "p384", @@ -4317,7 +4397,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -4375,7 +4455,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -4417,12 +4497,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "platforms" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" - [[package]] name = "plotters" version = "0.3.5" @@ -4574,9 +4648,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -4700,9 +4774,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -5226,7 +5300,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -5293,9 +5367,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.190" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -5312,13 +5386,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -5340,14 +5414,14 @@ checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -5522,7 +5596,7 @@ checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -5746,9 +5820,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -5764,7 +5838,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -5791,7 +5865,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d" dependencies = [ - "syn 2.0.38", + "syn 2.0.72", "synthez-codegen", "synthez-core", ] @@ -5802,7 +5876,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746" dependencies = [ - "syn 2.0.38", + "syn 2.0.72", "synthez-core", ] @@ -5815,7 +5889,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -5845,24 +5919,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" -[[package]] -name = "tari-curve25519-dalek" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b8e2644aae57a832e475ebc31199ab1114ebd7fe4d2621e67e89bdd9c8ac38" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "fiat-crypto 0.1.20", - "platforms", - "rand_core", - "rustc_version", - "serde", - "subtle", - "zeroize", -] - [[package]] name = "tari-tiny-keccak" version = "2.0.2" @@ -5875,30 +5931,28 @@ dependencies = [ [[package]] name = "tari_bulletproofs_plus" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9abc9cbebffb1149112a80d6955498ed891f9b786e89c50e07de4b4f16290fd8" +checksum = "8eac57729e3b003c18e822c64c8c4977770102b2eaea920a7c40bca5caf12c54" dependencies = [ "blake2", "byteorder", - "derivative", - "derive_more", + "curve25519-dalek", "digest 0.10.7", - "itertools 0.6.5", - "lazy_static", + "ff", + "itertools 0.12.1", "merlin", - "rand", + "once_cell", "rand_core", "serde", "sha3", - "tari-curve25519-dalek", - "thiserror", + "thiserror-no-std", "zeroize", ] [[package]] name = "tari_chat_client" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "async-trait", @@ -5924,12 +5978,14 @@ dependencies = [ [[package]] name = "tari_common" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "blake2", + "cargo_toml", "config", "dirs-next 1.0.2", + "git2", "log", "log4rs", "multiaddr", @@ -5950,7 +6006,7 @@ dependencies = [ [[package]] name = "tari_common_sqlite" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "diesel", "diesel_migrations", @@ -5964,11 +6020,13 @@ dependencies = [ [[package]] name = "tari_common_types" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "base64 0.21.5", + "bitflags 2.4.1", "blake2", "borsh", + "bs58 0.5.1", "chacha20poly1305", "digest 0.10.7", "newtype-ops", @@ -5986,7 +6044,7 @@ dependencies = [ [[package]] name = "tari_comms" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "async-trait", @@ -6035,7 +6093,7 @@ dependencies = [ [[package]] name = "tari_comms_dht" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "bitflags 2.4.1", @@ -6079,7 +6137,7 @@ dependencies = [ [[package]] name = "tari_comms_rpc_macros" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "futures 0.3.29", "proc-macro2", @@ -6094,14 +6152,14 @@ dependencies = [ [[package]] name = "tari_contacts" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "chrono", "diesel", "diesel_migrations", "futures 0.3.29", "log", - "num-derive 0.3.3", + "num-derive", "num-traits", "prost", "rand", @@ -6127,7 +6185,7 @@ dependencies = [ [[package]] name = "tari_core" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "async-trait", "bincode", @@ -6147,15 +6205,14 @@ dependencies = [ "futures 0.3.29", "hex", "integer-encoding", - "ledger-transport 0.10.0 (git+https://github.com/Zondax/ledger-rs?rev=20e2a20)", - "ledger-transport-hid", "libsqlite3-sys", "lmdb-zero", "log", "log-mdc", + "minotari_ledger_wallet_comms", "monero", "newtype-ops", - "num-derive 0.3.3", + "num-derive", "num-format", "num-traits", "once_cell", @@ -6171,7 +6228,6 @@ dependencies = [ "sha3", "strum", "strum_macros", - "tari-curve25519-dalek", "tari-tiny-keccak", "tari_common", "tari_common_sqlite", @@ -6202,21 +6258,23 @@ dependencies = [ [[package]] name = "tari_crypto" -version = "0.20.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63a3ed2c551101eb42b7f9386c207e28d53e6816f7b4c9a0883548922f317b3e" +checksum = "22b761f7cf1754eb2223286c9d437c81737526f393f09f69b2456bf49ba25a5b" dependencies = [ "blake2", "borsh", + "curve25519-dalek", "digest 0.10.7", "log", + "merlin", "once_cell", "rand_chacha", "rand_core", "serde", "sha3", "snafu", - "tari-curve25519-dalek", + "subtle", "tari_bulletproofs_plus", "tari_utilities", "zeroize", @@ -6224,11 +6282,11 @@ dependencies = [ [[package]] name = "tari_features" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" [[package]] name = "tari_hashing" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "blake2", "borsh", @@ -6238,7 +6296,7 @@ dependencies = [ [[package]] name = "tari_integration_tests" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "async-trait", @@ -6286,7 +6344,7 @@ dependencies = [ [[package]] name = "tari_key_manager" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "argon2", "async-trait", @@ -6321,7 +6379,7 @@ dependencies = [ [[package]] name = "tari_libtor" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "derivative", "libtor", @@ -6336,7 +6394,7 @@ dependencies = [ [[package]] name = "tari_metrics" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "futures 0.3.29", @@ -6351,7 +6409,7 @@ dependencies = [ [[package]] name = "tari_mmr" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "bincode", "blake2", @@ -6370,7 +6428,7 @@ dependencies = [ [[package]] name = "tari_p2p" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "clap 3.2.25", @@ -6406,7 +6464,7 @@ dependencies = [ [[package]] name = "tari_script" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "blake2", "borsh", @@ -6423,7 +6481,7 @@ dependencies = [ [[package]] name = "tari_service_framework" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "anyhow", "async-trait", @@ -6440,7 +6498,7 @@ dependencies = [ [[package]] name = "tari_shutdown" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "futures 0.3.29", "tokio", @@ -6448,7 +6506,7 @@ dependencies = [ [[package]] name = "tari_storage" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "bincode", "lmdb-zero", @@ -6461,7 +6519,7 @@ dependencies = [ [[package]] name = "tari_test_utils" -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" dependencies = [ "futures 0.3.29", "futures-test", @@ -6571,7 +6629,27 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", +] + +[[package]] +name = "thiserror-impl-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e6318948b519ba6dc2b442a6d0b904ebfb8d411a3ad3e07843615a72249758" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "thiserror-no-std" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ad459d94dd517257cc96add8a43190ee620011bb6e6cdc82dafd97dfafafea" +dependencies = [ + "thiserror-impl-no-std", ] [[package]] @@ -6596,12 +6674,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -6616,10 +6695,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -6694,7 +6774,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -6782,21 +6862,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.21.0", + "toml_edit 0.22.16", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -6811,7 +6891,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.18", ] [[package]] @@ -6822,20 +6902,20 @@ checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ "indexmap 2.1.0", "toml_datetime", - "winnow", + "winnow 0.5.18", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" dependencies = [ "indexmap 2.1.0", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.14", ] [[package]] @@ -6980,7 +7060,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -7116,7 +7196,7 @@ checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -7394,7 +7474,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", "wasm-bindgen-shared", ] @@ -7428,7 +7508,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7718,6 +7798,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -7760,14 +7849,16 @@ dependencies = [ [[package]] name = "yamux" -version = "0.10.2" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +checksum = "5f97202f6b125031b95d83e01dc57292b529384f80bfae4677e4bbc10178cf72" dependencies = [ "futures 0.3.29", + "instant", "log", "nohash-hasher", "parking_lot 0.12.1", + "pin-project 1.1.3", "rand", "static_assertions", ] @@ -7798,7 +7889,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] @@ -7818,7 +7909,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.72", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1763844b08..1677a6caf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace.package] -version = "1.0.0-dan.11" +version = "1.0.0-dan.16" edition = "2021" +authors = ["The Tari Development Community"] [workspace] resolver = "2" @@ -35,6 +36,7 @@ members = [ "applications/minotari_app_utilities", "applications/minotari_merge_mining_proxy", "applications/minotari_miner", + "applications/minotari_ledger_wallet/comms", "integration_tests", "hashing", ] @@ -74,8 +76,7 @@ tari_chat_client = { path = "base_layer/contacts/src/chat_client" } minotari_node = { path = "applications/minotari_node" } minotari_wallet_ffi = { path = "base_layer/wallet_ffi" } minotari_wallet_grpc_client = { path = "clients/rust/wallet_grpc_client" } - - +minotari_ledger_wallet_comms = { path = "applications/minotari_ledger_wallet/comms" } [profile.release] # Shutdown when panicking so we can see the error, specifically for the wallet diff --git a/Cross.toml b/Cross.toml index 3b82aed0c3..ab4927164e 100644 --- a/Cross.toml +++ b/Cross.toml @@ -6,6 +6,9 @@ passthrough = [ "BUILD_TARGET", "CARGO_BUILD_TARGET", "TARGET_CFLAGS", + "CC_aarch64_unknown_linux_gnu", + "PKG_CONFIG_SYSROOT_DIR", + "PKG_CONFIG_ALLOW_CROSS", "RUSTFLAGS", "RUST_BACKTRACE", "RUST_DEBUG", @@ -18,8 +21,6 @@ passthrough = [ "TARI_NETWORK_DIR", ] -# Don't forget export: -# CFLAGS=-DMDB_USE_ROBUST=0 [target.x86_64-linux-android] image = "ghcr.io/cross-rs/x86_64-linux-android:edge" pre-build = [ """ @@ -27,11 +28,16 @@ export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ apt-get --assume-yes --no-install-recommends install \ curl unzip && \ -curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-x86_64.zip && \ -unzip -o protoc-25.2-linux-x86_64.zip -d /usr/ && \ +curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-linux-x86_64.zip && \ +unzip -o protoc-26.1-linux-x86_64.zip -d /usr/ && \ /usr/bin/protoc --version """ ] +[target.x86_64-linux-android.env] +passthrough = [ + "CFLAGS=-DMDB_USE_ROBUST=0", +] + [target.aarch64-linux-android] image = "ghcr.io/cross-rs/aarch64-linux-android:edge" pre-build = [ """ @@ -39,45 +45,30 @@ export DEBIAN_FRONTEND=noninteractive && \ apt-get update && \ apt-get --assume-yes --no-install-recommends install \ curl unzip && \ -curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v25.2/protoc-25.2-linux-x86_64.zip && \ -unzip -o protoc-25.2-linux-x86_64.zip -d /usr/ && \ +curl -LO https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-linux-x86_64.zip && \ +unzip -o protoc-26.1-linux-x86_64.zip -d /usr/ && \ /usr/bin/protoc --version """ ] +[target.aarch64-linux-android.env] +passthrough = [ + "CFLAGS=-DMDB_USE_ROBUST=0", +] + +# Currently needs cross-rs from git ```cargo install cross --git https://github.com/cross-rs/cross``` [target.aarch64-unknown-linux-gnu] -image = "ubuntu:18.04" -# Mergered all scripts/install_ubuntu_dependencies*.sh scripts -pre-build = [ """ -apt-get update && \ -apt-get -y install \ - openssl \ - libssl-dev \ - pkg-config \ - libsqlite3-dev \ - clang-10 \ - git \ - cmake \ - dh-autoreconf \ - libc++-dev \ - libc++abi-dev \ - libprotobuf-dev \ - protobuf-compiler \ - libncurses5-dev \ - libncursesw5-dev \ - zip \ - curl \ - pkg-config-aarch64-linux-gnu \ - gcc-aarch64-linux-gnu \ - g++-aarch64-linux-gnu && \ -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \ -export PATH="$HOME/.cargo/bin:$PATH" && \ -rustup target add aarch64-unknown-linux-gnu && \ -rustup toolchain install stable-aarch64-unknown-linux-gnu -""" ] +image.name = "ubuntu:18.04" +# targetting is needed for apple silicon +image.toolchain = ["linux/arm64=aarch64-unknown-linux-gnu", "linux/amd64=x86_64-unknown-linux-gnu"] +pre-build = "./scripts/cross_compile_ubuntu_18-pre-build.sh" [target.aarch64-unknown-linux-gnu.env] passthrough = [ - "CARGO_BUILD_TARGET", - "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc", - "BINDGEN_EXTRA_CLANG_ARGS=--sysroot /usr/aarch64-linux-gnu/include/", + "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc", + "PKG_CONFIG_SYSROOT_DIR=/usr/lib/aarch64-linux-gnu/", + "PKG_CONFIG_ALLOW_CROSS=true", ] + +[target.x86_64-unknown-linux-gnu] +image = "ubuntu:18.04" +pre-build = "./scripts/cross_compile_ubuntu_18-pre-build.sh" diff --git a/README.md b/README.md index 7dbaa35891..dee541f4de 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ The recommended running versions of each network are: | Network | Version | |-----------|----------------| | Stagenet | 1.0.0-alpha.0a | -| Nextnet | 1.0.0-rc.6a | -| Esmeralda | 1.0.0-pre.11a | +| Nextnet | 1.0.0-rc.8 | +| Esmeralda | 1.0.0-pre.16 | For more detail about versioning, see [Release Ideology](https://github.com/tari-project/tari/blob/development/docs/src/branching_releases.md). diff --git a/applications/minotari_app_grpc/build.rs b/applications/minotari_app_grpc/build.rs index f8084fb744..ab84ce9d8b 100644 --- a/applications/minotari_app_grpc/build.rs +++ b/applications/minotari_app_grpc/build.rs @@ -10,6 +10,7 @@ fn main() -> Result<(), Box> { "proto/base_node.proto", "proto/wallet.proto", "proto/validator_node.proto", + "proto/p2pool.proto", ], &["proto"], )?; diff --git a/applications/minotari_app_grpc/proto/network.proto b/applications/minotari_app_grpc/proto/network.proto index 62f6c57cf8..fba0614749 100644 --- a/applications/minotari_app_grpc/proto/network.proto +++ b/applications/minotari_app_grpc/proto/network.proto @@ -70,7 +70,11 @@ message Address{ bytes address =1; string last_seen = 2; uint32 connection_attempts = 3; - uint64 avg_latency = 5; + AverageLatency avg_latency = 5; +} + +message AverageLatency { + uint64 latency = 1; } message ListConnectedPeersResponse { diff --git a/base_layer/wallet/src/types.rs b/applications/minotari_app_grpc/proto/p2pool.proto similarity index 74% rename from base_layer/wallet/src/types.rs rename to applications/minotari_app_grpc/proto/p2pool.proto index 97ad8c685e..5194b504d9 100644 --- a/base_layer/wallet/src/types.rs +++ b/applications/minotari_app_grpc/proto/p2pool.proto @@ -1,4 +1,4 @@ -// Copyright 2019. The Tari Project +// Copyright 2024. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: @@ -19,11 +19,26 @@ // 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. +syntax = "proto3"; -use tari_common_types::types::PublicKey; +package tari.rpc; -use crate::error::WalletError; +import "base_node.proto"; +import "block.proto"; -pub(crate) trait PersistentKeyManager { - fn create_and_store_new(&mut self) -> Result; +service ShaP2Pool { + rpc GetNewBlock(GetNewBlockRequest) returns(GetNewBlockResponse); + rpc SubmitBlock(SubmitBlockRequest) returns(tari.rpc.SubmitBlockResponse); } + +message GetNewBlockRequest {} + +message GetNewBlockResponse { + tari.rpc.GetNewBlockResult block = 1; + uint64 target_difficulty = 2; +} + +message SubmitBlockRequest { + tari.rpc.Block block = 1; + string wallet_payment_address = 2; +} \ No newline at end of file diff --git a/applications/minotari_app_grpc/proto/transaction.proto b/applications/minotari_app_grpc/proto/transaction.proto index 8133e70525..efef3ee014 100644 --- a/applications/minotari_app_grpc/proto/transaction.proto +++ b/applications/minotari_app_grpc/proto/transaction.proto @@ -178,5 +178,7 @@ message UnblindedOutput { bytes encrypted_data = 12; // The minimum value of the commitment that is proven by the range proof (in MicroMinotari) uint64 minimum_value_promise = 13; + // The range proof + RangeProof range_proof = 14; } diff --git a/applications/minotari_app_grpc/proto/wallet.proto b/applications/minotari_app_grpc/proto/wallet.proto index ae049f99dc..4f07e620b3 100644 --- a/applications/minotari_app_grpc/proto/wallet.proto +++ b/applications/minotari_app_grpc/proto/wallet.proto @@ -121,6 +121,7 @@ message PaymentRecipient { ONE_SIDED_TO_STEALTH_ADDRESS = 2; } PaymentType payment_type = 5; + bytes payment_id = 6; } message TransferResponse { diff --git a/applications/minotari_app_grpc/src/authentication/basic_auth.rs b/applications/minotari_app_grpc/src/authentication/basic_auth.rs index 20d23eb7fa..d1d148e21b 100644 --- a/applications/minotari_app_grpc/src/authentication/basic_auth.rs +++ b/applications/minotari_app_grpc/src/authentication/basic_auth.rs @@ -227,13 +227,9 @@ mod tests { } mod validate { - use std::{ - cmp::{max, min}, - thread::sleep, - }; + use std::{cmp::max, thread::sleep}; - use rand::RngCore; - use tari_utilities::{hex::Hex, ByteArray}; + use tari_utilities::hex::Hex; use super::*; use crate::authentication::salted_password::create_salted_hashed_password; diff --git a/applications/minotari_app_grpc/src/conversions/peer.rs b/applications/minotari_app_grpc/src/conversions/peer.rs index 5329e32e0f..8523ec6575 100644 --- a/applications/minotari_app_grpc/src/conversions/peer.rs +++ b/applications/minotari_app_grpc/src/conversions/peer.rs @@ -72,7 +72,10 @@ impl From for grpc::Address { None => String::new(), }; let connection_attempts = address_with_stats.connection_attempts(); - let avg_latency = address_with_stats.avg_latency().as_secs(); + let avg_latency = address_with_stats + .avg_latency() + .map(|val| grpc::AverageLatency { latency: val.as_secs() }); + Self { address, last_seen, diff --git a/applications/minotari_app_grpc/src/conversions/transaction_output.rs b/applications/minotari_app_grpc/src/conversions/transaction_output.rs index 88a0d9bb46..2994f982a6 100644 --- a/applications/minotari_app_grpc/src/conversions/transaction_output.rs +++ b/applications/minotari_app_grpc/src/conversions/transaction_output.rs @@ -23,7 +23,7 @@ use std::convert::{TryFrom, TryInto}; use borsh::{BorshDeserialize, BorshSerialize}; -use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey}; +use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey, RangeProof}; use tari_core::transactions::{ tari_amount::MicroMinotari, transaction_components::{EncryptedData, TransactionOutput, TransactionOutputVersion}, @@ -31,7 +31,7 @@ use tari_core::transactions::{ use tari_script::TariScript; use tari_utilities::ByteArray; -use crate::tari_rpc as grpc; +use crate::{tari_rpc as grpc, tari_rpc::RangeProof as GrpcRangeProof}; impl TryFrom for TransactionOutput { type Error = String; @@ -113,3 +113,11 @@ impl TryFrom for grpc::TransactionOutput { }) } } + +impl From for GrpcRangeProof { + fn from(proof: RangeProof) -> Self { + Self { + proof_bytes: proof.to_vec(), + } + } +} diff --git a/applications/minotari_app_grpc/src/conversions/unblinded_output.rs b/applications/minotari_app_grpc/src/conversions/unblinded_output.rs index 76a6f415cc..0a3764cfd2 100644 --- a/applications/minotari_app_grpc/src/conversions/unblinded_output.rs +++ b/applications/minotari_app_grpc/src/conversions/unblinded_output.rs @@ -23,7 +23,7 @@ use std::convert::{TryFrom, TryInto}; use borsh::{BorshDeserialize, BorshSerialize}; -use tari_common_types::types::{PrivateKey, PublicKey}; +use tari_common_types::types::{PrivateKey, PublicKey, RangeProof}; use tari_core::transactions::{ tari_amount::MicroMinotari, transaction_components::{EncryptedData, TransactionOutputVersion, UnblindedOutput}, @@ -59,6 +59,14 @@ impl TryFrom for grpc::UnblindedOutput { covenant, encrypted_data: output.encrypted_data.to_byte_vec(), minimum_value_promise: output.minimum_value_promise.into(), + range_proof: { + let proof_bytes = if let Some(proof) = output.range_proof { + proof.to_vec() + } else { + vec![] + }; + Some(grpc::RangeProof { proof_bytes }) + }, }) } } @@ -99,6 +107,18 @@ impl TryFrom for UnblindedOutput { let minimum_value_promise = MicroMinotari::from(output.minimum_value_promise); + let range_proof = if let Some(proof) = output.range_proof { + if proof.proof_bytes.is_empty() { + None + } else { + let proof = + RangeProof::from_vec(&proof.proof_bytes).map_err(|e| format!("Range proof is invalid: {}", e))?; + Some(proof) + } + } else { + return Err("Range proof not provided".to_string()); + }; + // zeroize output sensitive data output.spending_key.zeroize(); output.script_private_key.zeroize(); @@ -117,6 +137,7 @@ impl TryFrom for UnblindedOutput { covenant, encrypted_data, minimum_value_promise, + range_proof, )) } } diff --git a/applications/minotari_app_utilities/Cargo.toml b/applications/minotari_app_utilities/Cargo.toml index 8440a2b2e7..19515f51fd 100644 --- a/applications/minotari_app_utilities/Cargo.toml +++ b/applications/minotari_app_utilities/Cargo.toml @@ -25,9 +25,8 @@ tonic = "0.8.3" [build-dependencies] -tari_common = { workspace = true, features = ["build"] } +tari_common = { workspace = true, features = ["build", "static-application-info"] } tari_features = { workspace = true } [features] miner_input = ["minotari_app_grpc"] - diff --git a/applications/minotari_app_utilities/build.rs b/applications/minotari_app_utilities/build.rs index 0774dcc55d..8df78dd736 100644 --- a/applications/minotari_app_utilities/build.rs +++ b/applications/minotari_app_utilities/build.rs @@ -20,9 +20,12 @@ // 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::build::StaticApplicationInfo; use tari_features::resolver::build_features; fn main() -> Result<(), Box> { build_features(); + let gen = StaticApplicationInfo::initialize()?; + gen.write_consts_to_outdir("consts.rs")?; Ok(()) } diff --git a/applications/minotari_app_utilities/src/identity_management.rs b/applications/minotari_app_utilities/src/identity_management.rs index b6e821a2b3..53bcc9dd7a 100644 --- a/applications/minotari_app_utilities/src/identity_management.rs +++ b/applications/minotari_app_utilities/src/identity_management.rs @@ -42,7 +42,7 @@ const REQUIRED_IDENTITY_PERMS: u32 = 0o100600; /// - `identity_file` - Reference to file path /// - `public_address` - Network address of the base node /// - `create_id` - Only applies if the identity_file does not exist or is malformed. If true, a new identity will be -/// created, otherwise the user will be prompted to create a new ID +/// created, otherwise the user will be prompted to create a new ID /// - `peer_features` - Enables features of the base node /// /// # Return diff --git a/applications/minotari_app_utilities/src/lib.rs b/applications/minotari_app_utilities/src/lib.rs index 43963816d7..0859c10104 100644 --- a/applications/minotari_app_utilities/src/lib.rs +++ b/applications/minotari_app_utilities/src/lib.rs @@ -26,6 +26,11 @@ pub mod identity_management; pub mod parse_miner_input; pub mod utilities; +pub mod consts { + // Import the auto-generated const values from the Manifest and Git + include!(concat!(env!("OUT_DIR"), "/consts.rs")); +} + /// Non-64-bit architectures are untested. Depending on the application, it may not compile already or could be various /// classes of bugs (overflows, crashes, etc). Use this macro to explicitly fail compilation on non-64-bit targets. #[macro_export] diff --git a/applications/minotari_app_utilities/src/parse_miner_input.rs b/applications/minotari_app_utilities/src/parse_miner_input.rs index bdc183a496..5e258ea803 100644 --- a/applications/minotari_app_utilities/src/parse_miner_input.rs +++ b/applications/minotari_app_utilities/src/parse_miner_input.rs @@ -25,7 +25,13 @@ use std::{net::SocketAddr, str::FromStr}; use dialoguer::Input as InputPrompt; use minotari_app_grpc::{ authentication::ClientAuthenticationInterceptor, - tari_rpc::{base_node_client::BaseNodeClient, Block, NewBlockTemplate, NewBlockTemplateRequest}, + tari_rpc::{ + base_node_client::BaseNodeClient, + sha_p2_pool_client::ShaP2PoolClient, + Block, + NewBlockTemplate, + NewBlockTemplateRequest, + }, }; use tari_common::configuration::{ bootstrap::{grpc_default_port, ApplicationType}, @@ -163,6 +169,9 @@ pub fn wait_for_keypress() { /// Base node gRPC client pub type BaseNodeGrpcClient = BaseNodeClient>; +/// SHA P2Pool gRPC client +pub type ShaP2PoolGrpcClient = ShaP2PoolClient>; + /// Verify that the base node is responding to the mining gRPC requests pub async fn verify_base_node_grpc_mining_responses( node_conn: &mut BaseNodeGrpcClient, diff --git a/applications/minotari_app_utilities/src/utilities.rs b/applications/minotari_app_utilities/src/utilities.rs index b6daff88d2..afbb03bea2 100644 --- a/applications/minotari_app_utilities/src/utilities.rs +++ b/applications/minotari_app_utilities/src/utilities.rs @@ -28,10 +28,10 @@ use tari_common::exit_codes::{ExitCode, ExitError}; use tari_common_types::{ emoji::EmojiId, tari_address::TariAddress, - types::{BlockHash, PublicKey}, + types::{BlockHash, PrivateKey, PublicKey, Signature}, }; use tari_comms::{peer_manager::NodeId, types::CommsPublicKey}; -use tari_utilities::hex::Hex; +use tari_utilities::hex::{Hex, HexError}; use thiserror::Error; use tokio::{runtime, runtime::Runtime}; @@ -47,8 +47,8 @@ pub fn setup_runtime() -> Result { /// Returns a CommsPublicKey from either a emoji id or a public key pub fn parse_emoji_id_or_public_key(key: &str) -> Option { - EmojiId::from_emoji_string(&key.trim().replace('|', "")) - .map(|emoji_id| emoji_id.to_public_key()) + EmojiId::from_str(&key.trim().replace('|', "")) + .map(|emoji_id| PublicKey::from(&emoji_id)) .or_else(|_| CommsPublicKey::from_hex(key)) .ok() } @@ -79,12 +79,10 @@ impl FromStr for UniPublicKey { type Err = UniIdError; fn from_str(key: &str) -> Result { - if let Ok(emoji_id) = EmojiId::from_emoji_string(&key.trim().replace('|', "")) { - Ok(Self(emoji_id.to_public_key())) + if let Ok(emoji_id) = EmojiId::from_str(&key.trim().replace('|', "")) { + Ok(Self(PublicKey::from(&emoji_id))) } else if let Ok(public_key) = PublicKey::from_hex(key) { Ok(Self(public_key)) - } else if let Ok(tari_address) = TariAddress::from_hex(key) { - Ok(Self(tari_address.public_key().clone())) } else { Err(UniIdError::UnknownIdType) } @@ -97,7 +95,7 @@ impl From for PublicKey { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum UniNodeId { PublicKey(PublicKey), NodeId(NodeId), @@ -116,13 +114,13 @@ impl FromStr for UniNodeId { type Err = UniIdError; fn from_str(key: &str) -> Result { - if let Ok(emoji_id) = EmojiId::from_emoji_string(&key.trim().replace('|', "")) { - Ok(Self::PublicKey(emoji_id.to_public_key())) + if let Ok(emoji_id) = EmojiId::from_str(&key.trim().replace('|', "")) { + Ok(Self::PublicKey(PublicKey::from(&emoji_id))) } else if let Ok(public_key) = PublicKey::from_hex(key) { Ok(Self::PublicKey(public_key)) } else if let Ok(node_id) = NodeId::from_hex(key) { Ok(Self::NodeId(node_id)) - } else if let Ok(tari_address) = TariAddress::from_hex(key) { + } else if let Ok(tari_address) = TariAddress::from_base58(key) { Ok(Self::TariAddress(tari_address)) } else { Err(UniIdError::UnknownIdType) @@ -136,18 +134,40 @@ impl TryFrom for PublicKey { fn try_from(id: UniNodeId) -> Result { match id { UniNodeId::PublicKey(public_key) => Ok(public_key), - UniNodeId::TariAddress(tari_address) => Ok(tari_address.public_key().clone()), + UniNodeId::TariAddress(tari_address) => Ok(tari_address.public_spend_key().clone()), UniNodeId::NodeId(_) => Err(UniIdError::Nonconvertible), } } } +#[derive(Debug, Clone)] +pub struct UniSignature(Signature); + +impl FromStr for UniSignature { + type Err = HexError; + + fn from_str(s: &str) -> Result { + let data = s.split(',').collect::>(); + let signature = PrivateKey::from_hex(data[0])?; + let public_nonce = PublicKey::from_hex(data[1])?; + + let signature = Signature::new(public_nonce, signature); + Ok(Self(signature)) + } +} + +impl From for Signature { + fn from(id: UniSignature) -> Self { + id.0 + } +} + impl From for NodeId { fn from(id: UniNodeId) -> Self { match id { UniNodeId::PublicKey(public_key) => NodeId::from_public_key(&public_key), UniNodeId::NodeId(node_id) => node_id, - UniNodeId::TariAddress(tari_address) => NodeId::from_public_key(tari_address.public_key()), + UniNodeId::TariAddress(tari_address) => NodeId::from_public_key(tari_address.public_spend_key()), } } } diff --git a/applications/minotari_console_wallet/Cargo.toml b/applications/minotari_console_wallet/Cargo.toml index 042d945090..eb56db8804 100644 --- a/applications/minotari_console_wallet/Cargo.toml +++ b/applications/minotari_console_wallet/Cargo.toml @@ -8,6 +8,7 @@ license = "BSD-3-Clause" [dependencies] minotari_app_grpc = { workspace = true } minotari_app_utilities = { workspace = true } +minotari_ledger_wallet_comms = { workspace = true, optional = true } tari_common = { workspace = true } tari_common_types = { workspace = true } tari_comms = { workspace = true } @@ -17,6 +18,7 @@ tari_contacts = { workspace = true } tari_crypto = { workspace = true } tari_key_manager = { workspace = true } tari_libtor = { workspace = true, optional = true } +tari_script = { workspace = true } tari_p2p = { workspace = true, features = ["auto-update"] } tari_shutdown = { workspace = true } tari_utilities = { workspace = true } @@ -37,12 +39,13 @@ clap = { version = "3.2", features = ["derive", "env"] } config = "0.14.0" crossterm = { version = "0.25.0" } digest = "0.10" +dirs = "5.0" futures = { version = "^0.3.16", default-features = false, features = [ "alloc", ] } ledger-transport-hid = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20", optional = true } log = { version = "0.4.8", features = ["std"] } -log4rs = { version = "1.3.0", default_features = false, features = [ +log4rs = { version = "1.3.0", default-features = false, features = [ "config_parsing", "threshold_filter", "yaml_format", @@ -63,7 +66,6 @@ serde = "1.0.136" serde_json = "1.0.79" sha2 = "0.10" strum = "0.22" -strum_macros = "0.22" thiserror = "1.0.26" tonic = "0.8.3" unicode-segmentation = "1.6.0" @@ -82,9 +84,9 @@ features = ["crossterm"] tari_features = { workspace = true } [features] -default = ["libtor"] +default = ["libtor", "ledger"] grpc = [] -ledger = ["ledger-transport-hid"] +ledger = ["ledger-transport-hid", "minotari_ledger_wallet_comms"] libtor = ["tari_libtor"] [package.metadata.cargo-machete] diff --git a/applications/minotari_console_wallet/README.md b/applications/minotari_console_wallet/README.md index 69ab53b4d3..efb2b91243 100644 --- a/applications/minotari_console_wallet/README.md +++ b/applications/minotari_console_wallet/README.md @@ -251,7 +251,7 @@ example output: 1. whois c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 Public Key: c69fbe5f05a304eaec65d5f234a6aa258a90b8bb5b9ceffea779653667ef2108 -Emoji ID : 📈👛💭🎾🌍👡🌋😻🚀🏉🔥🚓🍳👹👿🍕🐵🐼💡💦🎺👘🚌🚿👻🐛🏉🍵🏥🚌🍑🌞🍹 +Emoji ID : 📈👛➕🎾🐋🥊🎯👍🚀⚽🔥🚓🍳🤡🤠🍕🐵🐼💡💦🎺👘🚚🚿👻🐛⚽🍵🏥🚚🍑🌕🍾 ``` ## Script mode diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index c3980cb23e..413fa8359e 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::{ + collections::HashMap, convert::TryInto, fs, fs::File, @@ -31,7 +32,7 @@ use std::{ }; use chrono::{DateTime, Utc}; -use digest::Digest; +use digest::{crypto_common::rand_core::OsRng, Digest}; use futures::FutureExt; use log::*; use minotari_app_grpc::tls::certs::{generate_self_signed_certs, print_warning, write_cert_to_disk}; @@ -48,13 +49,12 @@ use minotari_wallet::{ }; use serde::{de::DeserializeOwned, Serialize}; use sha2::Sha256; -use strum_macros::{Display, EnumIter, EnumString}; use tari_common_types::{ burnt_proof::BurntProof, emoji::EmojiId, tari_address::TariAddress, transaction::TxId, - types::{Commitment, FixedHash, PrivateKey, PublicKey, Signature}, + types::{Commitment, FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, }; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityRequester}, @@ -62,12 +62,31 @@ use tari_comms::{ types::CommsPublicKey, }; use tari_comms_dht::{envelope::NodeDestination, DhtDiscoveryRequester}; -use tari_core::transactions::{ - tari_amount::{uT, MicroMinotari, Minotari}, - transaction_components::{OutputFeatures, TransactionOutput, WalletOutput}, +use tari_core::{ + covenants::Covenant, + transactions::{ + key_manager::TransactionKeyManagerInterface, + tari_amount::{uT, MicroMinotari, Minotari}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + Transaction, + TransactionInput, + TransactionInputVersion, + TransactionOutput, + TransactionOutputVersion, + UnblindedOutput, + WalletOutput, + }, + }, +}; +use tari_crypto::{ + keys::SecretKey, + ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey}, }; -use tari_crypto::ristretto::RistrettoSecretKey; -use tari_utilities::{hex::Hex, ByteArray}; +use tari_key_manager::key_manager_service::KeyManagerInterface; +use tari_script::{script, CheckSigSchnorrSignature}; +use tari_utilities::{encoding::Base58, hex::Hex, ByteArray}; use tokio::{ sync::{broadcast, mpsc}, time::{sleep, timeout}, @@ -75,39 +94,37 @@ use tokio::{ use super::error::CommandError; use crate::{ + automation::{ + utils::{ + get_file_name, + move_session_file_to_session_dir, + out_dir, + read_and_verify, + read_session_info, + read_verify_session_info, + write_json_object_to_file_as_line, + write_to_json_file, + }, + Step1SessionInfo, + Step2OutputsForLeader, + Step2OutputsForSelf, + Step3OutputsForParties, + Step3OutputsForSelf, + Step4OutputsForLeader, + }, cli::{CliCommands, MakeItRainTransactionType}, utils::db::{CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, }; pub const LOG_TARGET: &str = "wallet::automation::commands"; - -/// Enum representing commands used by the wallet -#[derive(Clone, PartialEq, Debug, Display, EnumIter, EnumString)] -#[strum(serialize_all = "kebab_case")] -pub enum WalletCommand { - GetBalance, - SendTari, - SendOneSided, - MakeItRain, - CoinSplit, - DiscoverPeer, - Whois, - ExportUtxos, - ExportTx, - ImportTx, - ExportSpentUtxos, - CountUtxos, - SetBaseNode, - SetCustomBaseNode, - ClearCustomBaseNode, - InitShaAtomicSwap, - FinaliseShaAtomicSwap, - ClaimShaAtomicSwapRefund, - RegisterAsset, - MintTokens, - CreateInitialCheckpoint, - RevalidateWalletDb, -} +// Faucet file names +pub(crate) const FILE_EXTENSION: &str = "json"; +pub(crate) const SESSION_INFO: &str = "step_1_session_info"; +pub(crate) const STEP_2_LEADER: &str = "step_2_for_leader_from_"; +pub(crate) const STEP_2_SELF: &str = "step_2_for_self"; +pub(crate) const STEP_3_SELF: &str = "step_3_for_self"; +pub(crate) const STEP_3_PARTIES: &str = "step_3_for_parties"; +pub(crate) const STEP_4_LEADER: &str = "step_4_for_leader_from_"; #[derive(Debug)] pub struct SentTransaction {} @@ -153,6 +170,60 @@ pub async fn burn_tari( .map_err(CommandError::TransactionServiceError) } +/// encumbers a n-of-m transaction +#[allow(clippy::too_many_arguments)] +#[allow(clippy::mutable_key_type)] +async fn encumber_aggregate_utxo( + mut wallet_transaction_service: TransactionServiceHandle, + fee_per_gram: MicroMinotari, + output_hash: HashOutput, + expected_commitment: PedersenCommitment, + script_input_shares: HashMap, + script_signature_public_nonces: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, +) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), CommandError> { + wallet_transaction_service + .encumber_aggregate_utxo( + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + ) + .await + .map_err(CommandError::TransactionServiceError) +} + +/// finalises an already encumbered a n-of-m transaction +async fn finalise_aggregate_utxo( + mut wallet_transaction_service: TransactionServiceHandle, + tx_id: u64, + meta_signatures: Vec, + script_signatures: Vec, + wallet_script_secret_key: PrivateKey, +) -> Result { + let mut meta_sig = Signature::default(); + for sig in &meta_signatures { + meta_sig = &meta_sig + sig; + } + let mut script_sig = Signature::default(); + for sig in &script_signatures { + script_sig = &script_sig + sig; + } + + wallet_transaction_service + .finalize_aggregate_utxo(tx_id, meta_sig, script_sig, wallet_script_secret_key) + .await + .map_err(CommandError::TransactionServiceError) +} + /// publishes a tari-SHA atomic swap HTLC transaction pub async fn init_sha_atomic_swap( mut wallet_transaction_service: TransactionServiceHandle, @@ -230,28 +301,6 @@ pub async fn register_validator_node( .map_err(CommandError::TransactionServiceError) } -/// Send a one-sided transaction to a recipient -pub async fn send_one_sided( - mut wallet_transaction_service: TransactionServiceHandle, - fee_per_gram: u64, - amount: MicroMinotari, - selection_criteria: UtxoSelectionCriteria, - dest_address: TariAddress, - message: String, -) -> Result { - wallet_transaction_service - .send_one_sided_transaction( - dest_address, - amount, - selection_criteria, - OutputFeatures::default(), - fee_per_gram * uT, - message, - ) - .await - .map_err(CommandError::TransactionServiceError) -} - pub async fn send_one_sided_to_stealth_address( mut wallet_transaction_service: TransactionServiceHandle, fee_per_gram: u64, @@ -259,6 +308,7 @@ pub async fn send_one_sided_to_stealth_address( selection_criteria: UtxoSelectionCriteria, dest_address: TariAddress, message: String, + payment_id: PaymentId, ) -> Result { wallet_transaction_service .send_one_sided_to_stealth_address_transaction( @@ -268,6 +318,7 @@ pub async fn send_one_sided_to_stealth_address( OutputFeatures::default(), fee_per_gram * uT, message, + payment_id, ) .await .map_err(CommandError::TransactionServiceError) @@ -372,7 +423,7 @@ pub async fn make_it_rain( // - If a slower rate is requested as what is achievable, transactions will be delayed to match the rate. // - If a faster rate is requested as what is achievable, the maximum rate will be that of the integrated system. // - The default value of 25/s may not be achievable. - let transactions_per_second = transactions_per_second.abs().max(0.01).min(250.0); + let transactions_per_second = transactions_per_second.abs().clamp(0.01, 250.0); // We are spawning this command in parallel, thus not collecting transaction IDs tokio::task::spawn(async move { // Wait until specified test start time @@ -450,17 +501,6 @@ pub async fn make_it_rain( MakeItRainTransactionType::Interactive => { send_tari(tx_service, fee, amount, address.clone(), msg.clone()).await }, - MakeItRainTransactionType::OneSided => { - send_one_sided( - tx_service, - fee, - amount, - UtxoSelectionCriteria::default(), - address.clone(), - msg.clone(), - ) - .await - }, MakeItRainTransactionType::StealthOneSided => { send_one_sided_to_stealth_address( tx_service, @@ -469,6 +509,7 @@ pub async fn make_it_rain( UtxoSelectionCriteria::default(), address.clone(), msg.clone(), + PaymentId::Empty, ) .await }, @@ -647,6 +688,7 @@ pub async fn command_runner( let mut output_service = wallet.output_manager_service.clone(); let dht_service = wallet.dht_service.discovery_service_requester().clone(); let connectivity_requester = wallet.comms.connectivity(); + let key_manager_service = wallet.key_manager_service.clone(); let mut online = false; let mut tx_ids = Vec::new(); @@ -707,39 +749,364 @@ pub async fn command_runner( Err(e) => eprintln!("BurnMinotari error! {}", e), } }, - SendMinotari(args) => { - match send_tari( + FaucetGenerateSessionInfo(args) => { + let commitment = if let Ok(val) = Commitment::from_hex(&args.commitment) { + val + } else { + eprintln!("\nError: Invalid 'commitment' provided!\n"); + continue; + }; + let hash = if let Ok(val) = FixedHash::from_hex(&args.output_hash) { + val + } else { + eprintln!("\nError: Invalid 'output_hash' provided!\n"); + continue; + }; + + if args.verify_unspent_outputs { + let unspent_outputs = transaction_service.fetch_unspent_outputs(vec![hash]).await?; + if unspent_outputs.is_empty() { + eprintln!( + "\nError: Output with output_hash '{}' has already been spent!\n", + args.output_hash + ); + continue; + } + if unspent_outputs[0].commitment() != &commitment { + eprintln!( + "\nError: Mismatched commitment '{}' and output_hash '{}'; not for the same output!\n", + args.commitment, args.output_hash + ); + continue; + } + } + + let mut session_id = PrivateKey::random(&mut OsRng).to_base58(); + session_id.truncate(16); + let session_info = Step1SessionInfo { + session_id: session_id.clone(), + commitment_to_spend: args.commitment, + output_hash: args.output_hash, + recipient_address: args.recipient_address, + fee_per_gram: args.fee_per_gram, + }; + let out_dir = out_dir(&session_info.session_id)?; + let out_file = out_dir.join(get_file_name(SESSION_INFO, None)); + write_to_json_file(&out_file, true, session_info)?; + println!(); + println!("Concluded step 1 'faucet-generate-session-info'"); + println!("Your session ID is: '{}'", session_id); + println!("Your session's output directory is: '{}'", out_dir.display()); + println!("Session info saved to: '{}'", out_file.display()); + println!("Send '{}' to parties for step 2", get_file_name(SESSION_INFO, None)); + println!(); + }, + FaucetCreatePartyDetails(args) => { + if args.alias.is_empty() || args.alias.contains(" ") { + eprintln!("\nError: Alias cannot contain spaces!\n"); + continue; + } + if args.alias.chars().any(|c| !c.is_alphanumeric() && c != '_') { + eprintln!("\nError: Alias contains invalid characters! Only alphanumeric and '_' are allowed.\n"); + continue; + } + + let wallet_spend_key = wallet.key_manager_service.get_spend_key().await?; + let script_nonce_key = key_manager_service.get_random_key().await?; + let sender_offset_key = key_manager_service.get_random_key().await?; + let sender_offset_nonce = key_manager_service.get_random_key().await?; + + // Read session info + let session_info = read_session_info(args.input_file.clone())?; + + let commitment = Commitment::from_hex(&session_info.commitment_to_spend)?; + let shared_secret = key_manager_service + .get_diffie_hellman_shared_secret( + &sender_offset_key.key_id, + session_info + .recipient_address + .public_view_key() + .ok_or(CommandError::InvalidArgument("Missing public view key".to_string()))?, + ) + .await?; + let shared_secret_public_key = PublicKey::from_canonical_bytes(shared_secret.as_bytes())?; + + let script_input_signature = key_manager_service + .sign_script_message(&wallet_spend_key.key_id, commitment.as_bytes()) + .await?; + + let out_dir = out_dir(&session_info.session_id)?; + let step_2_outputs_for_leader = Step2OutputsForLeader { + script_input_signature, + wallet_public_spend_key: wallet_spend_key.pub_key, + public_script_nonce_key: script_nonce_key.pub_key, + public_sender_offset_key: sender_offset_key.pub_key, + public_sender_offset_nonce_key: sender_offset_nonce.pub_key, + dh_shared_secret_public_key: shared_secret_public_key, + }; + let out_file_leader = out_dir.join(get_file_name(STEP_2_LEADER, Some(args.alias.clone()))); + write_json_object_to_file_as_line(&out_file_leader, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file_leader, false, step_2_outputs_for_leader)?; + + let step_2_outputs_for_self = Step2OutputsForSelf { + alias: args.alias.clone(), + wallet_spend_key_id: wallet_spend_key.key_id, + script_nonce_key_id: script_nonce_key.key_id, + sender_offset_key_id: sender_offset_key.key_id, + sender_offset_nonce_key_id: sender_offset_nonce.key_id, + }; + let out_file_self = out_dir.join(get_file_name(STEP_2_SELF, None)); + write_json_object_to_file_as_line(&out_file_self, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file_self, false, step_2_outputs_for_self)?; + + println!(); + println!("Concluded step 2 'faucet-create-party-details'"); + println!("Your session's output directory is '{}'", out_dir.display()); + move_session_file_to_session_dir(&session_info.session_id, &args.input_file)?; + println!( + "Send '{}' to leader for step 3", + get_file_name(STEP_2_LEADER, Some(args.alias)) + ); + println!(); + }, + FaucetEncumberAggregateUtxo(args) => { + // Read session info + let session_info = read_verify_session_info(&args.session_id)?; + + #[allow(clippy::mutable_key_type)] + let mut input_shares = HashMap::new(); + let mut script_signature_public_nonces = Vec::with_capacity(args.input_file_names.len()); + let mut sender_offset_public_key_shares = Vec::with_capacity(args.input_file_names.len()); + let mut metadata_ephemeral_public_key_shares = Vec::with_capacity(args.input_file_names.len()); + let mut dh_shared_secret_shares = Vec::with_capacity(args.input_file_names.len()); + for file_name in args.input_file_names { + // Read party input + let party_info = + read_and_verify::(&args.session_id, &file_name, &session_info)?; + input_shares.insert(party_info.wallet_public_spend_key, party_info.script_input_signature); + script_signature_public_nonces.push(party_info.public_script_nonce_key); + sender_offset_public_key_shares.push(party_info.public_sender_offset_key); + metadata_ephemeral_public_key_shares.push(party_info.public_sender_offset_nonce_key); + dh_shared_secret_shares.push(party_info.dh_shared_secret_public_key); + } + + match encumber_aggregate_utxo( transaction_service.clone(), - config.fee_per_gram, - args.amount, - args.destination, - args.message, + session_info.fee_per_gram, + FixedHash::from_hex(&session_info.output_hash) + .map_err(|e| CommandError::InvalidArgument(e.to_string()))?, + Commitment::from_hex(&session_info.commitment_to_spend)?, + input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + session_info.recipient_address.clone(), ) .await { - Ok(tx_id) => { - debug!(target: LOG_TARGET, "send-minotari concluded with tx_id {}", tx_id); - tx_ids.push(tx_id); + Ok(( + tx_id, + transaction, + script_pubkey, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) => { + let out_dir = out_dir(&args.session_id)?; + let step_3_outputs_for_self = Step3OutputsForSelf { tx_id }; + let out_file = out_dir.join(get_file_name(STEP_3_SELF, None)); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, step_3_outputs_for_self)?; + + let step_3_outputs_for_parties = Step3OutputsForParties { + input_stack: transaction.body.inputs()[0].clone().input_data, + input_script: transaction.body.inputs()[0].script().unwrap().clone(), + total_script_key: script_pubkey, + script_signature_ephemeral_commitment: transaction.body.inputs()[0] + .script_signature + .ephemeral_commitment() + .clone(), + script_signature_ephemeral_pubkey: total_script_nonce, + output_commitment: transaction.body.outputs()[0].commitment().clone(), + sender_offset_pubkey: transaction.body.outputs()[0].clone().sender_offset_public_key, + metadata_signature_ephemeral_commitment: transaction.body.outputs()[0] + .metadata_signature + .ephemeral_commitment() + .clone(), + metadata_signature_ephemeral_pubkey: total_metadata_ephemeral_public_key, + encrypted_data: transaction.body.outputs()[0].clone().encrypted_data, + output_features: transaction.body.outputs()[0].clone().features, + }; + let out_file = out_dir.join(get_file_name(STEP_3_PARTIES, None)); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, step_3_outputs_for_parties)?; + + println!(); + println!("Concluded step 3 'faucet-encumber-aggregate-utxo'"); + println!("Send '{}' to parties for step 4", get_file_name(STEP_3_PARTIES, None)); + println!(); }, - Err(e) => eprintln!("SendMinotari error! {}", e), + Err(e) => eprintln!("\nError: Encumber aggregate transaction error! {}\n", e), } }, - SendOneSided(args) => { - match send_one_sided( + FaucetCreateInputOutputSigs(args) => { + // Read session info + let session_info = read_verify_session_info(&args.session_id)?; + // Read leader input + let leader_info = read_and_verify::( + &args.session_id, + &get_file_name(STEP_3_PARTIES, None), + &session_info, + )?; + // Read own party info + let party_info = read_and_verify::( + &args.session_id, + &get_file_name(STEP_2_SELF, None), + &session_info, + )?; + + // Script signature + let challenge = TransactionInput::build_script_signature_challenge( + &TransactionInputVersion::get_current_version(), + &leader_info.script_signature_ephemeral_commitment, + &leader_info.script_signature_ephemeral_pubkey, + &leader_info.input_script, + &leader_info.input_stack, + &leader_info.total_script_key, + &Commitment::from_hex(&session_info.commitment_to_spend)?, + ); + + let mut script_signature = Signature::default(); + match key_manager_service + .sign_with_nonce_and_message( + &party_info.wallet_spend_key_id, + &party_info.script_nonce_key_id, + &challenge, + ) + .await + { + Ok(signature) => { + script_signature = signature; + }, + Err(e) => eprintln!("\nError: Script signature SignMessage error! {}\n", e), + } + + // Metadata signature + let script_offset = key_manager_service + .get_script_offset(&vec![party_info.wallet_spend_key_id], &vec![party_info + .sender_offset_key_id + .clone()]) + .await?; + let challenge = TransactionOutput::build_metadata_signature_challenge( + &TransactionOutputVersion::get_current_version(), + &script!(PushPubKey(Box::new( + session_info.recipient_address.public_spend_key().clone() + ))), + &leader_info.output_features, + &leader_info.sender_offset_pubkey, + &leader_info.metadata_signature_ephemeral_commitment, + &leader_info.metadata_signature_ephemeral_pubkey, + &leader_info.output_commitment, + &Covenant::default(), + &leader_info.encrypted_data, + MicroMinotari::zero(), + ); + + let mut metadata_signature = Signature::default(); + match key_manager_service + .sign_with_nonce_and_message( + &party_info.sender_offset_key_id, + &party_info.sender_offset_nonce_key_id, + &challenge, + ) + .await + { + Ok(signature) => { + metadata_signature = signature; + }, + Err(e) => eprintln!("\nError: Metadata signature SignMessage error! {}\n", e), + } + + if script_signature.get_signature() == Signature::default().get_signature() || + metadata_signature.get_signature() == Signature::default().get_signature() + { + eprintln!("\nError: Script and/or metadata signatures not created!\n") + } else { + let step_4_outputs_for_leader = Step4OutputsForLeader { + script_signature, + metadata_signature, + script_offset, + }; + + let out_dir = out_dir(&args.session_id)?; + let out_file = out_dir.join(get_file_name(STEP_4_LEADER, Some(party_info.alias.clone()))); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, step_4_outputs_for_leader)?; + + println!(); + println!("Concluded step 4 'faucet-create-input-output-sigs'"); + println!( + "Send '{}' to leader for step 5", + get_file_name(STEP_4_LEADER, Some(party_info.alias)) + ); + println!(); + } + }, + FaucetSpendAggregateUtxo(args) => { + // Read session info + let session_info = read_verify_session_info(&args.session_id)?; + + let mut metadata_signatures = Vec::with_capacity(args.input_file_names.len()); + let mut script_signatures = Vec::with_capacity(args.input_file_names.len()); + let mut offset = PrivateKey::default(); + for file_name in args.input_file_names { + // Read party input + let party_info = + read_and_verify::(&args.session_id, &file_name, &session_info)?; + metadata_signatures.push(party_info.metadata_signature); + script_signatures.push(party_info.script_signature); + offset = &offset + &party_info.script_offset; + } + + // Read own party info + let leader_info = read_and_verify::( + &args.session_id, + &get_file_name(STEP_3_SELF, None), + &session_info, + )?; + + match finalise_aggregate_utxo( + transaction_service.clone(), + leader_info.tx_id.as_u64(), + metadata_signatures, + script_signatures, + offset, + ) + .await + { + Ok(_v) => { + println!(); + println!("Concluded step 5 'faucet-spend-aggregate-utxo'"); + println!(); + }, + Err(e) => println!("\nError: Error completing transaction! {}\n", e), + } + }, + SendMinotari(args) => { + match send_tari( transaction_service.clone(), config.fee_per_gram, args.amount, - UtxoSelectionCriteria::default(), args.destination, args.message, ) .await { Ok(tx_id) => { - debug!(target: LOG_TARGET, "send-one-sided concluded with tx_id {}", tx_id); + debug!(target: LOG_TARGET, "send-minotari concluded with tx_id {}", tx_id); tx_ids.push(tx_id); }, - Err(e) => eprintln!("SendOneSided error! {}", e), + Err(e) => eprintln!("SendMinotari error! {}", e), } }, SendOneSidedToStealthAddress(args) => { @@ -750,6 +1117,7 @@ pub async fn command_runner( UtxoSelectionCriteria::default(), args.destination, args.message, + PaymentId::Empty, ) .await { @@ -803,24 +1171,44 @@ pub async fn command_runner( }, Whois(args) => { let public_key = args.public_key.into(); - let emoji_id = EmojiId::from_public_key(&public_key).to_emoji_string(); + let emoji_id = EmojiId::from(&public_key).to_string(); println!("Public Key: {}", public_key.to_hex()); println!("Emoji ID : {}", emoji_id); }, ExportUtxos(args) => match output_service.get_unspent_outputs().await { Ok(utxos) => { - let utxos: Vec<(WalletOutput, Commitment)> = - utxos.into_iter().map(|v| (v.wallet_output, v.commitment)).collect(); - let count = utxos.len(); - let sum: MicroMinotari = utxos.iter().map(|utxo| utxo.0.value).sum(); + let mut unblinded_utxos: Vec<(UnblindedOutput, Commitment)> = Vec::with_capacity(utxos.len()); + for output in utxos { + let unblinded = + UnblindedOutput::from_wallet_output(output.wallet_output, &wallet.key_manager_service) + .await?; + unblinded_utxos.push((unblinded, output.commitment)); + } + let count = unblinded_utxos.len(); + let sum: MicroMinotari = unblinded_utxos.iter().map(|utxo| utxo.0.value).sum(); if let Some(file) = args.output_file { - if let Err(e) = write_utxos_to_csv_file(utxos, file) { + if let Err(e) = write_utxos_to_csv_file(unblinded_utxos, file, args.with_private_keys) { eprintln!("ExportUtxos error! {}", e); } } else { - for (i, utxo) in utxos.iter().enumerate() { - println!("{}. Value: {} {}", i + 1, utxo.0.value, utxo.0.features); + for (i, utxo) in unblinded_utxos.iter().enumerate() { + println!( + "{}. Value: {}, Spending Key: {:?}, Script Key: {:?}, Features: {}", + i + 1, + utxo.0.value, + if args.with_private_keys { + utxo.0.spending_key.to_hex() + } else { + "*hidden*".to_string() + }, + if args.with_private_keys { + utxo.0.script_private_key.to_hex() + } else { + "*hidden*".to_string() + }, + utxo.0.features + ); } } println!("Total number of UTXOs: {}", count); @@ -858,17 +1246,37 @@ pub async fn command_runner( }, ExportSpentUtxos(args) => match output_service.get_spent_outputs().await { Ok(utxos) => { - let utxos: Vec<(WalletOutput, Commitment)> = - utxos.into_iter().map(|v| (v.wallet_output, v.commitment)).collect(); - let count = utxos.len(); - let sum: MicroMinotari = utxos.iter().map(|utxo| utxo.0.value).sum(); + let mut unblinded_utxos: Vec<(UnblindedOutput, Commitment)> = Vec::with_capacity(utxos.len()); + for output in utxos { + let unblinded = + UnblindedOutput::from_wallet_output(output.wallet_output, &wallet.key_manager_service) + .await?; + unblinded_utxos.push((unblinded, output.commitment)); + } + let count = unblinded_utxos.len(); + let sum: MicroMinotari = unblinded_utxos.iter().map(|utxo| utxo.0.value).sum(); if let Some(file) = args.output_file { - if let Err(e) = write_utxos_to_csv_file(utxos, file) { + if let Err(e) = write_utxos_to_csv_file(unblinded_utxos, file, args.with_private_keys) { eprintln!("ExportSpentUtxos error! {}", e); } } else { - for (i, utxo) in utxos.iter().enumerate() { - println!("{}. Value: {} {}", i + 1, utxo.0.value, utxo.0.features); + for (i, utxo) in unblinded_utxos.iter().enumerate() { + println!( + "{}. Value: {}, Spending Key: {:?}, Script Key: {:?}, Features: {}", + i + 1, + utxo.0.value, + if args.with_private_keys { + utxo.0.spending_key.to_hex() + } else { + "*hidden*".to_string() + }, + if args.with_private_keys { + utxo.0.script_private_key.to_hex() + } else { + "*hidden*".to_string() + }, + utxo.0.features + ); } } println!("Total number of UTXOs: {}", count); @@ -1105,22 +1513,26 @@ pub async fn command_runner( Ok(()) } -fn write_utxos_to_csv_file(utxos: Vec<(WalletOutput, Commitment)>, file_path: PathBuf) -> Result<(), CommandError> { +fn write_utxos_to_csv_file( + utxos: Vec<(UnblindedOutput, Commitment)>, + file_path: PathBuf, + with_private_keys: bool, +) -> Result<(), CommandError> { let file = File::create(file_path).map_err(|e| CommandError::CSVFile(e.to_string()))?; let mut csv_file = LineWriter::new(file); writeln!( csv_file, - 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_data","minimum_value_promise""## + r##""index","version","value","spending_key","commitment","output_type","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_data","minimum_value_promise","range_proof""## ) - .map_err(|e| CommandError::CSVFile(e.to_string()))?; + .map_err(|e| CommandError::CSVFile(e.to_string()))?; for (i, (utxo, commitment)) in utxos.iter().enumerate() { writeln!( csv_file, - r##""{}","V{}","{}","{}","{}","{:?}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}""##, + r##""{}","V{}","{}","{}","{}","{:?}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}","{}""##, i + 1, utxo.version.as_u8(), utxo.value.0, - utxo.spending_key_id, + if with_private_keys {utxo.spending_key.to_hex()} else { "*hidden*".to_string() }, commitment.to_hex(), utxo.features.output_type, utxo.features.maturity, @@ -1129,7 +1541,7 @@ fn write_utxos_to_csv_file(utxos: Vec<(WalletOutput, Commitment)>, file_path: Pa utxo.script.to_hex(), utxo.covenant.to_bytes().to_hex(), utxo.input_data.to_hex(), - utxo.script_key_id, + if with_private_keys {utxo.script_private_key.to_hex()} else { "*hidden*".to_string() }, utxo.sender_offset_public_key.to_hex(), utxo.metadata_signature.ephemeral_commitment().to_hex(), utxo.metadata_signature.ephemeral_pubkey().to_hex(), @@ -1138,9 +1550,20 @@ fn write_utxos_to_csv_file(utxos: Vec<(WalletOutput, Commitment)>, file_path: Pa utxo.metadata_signature.u_y().to_hex(), utxo.script_lock_height, utxo.encrypted_data.to_byte_vec().to_hex(), - utxo.minimum_value_promise.as_u64() + utxo.minimum_value_promise.as_u64(), + if let Some(proof) = utxo.range_proof.clone() { + proof.to_hex() + } else { + "".to_string() + }, ) - .map_err(|e| CommandError::CSVFile(e.to_string()))?; + .map_err(|e| CommandError::CSVFile(e.to_string()))?; + debug!( + target: LOG_TARGET, + "UTXO {} exported: {:?}", + i + 1, + utxo + ); } Ok(()) } diff --git a/applications/minotari_console_wallet/src/automation/error.rs b/applications/minotari_console_wallet/src/automation/error.rs index 395e59992b..10887d3588 100644 --- a/applications/minotari_console_wallet/src/automation/error.rs +++ b/applications/minotari_console_wallet/src/automation/error.rs @@ -35,6 +35,7 @@ use minotari_wallet::{ use tari_common::exit_codes::{ExitCode, ExitError}; use tari_common_types::types::FixedHashSizeError; use tari_core::transactions::{tari_amount::MicroMinotariError, transaction_components::TransactionError}; +use tari_crypto::signatures::SchnorrSignatureError; use tari_key_manager::key_manager_service::KeyManagerServiceError; use tari_utilities::{hex::HexError, ByteArrayError}; use thiserror::Error; @@ -87,6 +88,8 @@ pub enum CommandError { ByteArrayError(String), #[error("gRPC TLS cert error {0}")] GrpcTlsError(#[from] GrpcTlsError), + #[error("Invalid signature: `{0}`")] + FailedSignature(#[from] SchnorrSignatureError), } impl From for CommandError { diff --git a/applications/minotari_console_wallet/src/automation/mod.rs b/applications/minotari_console_wallet/src/automation/mod.rs index 1470375082..acdc48c0c5 100644 --- a/applications/minotari_console_wallet/src/automation/mod.rs +++ b/applications/minotari_console_wallet/src/automation/mod.rs @@ -22,5 +22,80 @@ pub mod commands; pub mod error; +mod utils; // removed temporarily add back in when used. // mod prompt; + +use serde::{Deserialize, Serialize}; +use tari_common_types::{ + tari_address::TariAddress, + transaction::TxId, + types::{Commitment, PrivateKey, PublicKey, Signature}, +}; +use tari_core::transactions::{ + key_manager::TariKeyId, + tari_amount::MicroMinotari, + transaction_components::{EncryptedData, OutputFeatures}, +}; +use tari_script::{CheckSigSchnorrSignature, ExecutionStack, TariScript}; + +// Outputs for self with `FaucetCreatePartyDetails` +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step1SessionInfo { + session_id: String, + fee_per_gram: MicroMinotari, + commitment_to_spend: String, + output_hash: String, + recipient_address: TariAddress, +} + +// Outputs for self with `FaucetCreatePartyDetails` +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step2OutputsForSelf { + alias: String, + wallet_spend_key_id: TariKeyId, + script_nonce_key_id: TariKeyId, + sender_offset_key_id: TariKeyId, + sender_offset_nonce_key_id: TariKeyId, +} + +// Outputs for leader with `FaucetCreatePartyDetails` +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step2OutputsForLeader { + script_input_signature: CheckSigSchnorrSignature, + wallet_public_spend_key: PublicKey, + public_script_nonce_key: PublicKey, + public_sender_offset_key: PublicKey, + public_sender_offset_nonce_key: PublicKey, + dh_shared_secret_public_key: PublicKey, +} + +// Outputs for self with `FaucetEncumberAggregateUtxo` +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step3OutputsForSelf { + tx_id: TxId, +} + +// Outputs for parties with `FaucetEncumberAggregateUtxo` +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step3OutputsForParties { + input_stack: ExecutionStack, + input_script: TariScript, + total_script_key: PublicKey, + script_signature_ephemeral_commitment: Commitment, + script_signature_ephemeral_pubkey: PublicKey, + output_commitment: Commitment, + sender_offset_pubkey: PublicKey, + metadata_signature_ephemeral_commitment: Commitment, + metadata_signature_ephemeral_pubkey: PublicKey, + encrypted_data: EncryptedData, + output_features: OutputFeatures, +} + +// Outputs for leader with `FaucetCreateScriptSig` and `FaucetCreateMetaSig` +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step4OutputsForLeader { + script_signature: Signature, + metadata_signature: Signature, + script_offset: PrivateKey, +} diff --git a/applications/minotari_console_wallet/src/automation/utils.rs b/applications/minotari_console_wallet/src/automation/utils.rs new file mode 100644 index 0000000000..1cc6507875 --- /dev/null +++ b/applications/minotari_console_wallet/src/automation/utils.rs @@ -0,0 +1,215 @@ +// 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::{ + fs, + fs::{File, OpenOptions}, + io::{BufRead, BufReader, Write}, + path::{Path, PathBuf}, +}; + +use serde::{de::DeserializeOwned, Serialize}; + +use crate::automation::{ + commands::{FILE_EXTENSION, SESSION_INFO}, + error::CommandError, + Step1SessionInfo, +}; + +#[derive(Debug)] +pub(crate) struct PartialRead { + pub(crate) lines_to_read: usize, + pub(crate) lines_to_skip: usize, +} + +/// Reads an entire file into a single JSON object +pub(crate) fn json_from_file_single_object, T: DeserializeOwned>( + path: P, + partial_read: Option, +) -> Result { + if let Some(val) = partial_read { + let lines = BufReader::new( + File::open(path.as_ref()) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display())))?, + ) + .lines() + .take(val.lines_to_read) + .skip(val.lines_to_skip); + let mut json_str = String::new(); + for line in lines { + let line = line.map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display())))?; + json_str.push_str(&line); + } + serde_json::from_str(&json_str) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display()))) + } else { + serde_json::from_reader(BufReader::new( + File::open(path.as_ref()) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display())))?, + )) + .map_err(|e| CommandError::JsonFile(format!("{e} '{}'", path.as_ref().display()))) + } +} + +/// Write a single JSON object to file as a single line +pub(crate) fn write_json_object_to_file_as_line( + file: &Path, + reset_file: bool, + outputs: T, +) -> Result<(), CommandError> { + if let Some(file_path) = file.parent() { + if !file_path.exists() { + fs::create_dir_all(file_path).map_err(|e| CommandError::JsonFile(format!("{} ({})", e, file.display())))?; + } + } + if reset_file && file.exists() { + fs::remove_file(file).map_err(|e| CommandError::JsonFile(e.to_string()))?; + } + append_json_line_to_file(file, outputs)?; + Ok(()) +} + +fn append_json_line_to_file, T: Serialize>(file: P, output: T) -> Result<(), CommandError> { + fs::create_dir_all(file.as_ref().parent().unwrap()).map_err(|e| CommandError::JsonFile(e.to_string()))?; + let mut file_object = OpenOptions::new() + .create(true) + .append(true) + .open(file) + .map_err(|e| CommandError::JsonFile(e.to_string()))?; + let json = serde_json::to_string(&output).map_err(|e| CommandError::JsonFile(e.to_string()))?; + writeln!(file_object, "{json}").map_err(|e| CommandError::JsonFile(e.to_string()))?; + Ok(()) +} + +/// Write outputs to a JSON file +pub(crate) fn write_to_json_file(file: &Path, reset_file: bool, data: T) -> Result<(), CommandError> { + if let Some(file_path) = file.parent() { + if !file_path.exists() { + fs::create_dir_all(file_path).map_err(|e| CommandError::JsonFile(format!("{} ({})", e, file.display())))?; + } + } + if reset_file && file.exists() { + fs::remove_file(file).map_err(|e| CommandError::JsonFile(e.to_string()))?; + } + append_to_json_file(file, data)?; + Ok(()) +} + +fn append_to_json_file, T: Serialize>(file: P, data: T) -> Result<(), CommandError> { + fs::create_dir_all(file.as_ref().parent().unwrap()).map_err(|e| CommandError::JsonFile(e.to_string()))?; + let mut file_object = OpenOptions::new() + .create(true) + .append(true) + .open(file) + .map_err(|e| CommandError::JsonFile(e.to_string()))?; + let json = serde_json::to_string_pretty(&data).map_err(|e| CommandError::JsonFile(e.to_string()))?; + writeln!(file_object, "{json}").map_err(|e| CommandError::JsonFile(e.to_string()))?; + Ok(()) +} + +/// Return the output directory for the session +pub(crate) fn out_dir(session_id: &str) -> Result { + let base_dir = dirs::cache_dir().ok_or(CommandError::InvalidArgument( + "Could not find cache directory".to_string(), + ))?; + Ok(base_dir.join("tari_faucets").join(session_id)) +} + +/// Move the session file to the session directory +pub(crate) fn move_session_file_to_session_dir(session_id: &str, input_file: &PathBuf) -> Result<(), CommandError> { + let out_dir = out_dir(session_id)?; + let session_file = out_dir.join(get_file_name(SESSION_INFO, None)); + if input_file != &session_file { + fs::copy(input_file.clone(), session_file.clone())?; + fs::remove_file(input_file.clone())?; + println!( + "Session info file '{}' moved to '{}'", + input_file.display(), + session_file.display() + ); + } + Ok(()) +} + +/// Read the session info from the session directory and verify the supplied session ID +pub(crate) fn read_verify_session_info(session_id: &str) -> Result { + let file_path = out_dir(session_id)?.join(get_file_name(SESSION_INFO, None)); + let session_info = json_from_file_single_object::<_, Step1SessionInfo>(&file_path, None)?; + if session_info.session_id != session_id { + return Err(CommandError::InvalidArgument(format!( + "Session ID in session info file '{}' mismatch", + get_file_name(SESSION_INFO, None) + ))); + } + Ok(session_info) +} + +/// Read the session info from the session directory +pub(crate) fn read_session_info(session_file: PathBuf) -> Result { + json_from_file_single_object::<_, Step1SessionInfo>(&session_file, None) +} + +/// Read the inputs from the session directory and verify the header +pub(crate) fn read_and_verify( + session_id: &str, + file_name: &str, + session_info: &Step1SessionInfo, +) -> Result { + let out_dir = out_dir(session_id)?; + let header = json_from_file_single_object::<_, Step1SessionInfo>( + &out_dir.join(file_name), + Some(PartialRead { + lines_to_read: 1, + lines_to_skip: 0, + }), + )?; + if session_id != header.session_id { + return Err(CommandError::InvalidArgument(format!( + "Session ID in header for file '{}' mismatch", + file_name + ))); + } + if session_info != &header { + return Err(CommandError::InvalidArgument(format!( + "Session info in header for file '{}' mismatch", + file_name + ))); + } + json_from_file_single_object::<_, T>( + &out_dir.join(file_name), + Some(PartialRead { + lines_to_read: usize::MAX, + lines_to_skip: 1, + }), + ) +} + +/// Create the file name with the given stem and optional suffix +pub(crate) fn get_file_name(stem: &str, suffix: Option) -> String { + let mut file_name = stem.to_string(); + if let Some(suffix) = suffix { + file_name.push_str(&suffix); + } + file_name.push('.'); + file_name.push_str(FILE_EXTENSION); + file_name +} diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index fe0bb377ac..2787b2cee5 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -116,7 +116,11 @@ pub enum CliCommands { GetBalance, SendMinotari(SendMinotariArgs), BurnMinotari(BurnMinotariArgs), - SendOneSided(SendMinotariArgs), + FaucetGenerateSessionInfo(FaucetGenerateSessionInfoArgs), + FaucetCreatePartyDetails(FaucetCreatePartyDetailsArgs), + FaucetEncumberAggregateUtxo(FaucetEncumberAggregateUtxoArgs), + FaucetCreateInputOutputSigs(FaucetCreateInputOutputSigArgs), + FaucetSpendAggregateUtxo(FaucetSpendAggregateUtxoArgs), SendOneSidedToStealthAddress(SendMinotariArgs), MakeItRain(MakeItRainArgs), CoinSplit(CoinSplitArgs), @@ -158,6 +162,50 @@ pub struct BurnMinotariArgs { pub message: String, } +#[derive(Debug, Args, Clone)] +pub struct FaucetGenerateSessionInfoArgs { + #[clap(long)] + pub fee_per_gram: MicroMinotari, + #[clap(long)] + pub commitment: String, + #[clap(long)] + pub output_hash: String, + #[clap(long)] + pub recipient_address: TariAddress, + #[clap(long)] + pub verify_unspent_outputs: bool, +} + +#[derive(Debug, Args, Clone)] +pub struct FaucetCreatePartyDetailsArgs { + #[clap(long)] + pub input_file: PathBuf, + #[clap(long)] + pub alias: String, +} + +#[derive(Debug, Args, Clone)] +pub struct FaucetEncumberAggregateUtxoArgs { + #[clap(long)] + pub session_id: String, + #[clap(long)] + pub input_file_names: Vec, +} + +#[derive(Debug, Args, Clone)] +pub struct FaucetCreateInputOutputSigArgs { + #[clap(long)] + pub session_id: String, +} + +#[derive(Debug, Args, Clone)] +pub struct FaucetSpendAggregateUtxoArgs { + #[clap(long)] + pub session_id: String, + #[clap(long)] + pub input_file_names: Vec, +} + #[derive(Debug, Args, Clone)] pub struct MakeItRainArgs { pub destination: TariAddress, @@ -171,10 +219,8 @@ pub struct MakeItRainArgs { pub increase_amount: MicroMinotari, #[clap(long, parse(try_from_str=parse_start_time))] pub start_time: Option>, - #[clap(short, long)] - pub one_sided: bool, #[clap(long, alias = "stealth-one-sided")] - pub stealth: bool, + pub one_sided: bool, #[clap(short, long)] pub burn_tari: bool, #[clap(short, long, default_value = "Make it rain")] @@ -183,10 +229,8 @@ pub struct MakeItRainArgs { impl MakeItRainArgs { pub fn transaction_type(&self) -> MakeItRainTransactionType { - if self.stealth { + if self.one_sided { MakeItRainTransactionType::StealthOneSided - } else if self.one_sided { - MakeItRainTransactionType::OneSided } else if self.burn_tari { MakeItRainTransactionType::BurnTari } else { @@ -198,7 +242,6 @@ impl MakeItRainArgs { #[derive(Debug, Clone, Copy)] pub enum MakeItRainTransactionType { Interactive, - OneSided, StealthOneSided, BurnTari, } @@ -241,6 +284,7 @@ pub struct WhoisArgs { pub struct ExportUtxosArgs { #[clap(short, long)] pub output_file: Option, + pub with_private_keys: bool, } #[derive(Debug, Args, Clone)] diff --git a/applications/minotari_console_wallet/src/config.rs b/applications/minotari_console_wallet/src/config.rs index 254fa4bb1d..1f10fa5fd5 100644 --- a/applications/minotari_console_wallet/src/config.rs +++ b/applications/minotari_console_wallet/src/config.rs @@ -44,8 +44,6 @@ impl ApplicationConfig { peer_seeds: PeerSeedsConfig::load_from(cfg)?, }; - config.wallet.p2p.user_agent = format!("tari/wallet/{}", env!("CARGO_PKG_VERSION")); - config.wallet.set_base_path(config.common.base_path()); Ok(config) } diff --git a/applications/minotari_console_wallet/src/grpc/mod.rs b/applications/minotari_console_wallet/src/grpc/mod.rs index ecffc7ce38..b56951b623 100644 --- a/applications/minotari_console_wallet/src/grpc/mod.rs +++ b/applications/minotari_console_wallet/src/grpc/mod.rs @@ -23,8 +23,8 @@ pub fn convert_to_transaction_event(event: String, source: TransactionWrapper) - TransactionWrapper::Completed(completed) => TransactionEvent { event, tx_id: completed.tx_id.to_string(), - source_address: completed.source_address.to_bytes().to_vec(), - dest_address: completed.destination_address.to_bytes().to_vec(), + source_address: completed.source_address.to_vec(), + dest_address: completed.destination_address.to_vec(), status: completed.status.to_string(), direction: completed.direction.to_string(), amount: completed.amount.as_u64(), @@ -34,7 +34,7 @@ pub fn convert_to_transaction_event(event: String, source: TransactionWrapper) - event, tx_id: outbound.tx_id.to_string(), source_address: vec![], - dest_address: outbound.destination_address.to_bytes().to_vec(), + dest_address: outbound.destination_address.to_vec(), status: outbound.status.to_string(), direction: "outbound".to_string(), amount: outbound.amount.as_u64(), @@ -43,7 +43,7 @@ pub fn convert_to_transaction_event(event: String, source: TransactionWrapper) - TransactionWrapper::Inbound(inbound) => TransactionEvent { event, tx_id: inbound.tx_id.to_string(), - source_address: inbound.source_address.to_bytes().to_vec(), + source_address: inbound.source_address.to_vec(), dest_address: vec![], status: inbound.status.to_string(), direction: "inbound".to_string(), diff --git a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs index 3ded0ee040..510ff31571 100644 --- a/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/minotari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -99,7 +99,7 @@ use tari_core::{ consensus::{ConsensusBuilderError, ConsensusConstants, ConsensusManager}, transactions::{ tari_amount::MicroMinotari, - transaction_components::{OutputFeatures, UnblindedOutput}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, UnblindedOutput}, }, }; use tari_crypto::ristretto::RistrettoSecretKey; @@ -135,6 +135,7 @@ pub struct WalletGrpcServer { } impl WalletGrpcServer { + #[allow(dead_code)] pub fn new(wallet: WalletSqlite) -> Result { let rules = ConsensusManager::builder(wallet.network.as_network()).build()?; Ok(Self { wallet, rules }) @@ -154,7 +155,7 @@ impl WalletGrpcServer { fn get_consensus_constants(&self) -> Result<&ConsensusConstants, WalletStorageError> { // If we don't have the chain metadata, we hope that VNReg consensus constants did not change - worst case, we - // spend more than we need to or the the transaction is rejected. + // spend more than we need to or the transaction is rejected. let height = self .wallet .db @@ -220,11 +221,13 @@ 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); + let address = self + .wallet + .get_wallet_interactive_address() + .await + .map_err(|e| Status::internal(format!("{:?}", e)))?; Ok(Response::new(GetAddressResponse { - address: address.to_bytes().to_vec(), + address: address.to_vec(), })) } @@ -325,7 +328,7 @@ impl wallet_server::Wallet for WalletGrpcServer { .into_inner() .recipient .ok_or_else(|| Status::internal("Request is malformed".to_string()))?; - let address = TariAddress::from_hex(&message.address) + let address = TariAddress::from_base58(&message.address) .map_err(|_| Status::internal("Destination address is malformed".to_string()))?; let mut transaction_service = self.get_transaction_service(); @@ -486,7 +489,7 @@ impl wallet_server::Wallet for WalletGrpcServer { .into_iter() .enumerate() .map(|(idx, dest)| -> Result<_, String> { - let address = TariAddress::from_hex(&dest.address) + let address = TariAddress::from_base58(&dest.address) .map_err(|_| format!("Destination address at index {} is malformed", idx))?; Ok(( dest.address, @@ -495,13 +498,17 @@ impl wallet_server::Wallet for WalletGrpcServer { dest.fee_per_gram, dest.message, dest.payment_type, + dest.payment_id, )) }) .collect::, _>>() .map_err(Status::invalid_argument)?; let mut transfers = Vec::new(); - for (hex_address, address, amount, fee_per_gram, message, payment_type) in recipients { + for (hex_address, address, amount, fee_per_gram, message, payment_type, payment_id) in recipients { + let payment_id = PaymentId::from_bytes(&payment_id) + .map_err(|_| "Invalid payment id".to_string()) + .map_err(Status::invalid_argument)?; let mut transaction_service = self.get_transaction_service(); transfers.push(async move { ( @@ -526,6 +533,7 @@ impl wallet_server::Wallet for WalletGrpcServer { OutputFeatures::default(), fee_per_gram.into(), message, + payment_id, ) .await } else { @@ -537,6 +545,7 @@ impl wallet_server::Wallet for WalletGrpcServer { OutputFeatures::default(), fee_per_gram.into(), message, + payment_id, ) .await }, @@ -652,10 +661,11 @@ impl wallet_server::Wallet for WalletGrpcServer { .await .map(|tx| tx.into_iter()) .map_err(|err| Status::unknown(err.to_string()))?; - - let wallet_pk = self.wallet.comms.node_identity_ref().public_key(); - let wallet_network = self.wallet.network.as_network(); - let wallet_address = TariAddress::new(wallet_pk.clone(), wallet_network); + let wallet_address = self + .wallet + .get_wallet_interactive_address() + .await + .map_err(|e| Status::internal(format!("{:?}", e)))?; let transactions = transactions .map(|(tx_id, tx)| match tx { Some(tx) => convert_wallet_transaction_into_transaction_info(tx, &wallet_address), @@ -757,8 +767,8 @@ impl wallet_server::Wallet for WalletGrpcServer { let response = GetCompletedTransactionsResponse { transaction: Some(TransactionInfo { tx_id: txn.tx_id.into(), - source_address: txn.source_address.to_bytes().to_vec(), - dest_address: txn.destination_address.to_bytes().to_vec(), + source_address: txn.source_address.to_vec(), + dest_address: txn.destination_address.to_vec(), status: TransactionStatus::from(txn.status.clone()) as i32, amount: txn.amount.into(), is_cancelled: txn.cancelled.is_some(), @@ -1108,8 +1118,8 @@ fn convert_wallet_transaction_into_transaction_info( match tx { PendingInbound(tx) => TransactionInfo { tx_id: tx.tx_id.into(), - source_address: tx.source_address.to_bytes().to_vec(), - dest_address: wallet_address.to_bytes().to_vec(), + source_address: tx.source_address.to_vec(), + dest_address: wallet_address.to_vec(), status: TransactionStatus::from(tx.status) as i32, amount: tx.amount.into(), is_cancelled: tx.cancelled, @@ -1121,8 +1131,8 @@ fn convert_wallet_transaction_into_transaction_info( }, PendingOutbound(tx) => TransactionInfo { tx_id: tx.tx_id.into(), - source_address: wallet_address.to_bytes().to_vec(), - dest_address: tx.destination_address.to_bytes().to_vec(), + source_address: wallet_address.to_vec(), + dest_address: tx.destination_address.to_vec(), status: TransactionStatus::from(tx.status) as i32, amount: tx.amount.into(), is_cancelled: tx.cancelled, @@ -1134,8 +1144,8 @@ fn convert_wallet_transaction_into_transaction_info( }, Completed(tx) => TransactionInfo { tx_id: tx.tx_id.into(), - source_address: tx.source_address.to_bytes().to_vec(), - dest_address: tx.destination_address.to_bytes().to_vec(), + source_address: tx.source_address.to_vec(), + dest_address: tx.destination_address.to_vec(), status: TransactionStatus::from(tx.status) as i32, amount: tx.amount.into(), is_cancelled: tx.cancelled.is_some(), diff --git a/applications/minotari_console_wallet/src/init/mod.rs b/applications/minotari_console_wallet/src/init/mod.rs index e2cd0f9b1e..09dcc86651 100644 --- a/applications/minotari_console_wallet/src/init/mod.rs +++ b/applications/minotari_console_wallet/src/init/mod.rs @@ -27,7 +27,14 @@ use std::{fs, io, path::PathBuf, str::FromStr, sync::Arc, time::Instant}; #[cfg(feature = "ledger")] use ledger_transport_hid::{hidapi::HidApi, TransportNativeHID}; use log::*; -use minotari_app_utilities::identity_management::setup_node_identity; +use minotari_app_utilities::{consts, identity_management::setup_node_identity}; +#[cfg(feature = "ledger")] +use minotari_ledger_wallet_comms::ledger_wallet::LedgerCommands; +#[cfg(feature = "ledger")] +use minotari_ledger_wallet_comms::{ + error::LedgerDeviceError, + ledger_wallet::{get_transport, Instruction}, +}; use minotari_wallet::{ error::{WalletError, WalletStorageError}, output_manager_service::storage::database::OutputManagerDatabase, @@ -50,15 +57,21 @@ use tari_common::{ }, exit_codes::{ExitCode, ExitError}, }; -use tari_common_types::wallet_types::WalletType; +use tari_common_types::{ + types::{PrivateKey, PublicKey}, + wallet_types::{LedgerWallet, WalletType}, +}; use tari_comms::{ multiaddr::Multiaddr, peer_manager::{Peer, PeerFeatures, PeerQuery}, types::CommsPublicKey, NodeIdentity, }; -use tari_core::{consensus::ConsensusManager, transactions::CryptoFactories}; -use tari_crypto::keys::PublicKey; +use tari_core::{ + consensus::ConsensusManager, + transactions::{transaction_components::TransactionError, CryptoFactories}, +}; +use tari_crypto::{keys::PublicKey as PublicKeyTrait, ristretto::RistrettoPublicKey}; use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::MnemonicLanguage}; use tari_p2p::{peer_seeds::SeedPeer, TransportType}; use tari_shutdown::ShutdownSignal; @@ -419,24 +432,8 @@ pub async fn init_wallet( }; let master_seed = read_or_create_master_seed(recovery_seed.clone(), &wallet_db)?; - let wallet_type = read_or_create_wallet_type(wallet_type, &wallet_db); - let node_identity = match config.wallet.identity_file.as_ref() { - Some(identity_file) => { - warn!( - target: LOG_TARGET, - "Node identity overridden by file {}", - identity_file.to_string_lossy() - ); - setup_node_identity( - identity_file, - node_addresses.to_vec(), - true, - PeerFeatures::COMMUNICATION_CLIENT, - )? - }, - None => setup_identity_from_db(&wallet_db, &master_seed, node_addresses.to_vec())?, - }; + let node_identity = setup_identity_from_db(&wallet_db, &master_seed, node_addresses.to_vec())?; let mut wallet_config = config.wallet.clone(); if let TransportType::Tor = config.wallet.p2p.transport.transport_type { @@ -449,7 +446,7 @@ pub async fn init_wallet( let factories = CryptoFactories::default(); let now = Instant::now(); - + let user_agent = format!("tari/wallet/{}", consts::APP_VERSION_NUMBER); let mut wallet = Wallet::start( wallet_config, config.peer_seeds.clone(), @@ -465,7 +462,8 @@ pub async fn init_wallet( key_manager_backend, shutdown_signal, master_seed, - wallet_type.unwrap(), + wallet_type, + user_agent, ) .await .map_err(|e| match e { @@ -817,34 +815,89 @@ pub fn prompt_wallet_type( non_interactive: bool, ) -> Option { if non_interactive { - return Some(WalletType::Software); - } - - if wallet_config.wallet_type.is_some() { - return wallet_config.wallet_type; + return Some(WalletType::default()); } match boot_mode { - WalletBoot::New => { + WalletBoot::New | WalletBoot::Recovery => { #[cfg(not(feature = "ledger"))] - return Some(WalletType::Software); + return Some(WalletType::default()); #[cfg(feature = "ledger")] { - if prompt("\r\nWould you like to use a connected hardware wallet? (Supported types: Ledger)") { + let connected_hardware_msg = match boot_mode { + WalletBoot::Recovery => { + "\r\nWas your wallet connected to a hardware device? (Supported types: Ledger) (Y/n)" + }, + _ => "\r\nWould you like to use a connected hardware wallet? (Supported types: Ledger) (Y/n)", + }; + if prompt(connected_hardware_msg) { print!("Scanning for connected Ledger hardware device... "); - let err = "No connected device was found. Please make sure the device is plugged in before - continuing."; - match TransportNativeHID::new(&HidApi::new().expect(err)) { - Ok(_) => { + match get_transport() { + Ok(hid) => { println!("Device found."); - let account = prompt_ledger_account().expect("An account value"); - Some(WalletType::Ledger(account)) + let account = prompt_ledger_account(boot_mode).expect("An account value"); + let ledger = LedgerWallet::new(account, wallet_config.network, None, None); + match ledger + .build_command(Instruction::GetPublicAlpha, vec![]) + .execute_with_transport(&hid) + { + Ok(result) => { + debug!(target: LOG_TARGET, "result length: {}, data: {:?}", result.data().len(), result.data()); + if result.data().len() < 33 { + debug!(target: LOG_TARGET, "result less than 33"); + panic!( + "'get_public_key' insufficient data - expected 33 got {} bytes ({:?})", + result.data().len(), + result + ); + } + + let public_alpha = match PublicKey::from_canonical_bytes(&result.data()[1..33]) { + Ok(k) => k, + Err(e) => panic!("{}", e), + }; + + match ledger + .build_command(Instruction::GetViewKey, vec![]) + .execute_with_transport(&hid) + { + Ok(result) => { + debug!(target: LOG_TARGET, "result length: {}, data: {:?}", result.data().len(), result.data()); + if result.data().len() < 33 { + debug!(target: LOG_TARGET, "result less than 33"); + panic!( + "'get_view_key' insufficient data - expected 33 got {} bytes \ + ({:?})", + result.data().len(), + result + ); + } + + let view_key = match PrivateKey::from_canonical_bytes(&result.data()[1..33]) + { + Ok(k) => k, + Err(e) => panic!("{}", e), + }; + + let ledger = LedgerWallet::new( + account, + wallet_config.network, + Some(public_alpha), + Some(view_key), + ); + Some(WalletType::Ledger(ledger)) + }, + Err(e) => panic!("{}", e), + } + }, + Err(e) => panic!("{}", e), + } }, Err(e) => panic!("{}", e), } } else { - Some(WalletType::Software) + Some(WalletType::default()) } } }, @@ -852,9 +905,14 @@ pub fn prompt_wallet_type( } } -pub fn prompt_ledger_account() -> Option { - let question = - "\r\nPlease enter an account number for your ledger. A simple 1-9, easily remembered numbers are suggested."; +pub fn prompt_ledger_account(boot_mode: WalletBoot) -> Option { + let question = match boot_mode { + WalletBoot::Recovery => "\r\nPlease enter the account number you previously used for your device.", + _ => { + "\r\nPlease enter an account number for your device. A simple 1-9, easily remembered numbers are suggested." + }, + }; + println!("{}", question); let mut input = "".to_string(); io::stdin().read_line(&mut input).unwrap(); diff --git a/applications/minotari_console_wallet/src/lib.rs b/applications/minotari_console_wallet/src/lib.rs index 26634aa379..331b6bb2ef 100644 --- a/applications/minotari_console_wallet/src/lib.rs +++ b/applications/minotari_console_wallet/src/lib.rs @@ -128,7 +128,6 @@ pub fn run_wallet_with_cli( let recovery_seed = get_recovery_seed(boot_mode, &cli)?; - // This is deactivated at the moment as full support is not yet complete let wallet_type = prompt_wallet_type(boot_mode, &config.wallet, cli.non_interactive_mode); // get command line password if provided diff --git a/applications/minotari_console_wallet/src/notifier/mod.rs b/applications/minotari_console_wallet/src/notifier/mod.rs index 5f160970f0..2d5722f2d2 100644 --- a/applications/minotari_console_wallet/src/notifier/mod.rs +++ b/applications/minotari_console_wallet/src/notifier/mod.rs @@ -49,6 +49,7 @@ pub const CANCELLED: &str = "cancelled"; #[derive(Clone)] // FIXME +#[allow(dead_code)] #[allow(clippy::large_enum_variant)] pub enum WalletEventMessage { Completed { @@ -310,8 +311,8 @@ fn args_from_complete(tx: &CompletedTransaction, event: &str, confirmations: Opt amount, tx.tx_id.to_string(), tx.message.clone(), - tx.source_address.to_hex(), - tx.destination_address.to_hex(), + tx.source_address.to_base58(), + tx.destination_address.to_base58(), status, excess, public_nonce, @@ -331,7 +332,7 @@ fn args_from_outbound(tx: &OutboundTransaction, event: &str) -> Vec { amount, tx.tx_id.to_string(), tx.message.clone(), - tx.destination_address.to_hex(), + tx.destination_address.to_base58(), status, "outbound".to_string(), ] @@ -347,7 +348,7 @@ fn args_from_inbound(tx: &InboundTransaction, event: &str) -> Vec { amount, tx.tx_id.to_string(), tx.message.clone(), - tx.source_address.to_hex(), + tx.source_address.to_base58(), status, "inbound".to_string(), ] diff --git a/applications/minotari_console_wallet/src/recovery.rs b/applications/minotari_console_wallet/src/recovery.rs index 9ca3969a23..c1f0418f7d 100644 --- a/applications/minotari_console_wallet/src/recovery.rs +++ b/applications/minotari_console_wallet/src/recovery.rs @@ -22,11 +22,14 @@ #![allow(dead_code, unused)] +use std::ptr; + use chrono::offset::Local; use futures::FutureExt; use log::*; use minotari_wallet::{ connectivity_service::WalletConnectivityHandle, + error::WalletError, storage::sqlite_db::wallet::WalletSqliteDatabase, utxo_scanner_service::{handle::UtxoScannerEvent, service::UtxoScannerService}, WalletSqlite, @@ -37,7 +40,7 @@ use tari_crypto::tari_utilities::Hidden; use tari_key_manager::{cipher_seed::CipherSeed, mnemonic::Mnemonic, SeedWords}; use tari_shutdown::Shutdown; use tari_utilities::hex::Hex; -use tokio::sync::broadcast; +use tokio::{runtime::Runtime, sync::broadcast}; use zeroize::{Zeroize, Zeroizing}; use crate::wallet_modes::PeerConfig; @@ -122,7 +125,7 @@ pub async fn wallet_recovery( .with_peers(peer_public_keys) // Do not make this a small number as wallet recovery needs to be resilient .with_retry_limit(retry_limit) - .build_with_wallet(wallet, shutdown_signal); + .build_with_wallet(wallet, shutdown_signal).await.map_err(|e| ExitError::new(ExitCode::RecoveryError, e))?; let mut event_stream = recovery_task.get_event_receiver(); @@ -141,7 +144,8 @@ pub async fn wallet_recovery( current_height, tip_height, }) => { - let percentage_progress = (current_height * 100) / tip_height; + // its going to fail if the tip height is 0, meaning if you scanned up to 0, you are done + let percentage_progress = (current_height * 100).checked_div(tip_height).unwrap_or(100); debug!( target: LOG_TARGET, "{}: Recovery process {}% complete (Block {} of {}).", diff --git a/applications/minotari_console_wallet/src/ui/app.rs b/applications/minotari_console_wallet/src/ui/app.rs index 7cd1c19e74..98d5550c42 100644 --- a/applications/minotari_console_wallet/src/ui/app.rs +++ b/applications/minotari_console_wallet/src/ui/app.rs @@ -20,7 +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 minotari_wallet::{util::wallet_identity::WalletIdentity, WalletConfig, WalletSqlite}; +use minotari_wallet::{error::WalletError, util::wallet_identity::WalletIdentity, WalletConfig, WalletSqlite}; +use tari_common::exit_codes::ExitError; use tari_comms::peer_manager::Peer; use tokio::runtime::Handle; use tui::{ @@ -57,6 +58,7 @@ use crate::{ pub const LOG_TARGET: &str = "wallet::ui::app"; pub struct App { + #[allow(dead_code)] pub title: String, pub should_quit: bool, // Cached state this will need to be cleaned up into a threadsafe container @@ -69,15 +71,27 @@ pub struct App { } impl App { - pub fn new( + pub async fn new( title: String, wallet: WalletSqlite, wallet_config: WalletConfig, base_node_selected: Peer, base_node_config: PeerConfig, notifier: Notifier, - ) -> Self { - let wallet_id = WalletIdentity::new(wallet.comms.node_identity(), wallet.network.as_network()); + ) -> Result { + let wallet_address_interactive = wallet + .get_wallet_interactive_address() + .await + .map_err(WalletError::KeyManagerServiceError)?; + let wallet_address_one_sided = wallet + .get_wallet_one_sided_address() + .await + .map_err(WalletError::KeyManagerServiceError)?; + let wallet_id = WalletIdentity::new( + wallet.comms.node_identity(), + wallet_address_interactive, + wallet_address_one_sided, + ); let app_state = AppState::new( &wallet_id, wallet, @@ -101,7 +115,7 @@ impl App { let base_node_status = BaseNode::new(); let menu = Menu::new(); - Self { + Ok(Self { title, should_quit: false, app_state, @@ -109,7 +123,7 @@ impl App { base_node_status, menu, notifier, - } + }) } pub fn on_control_key(&mut self, c: char) { diff --git a/applications/minotari_console_wallet/src/ui/components/receive_tab.rs b/applications/minotari_console_wallet/src/ui/components/receive_tab.rs index 388b98f516..10617063d7 100644 --- a/applications/minotari_console_wallet/src/ui/components/receive_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/receive_tab.rs @@ -29,7 +29,7 @@ impl ReceiveTab { let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Length(6), Constraint::Length(23)].as_ref()) + .constraints([Constraint::Length(8), Constraint::Length(23)].as_ref()) .margin(1) .split(area); @@ -46,6 +46,8 @@ impl ReceiveTab { Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), + Constraint::Length(1), + Constraint::Length(1), ] .as_ref(), ) @@ -57,54 +59,76 @@ impl ReceiveTab { .title(Span::styled("Connection Details", Style::default().fg(Color::White))); f.render_widget(block, chunks[0]); - const ITEM_01: &str = "Tari Address: "; - const ITEM_02: &str = "Node ID: "; - const ITEM_03: &str = "Network Address: "; - const ITEM_04: &str = "Emoji ID: "; + const ITEM_01: &str = "Tari Address interactive: "; + const ITEM_02: &str = "Tari Address one-sided: "; + const ITEM_03: &str = "Node ID: "; + const ITEM_04: &str = "Network Address: "; + const ITEM_05: &str = "Interactive emoji address: "; + const ITEM_06: &str = "One-sided emoji address: "; // Tari address - let tari_address_text = Spans::from(vec![ + let tari_address_interactive_text = Spans::from(vec![ Span::styled(ITEM_01, Style::default().fg(Color::Magenta)), Span::styled( - app_state.get_identity().tari_address.clone(), + app_state.get_identity().tari_address_interactive.to_base58(), Style::default().fg(Color::White), ), ]); - let paragraph = Paragraph::new(tari_address_text).block(Block::default()); + let paragraph = Paragraph::new(tari_address_interactive_text).block(Block::default()); f.render_widget(paragraph, details_chunks[0]); + let tari_address_one_sided_text = Spans::from(vec![ + Span::styled(ITEM_02, Style::default().fg(Color::Magenta)), + Span::styled( + app_state.get_identity().tari_address_one_sided.to_base58(), + Style::default().fg(Color::White), + ), + ]); + let paragraph = Paragraph::new(tari_address_one_sided_text).block(Block::default()); + f.render_widget(paragraph, details_chunks[1]); + // NodeId let node_id_text = Spans::from(vec![ - Span::styled(ITEM_02, Style::default().fg(Color::Magenta)), + Span::styled(ITEM_03, Style::default().fg(Color::Magenta)), Span::styled( app_state.get_identity().node_id.clone(), Style::default().fg(Color::White), ), ]); let paragraph = Paragraph::new(node_id_text).block(Block::default()); - f.render_widget(paragraph, details_chunks[1]); + f.render_widget(paragraph, details_chunks[2]); // Public Address let public_address_text = Spans::from(vec![ - Span::styled(ITEM_03, Style::default().fg(Color::Magenta)), + Span::styled(ITEM_04, Style::default().fg(Color::Magenta)), Span::styled( app_state.get_identity().network_address.clone(), Style::default().fg(Color::White), ), ]); let paragraph = Paragraph::new(public_address_text).block(Block::default()); - f.render_widget(paragraph, details_chunks[2]); + f.render_widget(paragraph, details_chunks[3]); // Emoji ID let emoji_id_text = Spans::from(vec![ - Span::styled(ITEM_04, Style::default().fg(Color::Magenta)), + Span::styled(ITEM_05, Style::default().fg(Color::Magenta)), Span::styled( - app_state.get_identity().emoji_id.clone(), + app_state.get_identity().tari_address_interactive.to_emoji_string(), Style::default().fg(Color::White), ), ]); let paragraph = Paragraph::new(emoji_id_text).block(Block::default()); - f.render_widget(paragraph, details_chunks[3]); + f.render_widget(paragraph, details_chunks[4]); + + let emoji_id_text = Spans::from(vec![ + Span::styled(ITEM_06, Style::default().fg(Color::Magenta)), + Span::styled( + app_state.get_identity().tari_address_one_sided.to_emoji_string(), + Style::default().fg(Color::White), + ), + ]); + let paragraph = Paragraph::new(emoji_id_text).block(Block::default()); + f.render_widget(paragraph, details_chunks[5]); } } diff --git a/applications/minotari_console_wallet/src/ui/components/send_tab.rs b/applications/minotari_console_wallet/src/ui/components/send_tab.rs index 884dff315a..8c5c5dee98 100644 --- a/applications/minotari_console_wallet/src/ui/components/send_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/send_tab.rs @@ -29,6 +29,7 @@ pub struct SendTab { send_input_mode: SendInputMode, show_contacts: bool, to_field: String, + payment_id_field: String, amount_field: String, fee_field: String, message_field: String, @@ -49,6 +50,7 @@ impl SendTab { send_input_mode: SendInputMode::None, show_contacts: false, to_field: String::new(), + payment_id_field: String::new(), amount_field: String::new(), fee_field: app_state.get_default_fee_per_gram().as_u64().to_string(), message_field: String::new(), @@ -96,23 +98,23 @@ impl SendTab { Span::raw(" field, "), Span::styled("A", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), - Span::styled("Amount/Token", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(", "), + Span::styled("Amount/Token, ", Style::default().add_modifier(Modifier::BOLD)), Span::styled("F", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to edit "), Span::styled("Fee-Per-Gram", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" field, "), Span::styled("C", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to select a contact."), + Span::raw(" to select a contact, "), + Span::styled("P", Style::default().add_modifier(Modifier::BOLD)), + Span::raw(" to edit "), + Span::styled("Payment-id", Style::default().add_modifier(Modifier::BOLD)), ]), Spans::from(vec![ Span::raw("Press "), Span::styled("S", Style::default().add_modifier(Modifier::BOLD)), Span::raw(" to send a normal transaction, "), Span::styled("O", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to send a one-sided transaction, "), - Span::styled("X", Style::default().add_modifier(Modifier::BOLD)), - Span::raw(" to send a one-sided transaction to a stealth address."), + Span::raw(" to send a one-sided transaction"), ]), ]) .wrap(Wrap { trim: false }) @@ -167,6 +169,14 @@ impl SendTab { .block(Block::default().borders(Borders::ALL).title("(M)essage:")); f.render_widget(message_input, vert_chunks[3]); + let payment_id_input = Paragraph::new(self.payment_id_field.as_ref()) + .style(match self.send_input_mode { + SendInputMode::PaymentId => Style::default().fg(Color::Magenta), + _ => Style::default(), + }) + .block(Block::default().borders(Borders::ALL).title("(P)ayment-id:")); + f.render_widget(payment_id_input, vert_chunks[4]); + match self.send_input_mode { SendInputMode::None => (), SendInputMode::To => f.set_cursor( @@ -197,6 +207,12 @@ impl SendTab { // Move one line down, from the border to the input line vert_chunks[3].y + 1, ), + SendInputMode::PaymentId => f.set_cursor( + // Put cursor past the end of the input text + vert_chunks[4].x + self.payment_id_field.width() as u16 + 1, + // Move one line down, from the border to the input line + vert_chunks[4].y + 1, + ), } } @@ -241,9 +257,7 @@ impl SendTab { } else if 'y' == c { match self.confirmation_dialog { None => (), - Some(ConfirmationDialogType::Normal) | - Some(ConfirmationDialogType::OneSided) | - Some(ConfirmationDialogType::StealthAddress) => { + Some(ConfirmationDialogType::Normal) | Some(ConfirmationDialogType::StealthAddress) => { if 'y' == c { let amount = if let Ok(v) = self.amount_field.parse::() { v @@ -268,24 +282,6 @@ impl SendTab { let mut reset_fields = false; match self.confirmation_dialog { - Some(ConfirmationDialogType::OneSided) => { - match Handle::current().block_on(app_state.send_one_sided_transaction( - self.to_field.clone(), - amount.into(), - UtxoSelectionCriteria::default(), - fee_per_gram, - self.message_field.clone(), - tx, - )) { - Err(e) => { - self.error_message = Some(format!( - "Error sending one-sided transaction:\n{}\nPress Enter to continue.", - e - )) - }, - Ok(_) => reset_fields = true, - } - }, Some(ConfirmationDialogType::StealthAddress) => { match Handle::current().block_on( app_state.send_one_sided_to_stealth_address_transaction( @@ -294,6 +290,7 @@ impl SendTab { UtxoSelectionCriteria::default(), fee_per_gram, self.message_field.clone(), + self.payment_id_field.clone(), tx, ), ) { @@ -332,6 +329,7 @@ impl SendTab { self.selected_unique_id = None; self.fee_field = app_state.get_default_fee_per_gram().as_u64().to_string(); self.message_field = "".to_string(); + self.payment_id_field = "".to_string(); self.send_input_mode = SendInputMode::None; self.send_result_watch = Some(rx); } @@ -386,12 +384,19 @@ impl SendTab { }, }, SendInputMode::Message => match c { - '\n' => self.send_input_mode = SendInputMode::None, + '\n' => self.send_input_mode = SendInputMode::PaymentId, c => { self.message_field.push(c); return KeyHandled::Handled; }, }, + SendInputMode::PaymentId => match c { + '\n' => self.send_input_mode = SendInputMode::None, + c => { + self.payment_id_field.push(c); + return KeyHandled::Handled; + }, + }, } } @@ -424,7 +429,7 @@ impl Component for SendTab { .constraints( [ Constraint::Length(3), - Constraint::Length(14), + Constraint::Length(17), Constraint::Min(42), Constraint::Length(1), ] @@ -505,24 +510,12 @@ impl Component for SendTab { 9, ); }, - Some(ConfirmationDialogType::OneSided) => { - draw_dialog( - f, - area, - "Confirm Sending Transaction".to_string(), - "Are you sure you want to send this one-sided transaction?\n(Y)es / (N)o".to_string(), - Color::Red, - 120, - 9, - ); - }, Some(ConfirmationDialogType::StealthAddress) => { draw_dialog( f, area, "Confirm Sending Transaction".to_string(), - "Are you sure you want to send this one-sided transaction to a stealth address?\n(Y)es / (N)o" - .to_string(), + "Are you sure you want to send this one-sided transaction?\n(Y)es / (N)o".to_string(), Color::Red, 120, 9, @@ -579,7 +572,8 @@ impl Component for SendTab { }, 'f' => self.send_input_mode = SendInputMode::Fee, 'm' => self.send_input_mode = SendInputMode::Message, - 's' | 'o' | 'x' => { + 'p' => self.send_input_mode = SendInputMode::PaymentId, + 's' | 'o' => { if self.to_field.is_empty() { self.error_message = Some("Destination Tari Address/Emoji ID\nPress Enter to continue.".to_string()); @@ -596,8 +590,7 @@ impl Component for SendTab { } self.confirmation_dialog = Some(match c { - 'o' => ConfirmationDialogType::OneSided, - 'x' => ConfirmationDialogType::StealthAddress, + 'o' => ConfirmationDialogType::StealthAddress, _ => ConfirmationDialogType::Normal, }); }, @@ -651,6 +644,9 @@ impl Component for SendTab { SendInputMode::Message => { let _ = self.message_field.pop(); }, + SendInputMode::PaymentId => { + let _ = self.payment_id_field.pop(); + }, SendInputMode::None => {}, } } @@ -663,11 +659,11 @@ pub enum SendInputMode { Amount, Message, Fee, + PaymentId, } #[derive(PartialEq, Debug)] pub enum ConfirmationDialogType { Normal, - OneSided, StealthAddress, } diff --git a/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs b/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs index 0fed16d84f..9679c1b83b 100644 --- a/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs +++ b/applications/minotari_console_wallet/src/ui/components/transactions_tab.rs @@ -7,6 +7,7 @@ use chrono::{DateTime, Local}; use log::*; use minotari_wallet::transaction_service::storage::models::TxCancellationReason; use tari_common_types::transaction::{TransactionDirection, TransactionStatus}; +use tari_core::transactions::transaction_components::encrypted_data::PaymentId; use tokio::runtime::Handle; use tui::{ backend::Backend, @@ -156,7 +157,7 @@ impl TransactionsTab { .highlight_style(styles::highlight()) .heading_style(styles::header_row()) .max_width(MAX_WIDTH) - .add_column(Some("Source/Destination address"), Some(69), column0_items) + .add_column(Some("Source/Destination address"), Some(95), column0_items) .add_column(Some("Amount/Token"), Some(18), column1_items) .add_column(Some("Mined At (Local)"), Some(20), column2_items) .add_column(Some("Message"), None, column3_items); @@ -264,7 +265,7 @@ impl TransactionsTab { .highlight_style(Style::default().add_modifier(Modifier::BOLD).fg(Color::Magenta)) .heading_style(Style::default().fg(Color::Magenta)) .max_width(MAX_WIDTH) - .add_column(Some("Source/Destination Address"), Some(69), column0_items) + .add_column(Some("Source/Destination Address"), Some(95), column0_items) .add_column(Some("Amount/Token"), Some(18), column1_items) .add_column(Some("Mined At (Local)"), Some(20), column2_items) .add_column(Some("Status"), None, column3_items); @@ -288,7 +289,7 @@ impl TransactionsTab { .split(area); // Labels - let constraints = [Constraint::Length(1); 14]; + let constraints = [Constraint::Length(1); 15]; let label_layout = Layout::default().constraints(constraints).split(columns[0]); let tx_id = Span::styled("TxID:", Style::default().fg(Color::Magenta)); @@ -305,6 +306,7 @@ impl TransactionsTab { let confirmations = Span::styled("Confirmations:", Style::default().fg(Color::Magenta)); let mined_height = Span::styled("Mined Height:", Style::default().fg(Color::Magenta)); let maturity = Span::styled("Maturity:", Style::default().fg(Color::Magenta)); + let payment_id = Span::styled("Payment Id:", Style::default().fg(Color::Magenta)); let trim = Wrap { trim: true }; let paragraph = Paragraph::new(tx_id).wrap(trim); @@ -335,11 +337,13 @@ impl TransactionsTab { f.render_widget(paragraph, label_layout[12]); let paragraph = Paragraph::new(maturity).wrap(trim); f.render_widget(paragraph, label_layout[13]); + let paragraph = Paragraph::new(payment_id).wrap(trim); + f.render_widget(paragraph, label_layout[14]); // Content let required_confirmations = app_state.get_required_confirmations(); if let Some(tx) = self.detailed_transaction.as_ref() { - let constraints = [Constraint::Length(1); 14]; + let constraints = [Constraint::Length(1); 15]; let content_layout = Layout::default().constraints(constraints).split(columns[1]); let tx_id = Span::styled(format!("{}", tx.tx_id), Style::default().fg(Color::White)); @@ -429,6 +433,20 @@ impl TransactionsTab { }; let maturity = Span::styled(maturity, Style::default().fg(Color::White)); + let payment_id = match tx.payment_id.clone() { + Some(v) => { + if let PaymentId::Open(bytes) = v { + String::from_utf8(bytes) + .unwrap_or_else(|_| "Invalid".to_string()) + .to_string() + } else { + format!("#{}", v) + } + }, + None => "None".to_string(), + }; + let payment_id = Span::styled(payment_id, Style::default().fg(Color::White)); + let paragraph = Paragraph::new(tx_id).wrap(trim); f.render_widget(paragraph, content_layout[0]); let paragraph = Paragraph::new(source_address).wrap(trim); @@ -457,6 +475,8 @@ impl TransactionsTab { f.render_widget(paragraph, content_layout[12]); let paragraph = Paragraph::new(maturity).wrap(trim); f.render_widget(paragraph, content_layout[13]); + let paragraph = Paragraph::new(payment_id).wrap(trim); + f.render_widget(paragraph, content_layout[14]); } } } @@ -469,7 +489,7 @@ impl Component for TransactionsTab { Constraint::Length(3), Constraint::Length(1), Constraint::Min(9), - Constraint::Length(16), + Constraint::Length(17), ] .as_ref(), ) diff --git a/applications/minotari_console_wallet/src/ui/mod.rs b/applications/minotari_console_wallet/src/ui/mod.rs index 022e532820..e82e9dd422 100644 --- a/applications/minotari_console_wallet/src/ui/mod.rs +++ b/applications/minotari_console_wallet/src/ui/mod.rs @@ -48,7 +48,7 @@ use ui_error::UiError; use crate::utils::events::{Event, EventStream}; -pub const MAX_WIDTH: u16 = 133; +pub const MAX_WIDTH: u16 = 167; pub fn run(app: App>) -> Result<(), ExitError> { let mut app = app; diff --git a/applications/minotari_console_wallet/src/ui/state/app_state.rs b/applications/minotari_console_wallet/src/ui/state/app_state.rs index 1ad02af691..8c7e0cdbd5 100644 --- a/applications/minotari_console_wallet/src/ui/state/app_state.rs +++ b/applications/minotari_console_wallet/src/ui/state/app_state.rs @@ -23,6 +23,7 @@ use std::{ collections::{HashMap, VecDeque}, path::PathBuf, + str::FromStr, sync::Arc, time::{Duration, Instant}, }; @@ -57,12 +58,12 @@ use tari_comms::{ use tari_contacts::contacts_service::{handle::ContactsLivenessEvent, types::Contact}; use tari_core::transactions::{ tari_amount::{uT, MicroMinotari}, - transaction_components::{OutputFeatures, TemplateType, TransactionError}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, TemplateType, TransactionError}, weight::TransactionWeight, }; use tari_crypto::ristretto::RistrettoSecretKey; use tari_shutdown::ShutdownSignal; -use tari_utilities::hex::{from_hex, Hex}; +use tari_utilities::hex::Hex; use tokio::{ sync::{broadcast, watch, RwLock}, task, @@ -74,12 +75,7 @@ use crate::{ ui::{ state::{ debouncer::BalanceEnquiryDebouncer, - tasks::{ - send_burn_transaction_task, - send_one_sided_transaction_task, - send_register_template_transaction_task, - send_transaction_task, - }, + tasks::{send_burn_transaction_task, send_register_template_transaction_task, send_transaction_task}, wallet_event_monitor::WalletEventMonitor, }, ui_burnt_proof::UiBurntProof, @@ -228,11 +224,7 @@ impl AppState { pub async fn upsert_contact(&mut self, alias: String, tari_emoji: String) -> Result<(), UiError> { let mut inner = self.inner.write().await; - let address = match TariAddress::from_emoji_string(&tari_emoji) { - Ok(address) => address, - Err(_) => TariAddress::from_bytes(&from_hex(&tari_emoji).map_err(|_| UiError::PublicKeyParseError)?) - .map_err(|_| UiError::PublicKeyParseError)?, - }; + let address = TariAddress::from_str(&tari_emoji).map_err(|_| UiError::PublicKeyParseError)?; let contact = Contact::new(alias, address, None, None, false); inner.wallet.contacts_service.upsert_contact(contact).await?; @@ -245,26 +237,22 @@ impl AppState { // Return alias or pub key if the contact is not in the list. pub fn get_alias(&self, address: &TariAddress) -> String { - let address_hex = address.to_hex(); + let address_string = address.to_base58(); match self .cached_data .contacts .iter() - .find(|&contact| contact.address.eq(&address_hex)) + .find(|&contact| contact.address.eq(&address_string)) { Some(contact) => contact.alias.clone(), - None => address_hex, + None => address_string, } } pub async fn delete_contact(&mut self, tari_emoji: String) -> Result<(), UiError> { let mut inner = self.inner.write().await; - let address = match TariAddress::from_emoji_string(&tari_emoji) { - Ok(address) => address, - Err(_) => TariAddress::from_bytes(&from_hex(&tari_emoji).map_err(|_| UiError::PublicKeyParseError)?) - .map_err(|_| UiError::PublicKeyParseError)?, - }; + let address = TariAddress::from_str(&tari_emoji).map_err(|_| UiError::PublicKeyParseError)?; inner.wallet.contacts_service.remove_contact(address).await?; @@ -300,11 +288,7 @@ impl AppState { result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; - let address = match TariAddress::from_emoji_string(&address) { - Ok(address) => address, - Err(_) => TariAddress::from_bytes(&from_hex(&address).map_err(|_| UiError::PublicKeyParseError)?) - .map_err(|_| UiError::PublicKeyParseError)?, - }; + let address = TariAddress::from_str(&address).map_err(|_| UiError::PublicKeyParseError)?; let output_features = OutputFeatures { ..Default::default() }; @@ -324,39 +308,6 @@ impl AppState { Ok(()) } - pub async fn send_one_sided_transaction( - &mut self, - address: String, - amount: u64, - selection_criteria: UtxoSelectionCriteria, - fee_per_gram: u64, - message: String, - result_tx: watch::Sender, - ) -> Result<(), UiError> { - let inner = self.inner.write().await; - let address = match TariAddress::from_emoji_string(&address) { - Ok(address) => address, - Err(_) => TariAddress::from_bytes(&from_hex(&address).map_err(|_| UiError::PublicKeyParseError)?) - .map_err(|_| UiError::PublicKeyParseError)?, - }; - let output_features = OutputFeatures { ..Default::default() }; - - let fee_per_gram = fee_per_gram * uT; - let tx_service_handle = inner.wallet.transaction_service.clone(); - tokio::spawn(send_one_sided_transaction_task( - address, - MicroMinotari::from(amount), - selection_criteria, - output_features, - message, - fee_per_gram, - tx_service_handle, - result_tx, - )); - - Ok(()) - } - pub async fn send_one_sided_to_stealth_address_transaction( &mut self, address: String, @@ -364,13 +315,18 @@ impl AppState { selection_criteria: UtxoSelectionCriteria, fee_per_gram: u64, message: String, + payment_id_str: String, result_tx: watch::Sender, ) -> Result<(), UiError> { let inner = self.inner.write().await; - let address = match TariAddress::from_emoji_string(&address) { - Ok(address) => address, - Err(_) => TariAddress::from_bytes(&from_hex(&address).map_err(|_| UiError::PublicKeyParseError)?) - .map_err(|_| UiError::PublicKeyParseError)?, + let address = TariAddress::from_str(&address).map_err(|_| UiError::PublicKeyParseError)?; + let payment_id = if payment_id_str.is_empty() { + PaymentId::Empty + } else { + let payment_id_u64: u64 = payment_id_str + .parse::() + .map_err(|_| UiError::HexError("Could not convert payment_id to bytes".to_string()))?; + PaymentId::U64(payment_id_u64) }; let output_features = OutputFeatures { ..Default::default() }; @@ -384,6 +340,7 @@ impl AppState { output_features, message, fee_per_gram, + payment_id, tx_service_handle, result_tx, )); @@ -946,12 +903,11 @@ impl AppStateInner { } pub async fn refresh_network_id(&mut self) -> Result<(), UiError> { - let wallet_id = WalletIdentity::new(self.wallet.comms.node_identity(), self.wallet.network.as_network()); - let eid = wallet_id.address.to_emoji_string(); + let wallet_id = self.wallet.get_wallet_id().await?; let qr_link = format!( "tari://{}/transactions/send?tariAddress={}", - wallet_id.network, - wallet_id.address.to_hex() + wallet_id.network(), + wallet_id.address_interactive.to_base58() ); let code = QrCode::new(qr_link).unwrap(); let image = code @@ -963,7 +919,8 @@ impl AppStateInner { .skip(1) .fold("".to_string(), |acc, l| format!("{}{}\n", acc, l)); let identity = MyIdentity { - tari_address: wallet_id.address.to_hex(), + tari_address_interactive: wallet_id.address_interactive.clone(), + tari_address_one_sided: wallet_id.address_one_sided.clone(), network_address: wallet_id .node_identity .public_addresses() @@ -971,7 +928,6 @@ impl AppStateInner { .map(|a| a.to_string()) .collect::>() .join(", "), - emoji_id: eid, qr_code: image, node_id: wallet_id.node_identity.node_id().to_string(), }; @@ -1210,6 +1166,7 @@ pub struct CompletedTransactionInfo { pub weight: u64, pub inputs_count: usize, pub outputs_count: usize, + pub payment_id: Option, } impl CompletedTransactionInfo { @@ -1250,6 +1207,7 @@ impl CompletedTransactionInfo { weight, inputs_count, outputs_count, + payment_id: tx.payment_id, }) } } @@ -1282,11 +1240,10 @@ pub struct EventListItem { impl AppStateData { pub fn new(wallet_identity: &WalletIdentity, base_node_selected: Peer, base_node_config: PeerConfig) -> Self { - let eid = wallet_identity.address.to_emoji_string(); let qr_link = format!( "tari://{}/transactions/send?tariAddress={}", - wallet_identity.network, - wallet_identity.address.to_hex() + wallet_identity.network(), + wallet_identity.address_interactive.to_base58() ); let code = QrCode::new(qr_link).unwrap(); let image = code @@ -1299,7 +1256,8 @@ impl AppStateData { .fold("".to_string(), |acc, l| format!("{}{}\n", acc, l)); let identity = MyIdentity { - tari_address: wallet_identity.address.to_hex(), + tari_address_interactive: wallet_identity.address_interactive.clone(), + tari_address_one_sided: wallet_identity.address_one_sided.clone(), network_address: wallet_identity .node_identity .public_addresses() @@ -1307,7 +1265,6 @@ impl AppStateData { .map(|a| a.to_string()) .collect::>() .join(", "), - emoji_id: eid, qr_code: image, node_id: wallet_identity.node_identity.node_id().to_string(), }; @@ -1357,9 +1314,9 @@ impl AppStateData { #[derive(Clone)] pub struct MyIdentity { - pub tari_address: String, + pub tari_address_interactive: TariAddress, + pub tari_address_one_sided: TariAddress, pub network_address: String, - pub emoji_id: String, pub qr_code: String, pub node_id: String, } diff --git a/applications/minotari_console_wallet/src/ui/state/tasks.rs b/applications/minotari_console_wallet/src/ui/state/tasks.rs index 6160d0e51f..f2c22dd37d 100644 --- a/applications/minotari_console_wallet/src/ui/state/tasks.rs +++ b/applications/minotari_console_wallet/src/ui/state/tasks.rs @@ -37,7 +37,7 @@ use tari_core::{ consensus::{MaxSizeBytes, MaxSizeString}, transactions::{ tari_amount::MicroMinotari, - transaction_components::{BuildInfo, OutputFeatures, TemplateType}, + transaction_components::{encrypted_data::PaymentId, BuildInfo, OutputFeatures, TemplateType}, }, }; use tari_crypto::ristretto::RistrettoSecretKey; @@ -127,60 +127,6 @@ pub async fn send_transaction_task( } } -pub async fn send_one_sided_transaction_task( - address: TariAddress, - amount: MicroMinotari, - selection_criteria: UtxoSelectionCriteria, - output_features: OutputFeatures, - message: String, - fee_per_gram: MicroMinotari, - mut transaction_service_handle: TransactionServiceHandle, - result_tx: watch::Sender, -) { - let _result = result_tx.send(UiTransactionSendStatus::Initiated); - let mut event_stream = transaction_service_handle.get_event_stream(); - match transaction_service_handle - .send_one_sided_transaction( - address, - amount, - selection_criteria, - output_features, - fee_per_gram, - message, - ) - .await - { - Err(e) => { - let _result = result_tx.send(UiTransactionSendStatus::Error(UiError::from(e).to_string())); - }, - Ok(our_tx_id) => { - loop { - match event_stream.recv().await { - Ok(event) => { - if let TransactionEvent::TransactionCompletedImmediately(tx_id) = &*event { - if our_tx_id == *tx_id { - let _result = result_tx.send(UiTransactionSendStatus::TransactionComplete); - return; - } - } - }, - Err(e @ broadcast::error::RecvError::Lagged(_)) => { - log::warn!(target: LOG_TARGET, "Error reading from event broadcast channel {:?}", e); - continue; - }, - Err(broadcast::error::RecvError::Closed) => { - break; - }, - } - } - - let _result = result_tx.send(UiTransactionSendStatus::Error( - "One-sided transaction could not be sent".to_string(), - )); - }, - } -} - pub async fn send_one_sided_to_stealth_address_transaction( address: TariAddress, amount: MicroMinotari, @@ -188,6 +134,7 @@ pub async fn send_one_sided_to_stealth_address_transaction( output_features: OutputFeatures, message: String, fee_per_gram: MicroMinotari, + payment_id: PaymentId, mut transaction_service_handle: TransactionServiceHandle, result_tx: watch::Sender, ) { @@ -201,6 +148,7 @@ pub async fn send_one_sided_to_stealth_address_transaction( output_features, fee_per_gram, message, + payment_id, ) .await { @@ -473,7 +421,7 @@ pub async fn send_register_template_transaction_task( ) .await; - let sent_tx_id = match result { + let (sent_tx_id, _template_addr) = match result { Ok(tx_id) => tx_id, Err(e) => { error!(target: LOG_TARGET, "failed to register code template: {:?}", e); @@ -493,7 +441,7 @@ pub async fn send_register_template_transaction_task( match event_stream.recv().await { Ok(event) => { if let TransactionEvent::TransactionCompletedImmediately(completed_tx_id) = &*event { - if sent_tx_id.0 == *completed_tx_id { + if sent_tx_id == *completed_tx_id { result_tx.send(UiTransactionSendStatus::TransactionComplete).unwrap(); return; } diff --git a/applications/minotari_console_wallet/src/ui/state/wallet_event_monitor.rs b/applications/minotari_console_wallet/src/ui/state/wallet_event_monitor.rs index 768d9c3b17..91cdca75a8 100644 --- a/applications/minotari_console_wallet/src/ui/state/wallet_event_monitor.rs +++ b/applications/minotari_console_wallet/src/ui/state/wallet_event_monitor.rs @@ -200,7 +200,7 @@ impl WalletEventMonitor { ); match msg { ConnectivityEvent::PeerConnected(_) | - ConnectivityEvent::PeerDisconnected(_) => { + ConnectivityEvent::PeerDisconnected(..) => { self.trigger_peer_state_refresh().await; }, // Only the above variants trigger state refresh diff --git a/applications/minotari_console_wallet/src/ui/ui_contact.rs b/applications/minotari_console_wallet/src/ui/ui_contact.rs index 9851816f8b..86d8b18b90 100644 --- a/applications/minotari_console_wallet/src/ui/ui_contact.rs +++ b/applications/minotari_console_wallet/src/ui/ui_contact.rs @@ -44,7 +44,7 @@ impl From for UiContact { fn from(c: Contact) -> Self { Self { alias: c.alias, - address: c.address.to_hex(), + address: c.address.to_base58(), emoji_id: c.address.to_emoji_string(), last_seen: match c.last_seen { Some(val) => DateTime::::from_naive_utc_and_offset(val, Local::now().offset().to_owned()) diff --git a/applications/minotari_console_wallet/src/utils/formatting.rs b/applications/minotari_console_wallet/src/utils/formatting.rs index 1134aca756..5978365d48 100644 --- a/applications/minotari_console_wallet/src/utils/formatting.rs +++ b/applications/minotari_console_wallet/src/utils/formatting.rs @@ -60,10 +60,10 @@ mod test { assert_eq!(display_compressed_string(short_str.clone(), 5, 5), short_str); let long_str = "abcdefghijklmnopqrstuvwxyz".to_string(); assert_eq!(display_compressed_string(long_str, 3, 3), "abc..xyz".to_string()); - let emoji_str = "🐾💎🎤🎨📌🍄🎰🍉🚧💉💡👟🚒📌🔌🐶🐾🐢🔭🐨😻💨🐎🐊🚢👟🚧🐞🚜🌂🎩🎱📈".to_string(); + let emoji_str = "🐾💎🎤🎨📌🍄🎰🍉🚧💉💡👟🚒📌🔌🐶🐾🐢🔭🐨👍💨🦁🐊🚢👟🚧🐞🚜📟🎩🎱📈".to_string(); assert_eq!( display_compressed_string(emoji_str, 3, 6), - "🐾💎🎤..🐞🚜🌂🎩🎱📈".to_string() + "🐾💎🎤..🐞🚜📟🎩🎱📈".to_string() ); } } diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index 70fe202652..8fd7705f2c 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -144,14 +144,12 @@ pub(crate) fn command_mode( wallet: WalletSqlite, command: CliCommands, ) -> Result<(), ExitError> { - let commands = vec![command]; - // Do not remove this println! const CUCUMBER_TEST_MARKER_A: &str = "Minotari Console Wallet running... (Command mode started)"; println!("{}", CUCUMBER_TEST_MARKER_A); info!(target: LOG_TARGET, "Starting wallet command mode"); - handle.block_on(command_runner(config, commands, wallet.clone()))?; + handle.block_on(command_runner(config, vec![command.clone()], wallet.clone()))?; // Do not remove this println! const CUCUMBER_TEST_MARKER_B: &str = "Minotari Console Wallet running... (Command mode completed)"; @@ -159,7 +157,25 @@ pub(crate) fn command_mode( info!(target: LOG_TARGET, "Completed wallet command mode"); - wallet_or_exit(handle, cli, config, base_node_config, wallet) + wallet_or_exit( + handle, + cli, + config, + base_node_config, + wallet, + force_exit_for_faucet_commands(&command), + ) +} + +fn force_exit_for_faucet_commands(command: &CliCommands) -> bool { + matches!( + command, + CliCommands::FaucetGenerateSessionInfo(_) | + CliCommands::FaucetEncumberAggregateUtxo(_) | + CliCommands::FaucetSpendAggregateUtxo(_) | + CliCommands::FaucetCreatePartyDetails(_) | + CliCommands::FaucetCreateInputOutputSigs(_) + ) } pub(crate) fn parse_command_file(script: String) -> Result, ExitError> { @@ -206,22 +222,33 @@ pub(crate) fn script_mode( println!("Parsing commands..."); let commands = parse_command_file(script)?; - println!("{} commands parsed successfully.", commands.len()); + let mut exit_wallet = false; + for command in &commands { + if force_exit_for_faucet_commands(command) { + println!("Faucet commands may not run in script mode!"); + exit_wallet = true; + break; + } + } - // Do not remove this println! - const CUCUMBER_TEST_MARKER_A: &str = "Minotari Console Wallet running... (Script mode started)"; - println!("{}", CUCUMBER_TEST_MARKER_A); + if !exit_wallet { + println!("{} commands parsed successfully.", commands.len()); - println!("Starting the command runner!"); - handle.block_on(command_runner(config, commands, wallet.clone()))?; + // Do not remove this println! + const CUCUMBER_TEST_MARKER_A: &str = "Minotari Console Wallet running... (Script mode started)"; + println!("{}", CUCUMBER_TEST_MARKER_A); - // Do not remove this println! - const CUCUMBER_TEST_MARKER_B: &str = "Minotari Console Wallet running... (Script mode completed)"; - println!("{}", CUCUMBER_TEST_MARKER_B); + println!("Starting the command runner!"); + handle.block_on(command_runner(config, commands, wallet.clone()))?; - info!(target: LOG_TARGET, "Completed wallet script mode"); + // Do not remove this println! + const CUCUMBER_TEST_MARKER_B: &str = "Minotari Console Wallet running... (Script mode completed)"; + println!("{}", CUCUMBER_TEST_MARKER_B); - wallet_or_exit(handle, cli, config, base_node_config, wallet) + info!(target: LOG_TARGET, "Completed wallet script mode"); + } + + wallet_or_exit(handle, cli, config, base_node_config, wallet, exit_wallet) } /// Prompts the user to continue to the wallet, or exit. @@ -231,11 +258,16 @@ fn wallet_or_exit( config: &WalletConfig, base_node_config: &PeerConfig, wallet: WalletSqlite, + force_exit: bool, ) -> Result<(), ExitError> { if cli.command_mode_auto_exit { info!(target: LOG_TARGET, "Auto exit argument supplied - exiting."); return Ok(()); } + if force_exit { + info!(target: LOG_TARGET, "Forced exit argument supplied by process - exiting."); + return Ok(()); + } if cli.non_interactive_mode { info!(target: LOG_TARGET, "Starting GRPC server."); @@ -322,14 +354,14 @@ pub fn tui_mode( return Err(ExitError::new(ExitCode::WalletError, "Could not select a base node")); } - let app = App::>::new( + let app = handle.block_on(App::>::new( "Minotari Wallet".into(), wallet, config.clone(), base_node_selected, base_node_config.clone(), notifier, - ); + ))?; info!(target: LOG_TARGET, "Starting app"); @@ -480,8 +512,10 @@ mod test { use crate::{cli::CliCommands, wallet_modes::parse_command_file}; #[test] + #[allow(clippy::too_many_lines)] fn clap_parses_user_defined_commands_as_expected() { - let script = " + let script = + " # Beginning of script file get-balance @@ -490,15 +524,32 @@ mod test { discover-peer f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 - send-minotari --message Our_secret! 125T 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d615e + send-minotari --message Our_secret! 125T \ + f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb burn-minotari --message Ups_these_funds_will_be_burned! 100T + faucet-generate-session-info --fee-per-gram 2 --commitment \ + f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 --output-hash \ + f6b2ca781342a3ebe30ee1643655c96f1d7c14f4d49f077695395de98ae73665 --recipient-address \ + f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA \ + --verify-unspent-outputs + + faucet-create-party-details --input-file ./step_1_session_info.txt --alias alice + + faucet-encumber-aggregate-utxo --session-id ee1643655c --input-file-names=step_2_for_leader_from_alice.txt \ + --input-file-names=step_2_for_leader_from_bob.txt --input-file-names=step_2_for_leader_from_carol.txt + + faucet-create-input-output-sigs --session-id ee1643655c + + faucet-spend-aggregate-utxo --session-id ee1643655c --input-file-names=step_4_for_leader_from_alice.txt \ + --input-file-names=step_4_for_leader_from_bob.txt --input-file-names=step_4_for_leader_from_carol.txt + coin-split --message Make_many_dust_UTXOs! --fee-per-gram 2 0.001T 499 make-it-rain --duration 100 --transactions-per-second 10 --start-amount 0.009200T --increase-amount 0T \ - --start-time now --message Stressing_it_a_bit...!_(from_Feeling-a-bit-Generous) \ - 5c4f2a4b3f3f84e047333218a84fd24f581a9d7e4f23b78e3714e9d174427d615e + --start-time now --message Stressing_it_a_bit...!_(from_Feeling-a-bit-Generous) \ + f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb export-tx 123456789 --output-file pie.txt @@ -506,13 +557,18 @@ mod test { # End of script file " - .to_string(); + .to_string(); let commands = parse_command_file(script).unwrap(); let mut get_balance = false; let mut send_tari = false; let mut burn_tari = false; + let mut faucet_generate_session_info = false; + let mut faucet_encumber_aggregate_utxo = false; + let mut faucet_spend_aggregate_utxo = false; + let mut faucet_create_party_details = false; + let mut faucet_create_input_output_sigs = false; let mut make_it_rain = false; let mut coin_split = false; let mut discover_peer = false; @@ -524,7 +580,11 @@ mod test { CliCommands::GetBalance => get_balance = true, CliCommands::SendMinotari(_) => send_tari = true, CliCommands::BurnMinotari(_) => burn_tari = true, - CliCommands::SendOneSided(_) => {}, + CliCommands::FaucetGenerateSessionInfo(_) => faucet_generate_session_info = true, + CliCommands::FaucetEncumberAggregateUtxo(_) => faucet_encumber_aggregate_utxo = true, + CliCommands::FaucetSpendAggregateUtxo(_) => faucet_spend_aggregate_utxo = true, + CliCommands::FaucetCreatePartyDetails(_) => faucet_create_party_details = true, + CliCommands::FaucetCreateInputOutputSigs(_) => faucet_create_input_output_sigs = true, CliCommands::SendOneSidedToStealthAddress(_) => {}, CliCommands::MakeItRain(_) => make_it_rain = true, CliCommands::CoinSplit(_) => coin_split = true, @@ -558,6 +618,11 @@ mod test { get_balance && send_tari && burn_tari && + faucet_generate_session_info && + faucet_encumber_aggregate_utxo && + faucet_spend_aggregate_utxo && + faucet_create_party_details && + faucet_create_input_output_sigs && make_it_rain && coin_split && discover_peer && diff --git a/applications/minotari_ledger_wallet/Cargo.lock b/applications/minotari_ledger_wallet/Cargo.lock deleted file mode 100644 index 17fa7e0271..0000000000 --- a/applications/minotari_ledger_wallet/Cargo.lock +++ /dev/null @@ -1,467 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "borsh" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6cb63579996213e822f6d828b0a47e1d23b1e8708f52d18a6b1af5670dd207" -dependencies = [ - "cfg_aliases", -] - -[[package]] -name = "cc" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" -dependencies = [ - "libc", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "cpufeatures" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" -dependencies = [ - "libc", -] - -[[package]] -name = "critical-section" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - -[[package]] -name = "embedded-alloc" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8931e47e33c5d3194fbcf9cc82df0919193bd2fa40008f388eb1d28fd9c9ea6b" -dependencies = [ - "critical-section", - "linked_list_allocator", -] - -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gif" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "include_gif" -version = "0.1.0" -source = "git+https://github.com/LedgerHQ/sdk_include_gif#699d28c6157518c4493899e2eeaa8af08346e5e7" -dependencies = [ - "gif", - "syn 1.0.109", -] - -[[package]] -name = "keccak" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "linked_list_allocator" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" - -[[package]] -name = "log" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" - -[[package]] -name = "minotari_ledger_wallet" -version = "0.52.0-pre.0" -dependencies = [ - "blake2", - "borsh", - "critical-section", - "digest", - "embedded-alloc", - "nanos_sdk", - "nanos_ui", - "tari_crypto", -] - -[[package]] -name = "nanos_sdk" -version = "0.2.1" -source = "git+https://github.com/LedgerHQ/ledger-nanos-sdk.git#4d9bfc6183d94cee6edb239c39286be3825cc179" -dependencies = [ - "cc", - "num-traits", - "rand_core", -] - -[[package]] -name = "nanos_ui" -version = "0.2.0" -source = "git+https://github.com/LedgerHQ/ledger-nanos-ui.git?rev=6a7c4a3eb41ee0b09c8fd4dcc5be4f3a1f5d7b45#6a7c4a3eb41ee0b09c8fd4dcc5be4f3a1f5d7b45" -dependencies = [ - "include_gif", - "nanos_sdk", -] - -[[package]] -name = "num-traits" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -dependencies = [ - "atomic-polyfill", - "critical-section", -] - -[[package]] -name = "platforms" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "snafu" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" -dependencies = [ - "doc-comment", - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tari-curve25519-dalek" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b8e2644aae57a832e475ebc31199ab1114ebd7fe4d2621e67e89bdd9c8ac38" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "fiat-crypto", - "platforms", - "rand_core", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "tari_crypto" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc09581fc1a9709e54be25e0a50437dc405370b3f5795ee65dc913f4f7e726e5" -dependencies = [ - "blake2", - "digest", - "log", - "once_cell", - "rand_chacha", - "rand_core", - "sha3", - "snafu", - "tari-curve25519-dalek", - "tari_utilities", - "zeroize", -] - -[[package]] -name = "tari_utilities" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367d17d09cf48e4cf45222fd48536e206f8ef3aaa5eed449c7df38d2ab4586c6" -dependencies = [ - "generic-array", - "snafu", - "zeroize", -] - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "unicode-ident" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - -[[package]] -name = "zeroize" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] diff --git a/applications/minotari_ledger_wallet/Cargo.toml b/applications/minotari_ledger_wallet/Cargo.toml deleted file mode 100644 index 88e5b3146b..0000000000 --- a/applications/minotari_ledger_wallet/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "minotari_ledger_wallet" -version.workspace = true -authors = ["The Tari Development Community"] -license = "BSD-3-Clause" -edition.workspace = true - - -[dependencies] -# lock to rev as soon as this is fixed: https://github.com/rust-lang/rust/issues/98666 -nanos_sdk = { git = "https://github.com/LedgerHQ/ledger-nanos-sdk.git" } -nanos_ui = { git = "https://github.com/LedgerHQ/ledger-nanos-ui.git", rev = "6a7c4a3eb41ee0b09c8fd4dcc5be4f3a1f5d7b45" } - -tari_crypto = { workspace = true, default-features = false } - -embedded-alloc = "0.5.0" -critical-section = { version = "1.1.1" } -digest = { version = "0.10", default-features = false } -borsh = { version = "1.0", default-features = false } -blake2 = { version = "0.10", default-features = false } - -[profile.release] -opt-level = 's' -lto = "fat" # same as `true` -panic = "abort" - -[package.metadata.nanos] -name = "MinoTari Wallet" -curve = ["secp256k1", "ed25519"] -flags = "0" -icon = "key_16x16.gif" -icon_small = "key_14x14.gif" -path = ["44'/1022'", "m/5261654'", "m/44'"] -api_level = "1" - -[workspace] diff --git a/applications/minotari_ledger_wallet/app_nanosplus.json b/applications/minotari_ledger_wallet/app_nanosplus.json deleted file mode 100644 index e1545ac66b..0000000000 --- a/applications/minotari_ledger_wallet/app_nanosplus.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "apiLevel": "1", - "binary": "target/nanosplus/release/app.hex", - "dataSize": 0, - "derivationPath": { - "curves": [ - "secp256k1", - "ed25519" - ], - "paths": [ - "44'/1022'", - "m/5261654'", - "m/44'" - ] - }, - "flags": "0", - "icon": "key_14x14.gif", - "name": "MinoTari Wallet", - "targetId": "0x33100004", - "version": "0.52.0-pre.0" -} \ No newline at end of file diff --git a/applications/minotari_ledger_wallet/comms/Cargo.toml b/applications/minotari_ledger_wallet/comms/Cargo.toml new file mode 100644 index 0000000000..6493648d74 --- /dev/null +++ b/applications/minotari_ledger_wallet/comms/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "minotari_ledger_wallet_comms" +version = "1.0.0-pre.16" +authors = ["The Tari Development Community"] +license = "BSD-3-Clause" +edition = "2021" + +[dependencies] +tari_crypto = { workspace = true } +tari_common_types = { workspace = true } +ledger-transport = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20" } +ledger-transport-hid = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20" } +num-derive = "0.4.2" +num-traits = "0.2.15" +serde = { version = "1.0.106", features = ["derive"] } +thiserror = "1.0.26" \ No newline at end of file diff --git a/base_layer/core/src/common/limited_reader.rs b/applications/minotari_ledger_wallet/comms/src/error.rs similarity index 50% rename from base_layer/core/src/common/limited_reader.rs rename to applications/minotari_ledger_wallet/comms/src/error.rs index a5c21fd76e..c9afc2e03f 100644 --- a/base_layer/core/src/common/limited_reader.rs +++ b/applications/minotari_ledger_wallet/comms/src/error.rs @@ -1,4 +1,4 @@ -// Copyright 2022, The Tari Project +// Copyright 2024 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,56 +20,38 @@ // 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::{io, io::Read}; - -pub struct LimitedBytesReader { - byte_limit: usize, - num_read: usize, - inner: R, +use serde::{Deserialize, Serialize}; +use tari_crypto::tari_utilities::ByteArrayError; +use thiserror::Error; + +/// Ledger device errors. +#[derive(Debug, Error, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub enum LedgerDeviceError { + /// HID API error + #[error("HID API error `{0}`")] + HidApi(String), + /// Native HID transport error + #[error("Native HID transport error `{0}`")] + NativeTransport(String), + /// Ledger application not started + #[error("Ledger application not started")] + ApplicationNotStarted, + /// Ledger application instruction error + #[error("Ledger application instruction error `{0}`")] + Instruction(String), + /// Ledger application processing error + #[error("Processing error `{0}`")] + Processing(String), + /// Conversion error to or from ledger + #[error("Conversion failed: {0}")] + ByteArrayError(String), + /// Not yet supported + #[error("Ledger is not fully supported")] + NotSupported, } -impl Read for LimitedBytesReader { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - let read = self.inner.read(buf)?; - self.num_read += read; - if self.num_read > self.byte_limit { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - format!("Read more bytes than the maximum ({})", self.byte_limit), - )); - } - Ok(read) - } -} - -#[cfg(test)] -mod test { - use std::io::Read; - - use super::*; - - impl LimitedBytesReader { - pub fn new(byte_limit: usize, reader: R) -> Self { - Self { - byte_limit, - num_read: 0, - inner: reader, - } - } - } - - #[test] - fn read_test() { - // read should work fine in the case of a buffer whose length is within byte_limit - let inner: &[u8] = &[0u8, 1u8, 2u8, 3u8, 4u8]; - let mut reader = LimitedBytesReader::new(3, inner); - let mut buf = [0u8; 3]; - let output = reader.read(&mut buf).unwrap(); - assert_eq!(output, buf.len()); - - // in case of buffer with length strictly bigger than reader byte_limit, the code should throw an error - let mut new_buf = [0u8; 4]; - let output = reader.read(&mut new_buf); - assert!(output.is_err()); +impl From for LedgerDeviceError { + fn from(e: ByteArrayError) -> Self { + LedgerDeviceError::ByteArrayError(e.to_string()) } } diff --git a/applications/minotari_ledger_wallet/comms/src/ledger_wallet.rs b/applications/minotari_ledger_wallet/comms/src/ledger_wallet.rs new file mode 100644 index 0000000000..f85dfc4fb5 --- /dev/null +++ b/applications/minotari_ledger_wallet/comms/src/ledger_wallet.rs @@ -0,0 +1,140 @@ +// Copyright 2024 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::ops::Deref; + +use ledger_transport::{APDUAnswer, APDUCommand}; +use ledger_transport_hid::{hidapi::HidApi, TransportNativeHID}; +use num_derive::FromPrimitive; +use num_traits::FromPrimitive; +use tari_common_types::wallet_types::LedgerWallet; + +use crate::error::LedgerDeviceError; + +#[repr(u8)] +#[derive(FromPrimitive, Debug, Copy, Clone, PartialEq)] +pub enum Instruction { + GetVersion = 0x01, + GetAppName = 0x02, + GetPublicAlpha = 0x03, + GetPublicKey = 0x04, + GetScriptSignature = 0x05, + GetScriptOffset = 0x06, + GetMetadataSignature = 0x07, + GetScriptSignatureFromChallenge = 0x08, + GetViewKey = 0x09, + GetDHSharedSecret = 0x10, +} + +impl Instruction { + pub fn as_byte(self) -> u8 { + self as u8 + } + + pub fn from_byte(value: u8) -> Option { + FromPrimitive::from_u8(value) + } +} + +pub fn get_transport() -> Result { + let hid = HidApi::new().map_err(|e| LedgerDeviceError::HidApi(e.to_string()))?; + TransportNativeHID::new(&hid).map_err(|e| LedgerDeviceError::NativeTransport(e.to_string())) +} + +#[derive(Debug, Clone)] +pub struct Command { + inner: APDUCommand, +} + +impl> Command { + pub fn new(inner: APDUCommand) -> Command { + Self { inner } + } + + pub fn execute(&self) -> Result>, LedgerDeviceError> { + get_transport()? + .exchange(&self.inner) + .map_err(|e| LedgerDeviceError::NativeTransport(e.to_string())) + } + + pub fn execute_with_transport( + &self, + transport: &TransportNativeHID, + ) -> Result>, LedgerDeviceError> { + transport + .exchange(&self.inner) + .map_err(|e| LedgerDeviceError::NativeTransport(e.to_string())) + } +} + +pub trait LedgerCommands { + fn build_command(&self, instruction: Instruction, data: Vec) -> Command>; + fn chunk_command(&self, instruction: Instruction, data: Vec>) -> Vec>>; +} + +const WALLET_CLA: u8 = 0x80; + +impl LedgerCommands for LedgerWallet { + fn build_command(&self, instruction: Instruction, data: Vec) -> Command> { + let mut base_data = self.account_bytes(); + base_data.extend_from_slice(&data); + + Command::new(APDUCommand { + cla: WALLET_CLA, + ins: instruction.as_byte(), + p1: 0x00, + p2: 0x00, + data: base_data, + }) + } + + fn chunk_command(&self, instruction: Instruction, data: Vec>) -> Vec>> { + let num_chunks = data.len(); + let mut more; + let mut commands = vec![]; + + for (i, chunk) in data.iter().enumerate() { + if i + 1 == num_chunks { + more = 0; + } else { + more = 1; + } + + // Prepend the account on the first payload + let mut base_data = vec![]; + if i == 0 { + base_data.extend_from_slice(&self.account_bytes()); + } + base_data.extend_from_slice(chunk); + + commands.push(Command::new(APDUCommand { + cla: WALLET_CLA, + ins: instruction.as_byte(), + p1: u8::try_from(i).unwrap_or(0), + p2: more, + data: base_data, + })); + } + + commands + } +} diff --git a/applications/minotari_ledger_wallet/comms/src/lib.rs b/applications/minotari_ledger_wallet/comms/src/lib.rs new file mode 100644 index 0000000000..a14b091c78 --- /dev/null +++ b/applications/minotari_ledger_wallet/comms/src/lib.rs @@ -0,0 +1,24 @@ +// Copyright 2024 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 ledger_wallet; diff --git a/applications/minotari_ledger_wallet/key_14x14.gif b/applications/minotari_ledger_wallet/key_14x14.gif deleted file mode 100644 index 2bab27badea681466df189271821b6354dfc29d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 145 zcmZ?wbh9u|D>()DVbWF3OqWEDr~2@9Addo p72ViltHI;4{yR5crR&3u(_)MoPo-YAjO`L)eeNsp!j*x+8URtMFlqn* diff --git a/applications/minotari_ledger_wallet/key_16x16.gif b/applications/minotari_ledger_wallet/key_16x16.gif deleted file mode 100644 index 55e198b16c9411d3bbf49cdda5273592c1a154a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmZ?wbh9u|6krfw*vtR|%F4=`nwk+25lu}^vuDpJ$v=))!Vmkzj^cK z%a(PhantomData); - -impl DomainSeparatedConsensusHasher { - /// Create a new hasher with the given label - pub fn new(label: &'static str) -> ConsensusHasher> { - let mut digest = Blake2b::::new(); - M::add_domain_separation_tag(&mut digest, label); - ConsensusHasher::from_digest(digest) - } -} - -/// Consensus hasher -#[derive(Clone)] -pub struct ConsensusHasher { - writer: WriteHashWrapper, -} - -impl ConsensusHasher { - fn from_digest(digest: D) -> Self { - Self { - writer: WriteHashWrapper(digest), - } - } -} - -impl ConsensusHasher -where D: Digest -{ - /// Finalize the hasher and return the hash - pub fn finalize(self) -> [u8; 64] { - self.writer.0.finalize().into() - } - - /// Update the hasher with the given data - pub fn update_consensus_encode(&mut self, data: &T) { - BorshSerialize::serialize(data, &mut self.writer) - .expect("Incorrect implementation of BorshSerialize encountered. Implementations MUST be infallible."); - } - - /// Update the hasher with the given data - pub fn chain(mut self, data: &T) -> Self { - self.update_consensus_encode(data); - self - } -} - -#[derive(Clone)] -struct WriteHashWrapper(D); - -impl Write for WriteHashWrapper { - fn write(&mut self, buf: &[u8]) -> BorshResult { - self.0.update(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> BorshResult<()> { - Ok(()) - } -} diff --git a/applications/minotari_ledger_wallet/src/main.rs b/applications/minotari_ledger_wallet/src/main.rs deleted file mode 100644 index 48e2d0e8a9..0000000000 --- a/applications/minotari_ledger_wallet/src/main.rs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -//! # MinoTari Ledger Wallet - -#![no_std] -#![no_main] -#![feature(alloc_error_handler)] - -extern crate alloc; -use core::{cmp::min, mem::MaybeUninit}; - -use critical_section::RawRestoreState; -use nanos_sdk::{ - buttons::ButtonEvent, - io, - io::{ApduHeader, Reply, StatusWords, SyscallError}, -}; -use nanos_ui::ui; -use tari_crypto::{ristretto::RistrettoSecretKey, tari_utilities::ByteArray}; - -use crate::{ - alloc::string::ToString, - utils::{byte_to_hex, get_raw_key, u64_to_string}, -}; - -static MINOTARI_LEDGER_ID: u32 = 535348; -static MINOTARI_ACCOUNT_ID: u32 = 7041; - -pub mod hashing; -pub mod utils; - -nanos_sdk::set_panic!(nanos_sdk::exiting_panic); - -/// Allocator heap size -const HEAP_SIZE: usize = 1024 * 26; - -/// Statically allocated heap memory -static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; - -/// Bind global allocator -#[global_allocator] -static HEAP: embedded_alloc::Heap = embedded_alloc::Heap::empty(); - -/// Error handler for allocation -#[alloc_error_handler] -fn alloc_error(_: core::alloc::Layout) -> ! { - ui::SingleMessage::new("allocation error!").show_and_wait(); - nanos_sdk::exit_app(250) -} - -/// Initialise allocator -pub fn init() { - unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } -} - -struct MyCriticalSection; -critical_section::set_impl!(MyCriticalSection); - -unsafe impl critical_section::Impl for MyCriticalSection { - unsafe fn acquire() -> RawRestoreState { - // nothing, it's all good, don't worry bout it - } - - unsafe fn release(_token: RawRestoreState) { - // nothing, it's all good, don't worry bout it - } -} - -/// App Version parameters -const NAME: &str = env!("CARGO_PKG_NAME"); -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -enum Instruction { - GetVersion, - GetPrivateKey, - BadInstruction(u8), - Exit, -} - -impl From for Instruction { - fn from(header: io::ApduHeader) -> Instruction { - match header.ins { - 0x01 => Self::GetVersion, - 0x02 => Self::GetPrivateKey, - 0x03 => Self::Exit, - other => Self::BadInstruction(other), - } - } -} - -#[no_mangle] -extern "C" fn sample_main() { - let mut comm = io::Comm::new(); - init(); - let messages = alloc::vec!["MinoTari Wallet", "keep the app open..", "[exit = both buttons]"]; - let mut index = 0; - ui::SingleMessage::new(messages[index]).show(); - loop { - let event = comm.next_event::(); - match event { - io::Event::Button(ButtonEvent::BothButtonsRelease) => nanos_sdk::exit_app(0), - io::Event::Button(ButtonEvent::RightButtonRelease) => { - index = min(index + 1, messages.len() - 1); - ui::SingleMessage::new(messages[index]).show() - }, - io::Event::Button(ButtonEvent::LeftButtonRelease) => { - if index > 0 { - index -= 1; - } - ui::SingleMessage::new(messages[index]).show() - }, - io::Event::Button(_) => {}, - io::Event::Command(apdu_header) => match handle_apdu(&mut comm, apdu_header.into()) { - Ok(()) => comm.reply_ok(), - Err(e) => comm.reply(e), - }, - io::Event::Ticker => {}, - } - } -} - -// Perform ledger instructions -fn handle_apdu(comm: &mut io::Comm, instruction: Instruction) -> Result<(), Reply> { - if comm.rx == 0 { - return Err(io::StatusWords::NothingReceived.into()); - } - - match instruction { - Instruction::GetVersion => { - ui::SingleMessage::new("GetVersion...").show(); - let name_bytes = NAME.as_bytes(); - let version_bytes = VERSION.as_bytes(); - comm.append(&[1]); // Format - comm.append(&[name_bytes.len() as u8]); - comm.append(name_bytes); - comm.append(&[version_bytes.len() as u8]); - comm.append(version_bytes); - comm.append(&[0]); // No flags - ui::SingleMessage::new("GetVersion... Done").show(); - comm.reply_ok(); - }, - Instruction::GetPrivateKey => { - // first 5 bytes are instruction details - let offset = 5; - let mut address_index_bytes = [0u8; 8]; - address_index_bytes.clone_from_slice(comm.get(offset, offset + 8)); - let address_index = crate::u64_to_string(u64::from_le_bytes(address_index_bytes)); - - let mut msg = "GetPrivateKey... ".to_string(); - msg.push_str(&address_index); - ui::SingleMessage::new(&msg).show(); - - let mut bip32_path = "m/44'/".to_string(); - bip32_path.push_str(&MINOTARI_LEDGER_ID.to_string()); - bip32_path.push_str(&"'/"); - bip32_path.push_str(&MINOTARI_ACCOUNT_ID.to_string()); - bip32_path.push_str(&"'/0/"); - bip32_path.push_str(&address_index); - let path: [u32; 5] = nanos_sdk::ecc::make_bip32_path(bip32_path.as_bytes()); - - let raw_key = get_raw_key(&path)?; - - let k = match RistrettoSecretKey::from_bytes(&raw_key) { - Ok(val) => val, - Err(_) => { - ui::SingleMessage::new("Err: key conversion").show(); - return Err(SyscallError::InvalidParameter.into()); - }, - }; - comm.append(&[1]); // version - comm.append(k.as_bytes()); - comm.reply_ok(); - }, - Instruction::BadInstruction(val) => { - let mut error = "BadInstruction... ! (".to_string(); - error.push_str(&crate::byte_to_hex(val)); - error.push_str(&")"); - ui::SingleMessage::new(&error).show(); - return Err(StatusWords::BadIns.into()); - }, - Instruction::Exit => { - ui::SingleMessage::new("Exit...").show(); - comm.reply_ok(); - nanos_sdk::exit_app(0) - }, - } - Ok(()) -} diff --git a/applications/minotari_ledger_wallet/src/utils.rs b/applications/minotari_ledger_wallet/src/utils.rs deleted file mode 100644 index e7c609f46a..0000000000 --- a/applications/minotari_ledger_wallet/src/utils.rs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - -//! # MinoTari Ledger Wallet - Utils - -use nanos_sdk::{ - ecc::{bip32_derive, CurvesId, CxError, Secret}, - io::SyscallError, -}; -use nanos_ui::ui; -use tari_crypto::hash_domain; - -use crate::{ - alloc::string::{String, ToString}, - hashing::DomainSeparatedConsensusHasher, -}; - -hash_domain!(LedgerHashDomain, "com.tari.genesis_tools.applications.mp_ldeger", 0); - -/// Convert a u64 to a string without using the standard library -pub fn u64_to_string(number: u64) -> String { - let mut buffer = [0u8; 20]; // Maximum length for a 64-bit integer (including null terminator) - let mut pos = 0; - - if number == 0 { - buffer[pos] = b'0'; - pos += 1; - } else { - let mut num = number; - - let mut digits = [0u8; 20]; - let mut num_digits = 0; - - while num > 0 { - digits[num_digits] = b'0' + (num % 10) as u8; - num /= 10; - num_digits += 1; - } - - while num_digits > 0 { - num_digits -= 1; - buffer[pos] = digits[num_digits]; - pos += 1; - } - } - - String::from_utf8_lossy(&buffer[..pos]).to_string() -} - -/// Convert a single byte to a hex string -pub fn byte_to_hex(byte: u8) -> String { - const HEX_CHARS: [u8; 16] = *b"0123456789abcdef"; - let hex = [HEX_CHARS[(byte >> 4) as usize], HEX_CHARS[(byte & 0x0F) as usize]]; - String::from_utf8_lossy(&hex).to_string() -} - -// Convert CxError to a string for display -fn cx_error_to_string(e: CxError) -> String { - let err = match e { - CxError::Carry => "Carry", - CxError::Locked => "Locked", - CxError::Unlocked => "Unlocked", - CxError::NotLocked => "NotLocked", - CxError::NotUnlocked => "NotUnlocked", - CxError::InternalError => "InternalError", - CxError::InvalidParameterSize => "InvalidParameterSize", - CxError::InvalidParameterValue => "InvalidParameterValue", - CxError::InvalidParameter => "InvalidParameter", - CxError::NotInvertible => "NotInvertible", - CxError::Overflow => "Overflow", - CxError::MemoryFull => "MemoryFull", - CxError::NoResidue => "NoResidue", - CxError::PointAtInfinity => "PointAtInfinity", - CxError::InvalidPoint => "InvalidPoint", - CxError::InvalidCurve => "InvalidCurve", - CxError::GenericError => "GenericError", - }; - err.to_string() -} - -// Get a raw 32 byte key hash from the BIP32 path. -// - The wrapper function for the syscall `os_perso_derive_node_bip32`, `bip32_derive`, requires a 96 byte buffer when -// called with `CurvesId::Ed25519` as it checks the consistency of the curve choice and key length in order to prevent -// the underlying syscall from panicking. -// - The syscall `os_perso_derive_node_bip32` returns 96 bytes as: -// private key: 64 bytes -// chain: 32 bytes -// Example: -// d8a57c1be0c52e9643485e77aac56d72fa6c4eb831466c2abd2d320c82d3d14929811c598c13d431bad433e037dbd97265492cea42bc2e3aad15440210a20a2d0000000000000000000000000000000000000000000000000000000000000000 -// - This function applies domain separated hashing to the 64 byte private key of the returned buffer to get 32 -// uniformly distributed random bytes. -fn get_raw_key_hash(path: &[u32]) -> Result<[u8; 64], String> { - let mut key = Secret::<96>::new(); - let raw_key_64 = match bip32_derive(CurvesId::Ed25519, path, key.as_mut()) { - Ok(_) => { - let binding = &key.as_ref()[..64]; - let raw_key_64: [u8; 64] = match binding.try_into() { - Ok(v) => v, - Err(_) => return Err("Err: get_raw_key".to_string()), - }; - raw_key_64 - }, - Err(e) => return Err(cx_error_to_string(e)), - }; - - Ok(DomainSeparatedConsensusHasher::::new("raw_key") - .chain(&raw_key_64) - .finalize()) -} - -/// Get a raw 32 byte key hash from the BIP32 path. In cas of an error, display an interactive message on the device. -pub fn get_raw_key(path: &[u32]) -> Result<[u8; 64], SyscallError> { - match get_raw_key_hash(&path) { - Ok(val) => Ok(val), - Err(e) => { - let mut msg = "".to_string(); - msg.push_str("Err: raw key >>..."); - ui::SingleMessage::new(&msg).show_and_wait(); - ui::SingleMessage::new(&e).show(); - Err(SyscallError::InvalidParameter.into()) - }, - } -} diff --git a/applications/minotari_ledger_wallet/wallet/.cargo/config.toml b/applications/minotari_ledger_wallet/wallet/.cargo/config.toml new file mode 100644 index 0000000000..d8db64960b --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/.cargo/config.toml @@ -0,0 +1,15 @@ +[target.nanosplus] +runner = "speculos -m nanosp" + +[build] +target = "nanosplus" + +[unstable] +avoid-dev-deps = true +build-std = ["core", "std", "alloc"] +build-std-features = ["compiler-builtins-mem"] +host-config = true +target-applies-to-host = true + +[host] +rustflags = ["-Ctarget-feature=-crt-static"] \ No newline at end of file diff --git a/applications/minotari_ledger_wallet/wallet/.gitignore b/applications/minotari_ledger_wallet/wallet/.gitignore new file mode 100644 index 0000000000..2e4f43b784 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/.gitignore @@ -0,0 +1,22 @@ +../target +app.json +app_nanos.json +app_nanosplus.json +app_nanox.json + +# Temporary directory with snapshots taken during test runs +tests/snapshots-tmp/ + +# Python +*.pyc[cod] +*.egg +__pycache__/ +*.egg-info/ +.eggs/ +.python-version + +# Related to the Ledger VSCode extension +# Virtual env for sideload (macOS and Windows) +ledger/ +# Build directory +build/ \ No newline at end of file diff --git a/applications/minotari_ledger_wallet/wallet/Cargo.lock b/applications/minotari_ledger_wallet/wallet/Cargo.lock new file mode 100644 index 0000000000..41e9ba9a3e --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/Cargo.lock @@ -0,0 +1,909 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.60", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a8646f94ab393e43e8b35a2558b1624bed28b97ee09c5d15456e3c9463f46d" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.60", + "syn_derive", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rand_core", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "embedded-alloc" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddae17915accbac2cfbc64ea0ae6e3b330e6ea124ba108dada63646fd3c6f815" +dependencies = [ + "critical-section", + "linked_list_allocator", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "include_gif" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "132290d08a42868f8f90e96a9206e955b0aae1e5a5df54c0029e8c2ab8652625" +dependencies = [ + "gif", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "ledger_device_sdk" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ff8da28601abd5150650f1ebd4e6d95fe8ac59c4a6ea24ac418cf2ffa6459c" +dependencies = [ + "include_gif", + "ledger_secure_sdk_sys", + "num-traits", + "numtoa", + "rand_core", + "zeroize", +] + +[[package]] +name = "ledger_secure_sdk_sys" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e201ddad57baebaf48c694341d1e0aa910d3516cafee32fc385fa73c01381c" +dependencies = [ + "bindgen", + "cc", +] + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minotari_ledger_wallet" +version = "1.0.0-pre.16" +dependencies = [ + "blake2", + "borsh", + "critical-section", + "digest", + "embedded-alloc", + "include_gif", + "ledger_device_sdk", + "once_cell", + "tari_crypto", + "tari_hashing", + "zeroize", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "numtoa" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa2c4e539b869820a2b82e1aef6ff40aa85e65decdd5185e83fb4b1249cd00f" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +dependencies = [ + "atomic-polyfill", + "critical-section", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac2cf0f2e4f42b49f5ffd07dae8d746508ef7526c13940e5f524012ae6c6550" +dependencies = [ + "proc-macro2", + "syn 2.0.60", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "tari_crypto" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40833db37f54c3b176da3d07199c76ec7a062b19fbf74bf270d614804114ee58" +dependencies = [ + "blake2", + "borsh", + "curve25519-dalek", + "digest", + "log", + "merlin", + "once_cell", + "rand_chacha", + "rand_core", + "sha3", + "snafu", + "subtle", + "tari_utilities", + "zeroize", +] + +[[package]] +name = "tari_hashing" +version = "1.0.0-pre.16" +dependencies = [ + "borsh", + "digest", + "tari_crypto", +] + +[[package]] +name = "tari_utilities" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1bb0e5d1d812f2be2d6ad861caad68f75adb5b2e8376264850300deb16ddc7" +dependencies = [ + "generic-array", + "snafu", + "zeroize", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "weezl" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] diff --git a/applications/minotari_ledger_wallet/wallet/Cargo.toml b/applications/minotari_ledger_wallet/wallet/Cargo.toml new file mode 100644 index 0000000000..1c4839ab69 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "minotari_ledger_wallet" +version = "1.0.0-pre.16" +authors = ["The Tari Development Community"] +license = "BSD-3-Clause" +edition = "2021" + +[dependencies] +tari_crypto = { version = "0.20.3", default-features = false, features = ["borsh"]} +tari_hashing = { path = "../../../hashing", version = "1.0.0-pre.16" } + +blake2 = { version = "0.10", default-features = false } +borsh = { version = "1.2", default-features = false } +critical-section = { version = "1.1.1" } +digest = { version = "0.10", default-features = false } +embedded-alloc = "0.5.0" +include_gif = "1.0.1" +ledger_device_sdk = "1.7.1" +zeroize = { version = "1", default-features = false } + +# once_cell defined here just to lock the version. Other dependencies may try to go to 1.19 which is incompatabile with +# ledger at this time. 1.19 removes "atomic-polyfill" and replaces it with "portable-atomic" which can not build due to +# target mismatches. +once_cell = { version = "=1.18.0", default-features = false } + +[package.metadata.cargo-machete] +ignored = ["once_cell"] + +[profile.release] +opt-level = 's' +lto = "fat" # same as `true` +panic = "abort" + +[features] +default = ["pending_review_screen"] +pending_review_screen = [] + +[package.metadata.ledger] +curve = ["ed25519"] +flags = "0" +path = ["44'/535348'"] +name = "MinoTari Wallet" +api_level = "1" + +[package.metadata.ledger.nanos] +icon = "key.gif" + +[package.metadata.ledger.nanox] +icon = "key_14x14.gif" + +[package.metadata.ledger.nanosplus] +icon = "key_14x14.gif" + +[workspace] \ No newline at end of file diff --git a/applications/minotari_ledger_wallet/README.md b/applications/minotari_ledger_wallet/wallet/README.md similarity index 55% rename from applications/minotari_ledger_wallet/README.md rename to applications/minotari_ledger_wallet/wallet/README.md index 3d03356929..5583dc0672 100644 --- a/applications/minotari_ledger_wallet/README.md +++ b/applications/minotari_ledger_wallet/wallet/README.md @@ -7,32 +7,27 @@ Ledger does not build with the standard library, so we need to install `rust-src rustup component add rust-src --toolchain nightly ``` -For loading a BOLOS application to a Ledger device, Ledger has actually written a command, called +For loading a BOLOS application to a Ledger device, Ledger has actually written a command, called [Cargo Ledger](https://github.com/LedgerHQ/cargo-ledger). This we need to install with: ``` -cargo install --git https://github.com/LedgerHQ/cargo-ledger +cargo install --git https://github.com/LedgerHQ/cargo-ledger cargo-ledger ``` -As per the [Cargo Ledger setup instructions](https://github.com/LedgerHQ/cargo-ledger#setup) run the following to add +As per the [Cargo Ledger setup instructions](https://github.com/LedgerHQ/cargo-ledger#setup) run the following to add new build targets for the current rust toolchain: ``` cargo ledger setup ``` -Next up we need install the supporting Python libraries from Ledger to control Ledger devices, +To control ledger devices we use the ledgerctl library. +We install the supporting Python libraries from Ledger to control Ledger devices, [LedgerCTL](https://github.com/LedgerHQ/ledgerctl). This we do with: ``` pip3 install --upgrade protobuf setuptools ecdsa pip3 install git+https://github.com/LedgerHQ/ledgerctl ``` -Lastly install the ARM GCC toolchain: `arm-none-eabi-gcc` for your OS (https://developer.arm.com/downloads/-/gnu-rm). -For MacOS, we can use brew with: -``` -brew install armmbed/formulae/arm-none-eabi-gcc -``` - ## Device configuration See https://github.com/LedgerHQ/ledgerctl#device-configuration @@ -47,14 +42,18 @@ Once in recovery mode run the following where is simply the name of the C ledgerctl install-ca ``` -## Runtime +## Building + +### Native -Open a terminal in the subfolder `./applications/ledger` +Open a terminal in the subfolder `./applications/minotari_ledger_wallet/wallet` _**Note:** Windows users should start a "x64 Native Tools Command Prompt for VS 2019" to have the build tools available and then start a python shell within that terminal to have the Python libraries available._ -### Build `ledger` +#### Build `ledger` + +This must be run from a Python shell (`pip3 --version` should work). To build, run @@ -64,19 +63,38 @@ cargo ledger build {TARGET} -- "-Zbuild-std=std,alloc" where TARGET = nanosplus, nanos, etc. -### Build and install `ledger` +#### Build and install `ledger` -This must be run from a Python shell (`pip3 --version` should work). To build and load, run +To build and load, run ``` cargo ledger build {TARGET} --load -- "-Zbuild-std=std,alloc" ``` + where TARGET = nanosplus, nanos, etc. **Errors** -If the auto-load does not work ("ledgerwallet.client.CommException: Exception : Invalid status 6512 (Unknown reason)"), -try to do a manual installation. +If the auto-load does not work, try to do a manual installation. + +### Using Docker + +Ledger does not easily compile locally and it is easiest to compile via docker using their provided [ledger-app-builder](https://github.com/LedgerHQ/ledger-app-builder/). +See their readme for setup. +Once installed you can build the Tari Wallet for ledger by navigating to `./applications/minotari_ledger_wallet` and running the docker command: + +``` +docker run --rm -it -v ".:/app" ghcr.io/ledgerhq/ledger-app-builder/ledger-app-builder +``` + +This will load you into the docker vm where you can now build the ledger app. +where TARGET = nanosplus, nanos, etc. + +``` +cargo ledger build {TARGET} +``` + +Please note docker has no access to usb devices on MacOS. So the use of `cargo ledger build {TARGET} --load` will fail. ### Manual installation @@ -91,19 +109,20 @@ try to do a manual installation. ``` `ledgerctl install app_nanosplus.json` ``` -**Note:** In some cases the `cargo ledger build` action will invalidate `app_nanosplus.json` by setting the first line -to `"apiLevel": "0",` - ensure it is set to `"apiLevel": "1",` + +**Note:** In some cases the `cargo ledger build` action will invalidate `app_nanosplus.json` by setting the first line +to something other than `"apiLevel": "1",` - ensure it is set to `"apiLevel": "1",` ### Running the ledger application -Start the `MinoTari Wallet` application on the Ledger by navigating to the app and pressing both buttons. You should +Start the `MinoTari Wallet` application on the Ledger by navigating to the app and pressing both buttons. You should see `MinoTari Wallet` displayed on the screen. Now your device is ready to be used with the console wallet. _**Note:** To manually exit the application, press both buttons on the Ledger._ **Errors** -- If the `MinoTari Wallet` application on the Ledger is not started when trying to access it with a desktop +- If the `MinoTari Wallet` application on the Ledger is not started when trying to access it with a desktop application, you should see the following error on the desktop: `Error: Ledger application not started` @@ -115,3 +134,28 @@ _**Note:** To manually exit the application, press both buttons on the Ledger._ - If the `MinoTari Wallet` application has an incorrect version, you should see the following error on the desktop: `Error: Processing error 'MinoTari Wallet application version mismatch: expected ...'` + +## Emulator + +Ledger has provided an in browser ledger emulator [Speculos](https://github.com/LedgerHQ/speculos) + +To build on M1 devices clone the repository + +``` +$ git checkout df84117d2ac300cd277d58913a9f56e061b5fb2f + +// Now build the docker image +$ docker build -t speculos-builder:latest -f build.Dockerfile . + +// Now build the main docker image, which will be based of the builder image. + +$ docker build -t speculos:latest . +``` + +Once built, run the emulator with: + +``` +docker run --rm -it -v $(pwd):/speculos/apps -p 1234:1234 -p 3000:3000 -p 40000:40000 -p 41000:41000 speculos --display headless --api-port 3000 --vnc-port 41000 apps/target/nanosplus/release/minotari_ledger_wallet --model nanosp +``` + +Browse to the address `http://localhost:3000` \ No newline at end of file diff --git a/applications/minotari_ledger_wallet/wallet/build.rs b/applications/minotari_ledger_wallet/wallet/build.rs new file mode 100644 index 0000000000..78d986af60 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/build.rs @@ -0,0 +1,6 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +fn main() { + println!("cargo:rerun-if-changed=script.ld"); +} diff --git a/applications/minotari_ledger_wallet/wallet/key.gif b/applications/minotari_ledger_wallet/wallet/key.gif new file mode 100644 index 0000000000000000000000000000000000000000..287a8df0d61170883490f87d222ed878509aa4d7 GIT binary patch literal 892 zcmZ?wbhEHb6krfwXlDR{|NsAk=}}@d1V&s4DE{a6a}5c0b_{Se(lcOY1O|ZOPZm}t zAgu!m3{W;<;BaGL Event { + let pages = [ + &Page::from((["MinoTari Wallet", "(c) 2024 The Tari Project"], true)), + &Page::from(("Back", &BACK)), + ]; + loop { + match MultiPageMenu::new(comm, &pages).show() { + EventOrPageIndex::Event(e) => return e, + EventOrPageIndex::Index(1) => return ui_menu_main(comm), + EventOrPageIndex::Index(_) => (), + } + } +} + +pub fn ui_menu_main(comm: &mut Comm) -> Event { + const APP_ICON: Glyph = Glyph::from_include(include_gif!("key.gif")); + let pages = [ + // The from trait allows to create different styles of pages + // without having to use the new() function. + &Page::from((["MinoTari", "Wallet"], &APP_ICON)), + &Page::from((["Version", env!("CARGO_PKG_VERSION")], true)), + &Page::from(("About", &CERTIFICATE)), + &Page::from(("Quit", &DASHBOARD_X)), + ]; + loop { + match MultiPageMenu::new(comm, &pages).show() { + EventOrPageIndex::Event(e) => return e, + EventOrPageIndex::Index(2) => return ui_about_menu(comm), + EventOrPageIndex::Index(3) => ledger_device_sdk::exit_app(0), + EventOrPageIndex::Index(_) => (), + } + } +} diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_dh_shared_secret.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_dh_shared_secret.rs new file mode 100644 index 0000000000..2abdea2e58 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_dh_shared_secret.rs @@ -0,0 +1,44 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use core::ops::Deref; + +use ledger_device_sdk::io::Comm; +use tari_crypto::{ristretto::RistrettoPublicKey, tari_utilities::ByteArray}; + +use crate::{ + utils::{derive_from_bip32_key, get_key_from_canonical_bytes}, + AppSW, + KeyType, + RESPONSE_VERSION, +}; + +pub fn handler_get_dh_shared_secret(comm: &mut Comm) -> Result<(), AppSW> { + let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?; + + let mut account_bytes = [0u8; 8]; + account_bytes.clone_from_slice(&data[0..8]); + let account = u64::from_le_bytes(account_bytes); + + let mut index_bytes = [0u8; 8]; + index_bytes.clone_from_slice(&data[8..16]); + let index = u64::from_le_bytes(index_bytes); + + let mut key_bytes = [0u8; 8]; + key_bytes.clone_from_slice(&data[16..24]); + let key_int = u64::from_le_bytes(key_bytes); + let key = KeyType::from_branch_key(key_int)?; + + let public_key: RistrettoPublicKey = get_key_from_canonical_bytes(&data[24..56])?; + + let shared_secret_key = match derive_from_bip32_key(account, index, key) { + Ok(k) => k.deref() * public_key, + Err(e) => return Err(e), + }; + + comm.append(&[RESPONSE_VERSION]); // version + comm.append(shared_secret_key.as_bytes()); + comm.reply_ok(); + + Ok(()) +} diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_public_key.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_public_key.rs new file mode 100644 index 0000000000..2537538341 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_public_key.rs @@ -0,0 +1,35 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use ledger_device_sdk::io::Comm; +use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey, tari_utilities::ByteArray}; + +use crate::{utils::derive_from_bip32_key, AppSW, KeyType, RESPONSE_VERSION}; + +pub fn handler_get_public_key(comm: &mut Comm) -> Result<(), AppSW> { + let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?; + + let mut account_bytes = [0u8; 8]; + account_bytes.clone_from_slice(&data[0..8]); + let account = u64::from_le_bytes(account_bytes); + + let mut index_bytes = [0u8; 8]; + index_bytes.clone_from_slice(&data[8..16]); + let index = u64::from_le_bytes(index_bytes); + + let mut key_bytes = [0u8; 8]; + key_bytes.clone_from_slice(&data[16..24]); + let key_int = u64::from_le_bytes(key_bytes); + let key = KeyType::from_branch_key(key_int)?; + + let pk = match derive_from_bip32_key(account, index, key) { + Ok(k) => RistrettoPublicKey::from_secret_key(&k), + Err(e) => return Err(e), + }; + + comm.append(&[RESPONSE_VERSION]); // version + comm.append(pk.as_bytes()); + comm.reply_ok(); + + Ok(()) +} diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_public_spend_key.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_public_spend_key.rs new file mode 100644 index 0000000000..f621e6dff2 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_public_spend_key.rs @@ -0,0 +1,26 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use ledger_device_sdk::io::Comm; +use tari_crypto::{keys::PublicKey, ristretto::RistrettoPublicKey, tari_utilities::ByteArray}; + +use crate::{utils::derive_from_bip32_key, AppSW, KeyType, RESPONSE_VERSION, STATIC_SPEND_INDEX}; + +pub fn handler_get_public_spend_key(comm: &mut Comm) -> Result<(), AppSW> { + let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?; + + let mut account_bytes = [0u8; 8]; + account_bytes.clone_from_slice(&data[0..8]); + let account = u64::from_le_bytes(account_bytes); + + let pk = match derive_from_bip32_key(account, STATIC_SPEND_INDEX, KeyType::Spend) { + Ok(k) => RistrettoPublicKey::from_secret_key(&k), + Err(e) => return Err(e), + }; + + comm.append(&[RESPONSE_VERSION]); // version + comm.append(pk.as_bytes()); + comm.reply_ok(); + + Ok(()) +} diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_offset.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_offset.rs new file mode 100644 index 0000000000..034c0d8dd3 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_offset.rs @@ -0,0 +1,150 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use alloc::vec::Vec; +use core::ops::Deref; + +use ledger_device_sdk::io::Comm; +use tari_crypto::{ristretto::RistrettoSecretKey, tari_utilities::ByteArray}; +use zeroize::Zeroizing; + +use crate::{ + utils::{alpha_hasher, derive_from_bip32_key, get_key_from_canonical_bytes}, + AppSW, + KeyType, + RESPONSE_VERSION, + STATIC_SPEND_INDEX, +}; + +const MIN_UNIQUE_KEYS: usize = 2; + +pub struct ScriptOffsetCtx { + total_sender_offset_private_key: Zeroizing, + total_script_private_key: Zeroizing, + account: u64, + total_offset_indexes: u64, + total_commitment_keys: u64, + unique_keys: Vec>, +} + +// Implement constructor for TxInfo with default values +impl ScriptOffsetCtx { + pub fn new() -> Self { + Self { + total_sender_offset_private_key: Zeroizing::new(RistrettoSecretKey::default()), + total_script_private_key: Zeroizing::new(RistrettoSecretKey::default()), + account: 0, + total_offset_indexes: 0, + total_commitment_keys: 0, + unique_keys: Vec::new(), + } + } + + // Implement reset for TxInfo + fn reset(&mut self) { + self.total_sender_offset_private_key = Zeroizing::new(RistrettoSecretKey::default()); + self.total_script_private_key = Zeroizing::new(RistrettoSecretKey::default()); + self.account = 0; + self.total_offset_indexes = 0; + self.total_commitment_keys = 0; + self.unique_keys = Vec::new(); + } + + fn add_unique_key(&mut self, secret_key: Zeroizing) { + if !self.unique_keys.contains(&secret_key) { + self.unique_keys.push(secret_key); + } + } +} + +fn read_instructions(offset_ctx: &mut ScriptOffsetCtx, data: &[u8]) { + let mut account_bytes = [0u8; 8]; + account_bytes.clone_from_slice(&data[0..8]); + offset_ctx.account = u64::from_le_bytes(account_bytes); + + if data.len() < 16 { + offset_ctx.total_offset_indexes = 0; + } else { + let mut total_offset_keys = [0u8; 8]; + total_offset_keys.clone_from_slice(&data[8..16]); + offset_ctx.total_offset_indexes = u64::from_le_bytes(total_offset_keys); + } + + if data.len() < 24 { + offset_ctx.total_commitment_keys = 0; + } else { + let mut total_commitment_keys = [0u8; 8]; + total_commitment_keys.clone_from_slice(&data[16..24]); + offset_ctx.total_commitment_keys = u64::from_le_bytes(total_commitment_keys); + } +} + +pub fn handler_get_script_offset( + comm: &mut Comm, + chunk: u8, + more: bool, + offset_ctx: &mut ScriptOffsetCtx, +) -> Result<(), AppSW> { + let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?; + + if chunk == 0 { + // Reset offset context + offset_ctx.reset(); + read_instructions(offset_ctx, data); + return Ok(()); + } + + if chunk == 1 { + // The sum of managed private keys + let k: Zeroizing = get_key_from_canonical_bytes::(&data[0..32])?.into(); + offset_ctx.total_script_private_key = Zeroizing::new(offset_ctx.total_script_private_key.deref() + k.deref()); + + return Ok(()); + } + + let payload_offset = 2; + let end_offset_indexes = payload_offset + offset_ctx.total_offset_indexes; + + if (payload_offset..end_offset_indexes).contains(&(chunk as u64)) { + let mut index_bytes = [0u8; 8]; + index_bytes.clone_from_slice(&data[0..8]); + let index = u64::from_le_bytes(index_bytes); + + let offset = derive_from_bip32_key(offset_ctx.account, index, KeyType::OneSidedSenderOffset)?; + offset_ctx.add_unique_key(offset.clone()); + offset_ctx.total_sender_offset_private_key = + Zeroizing::new(offset_ctx.total_sender_offset_private_key.deref() + offset.deref()); + } + + let end_commitment_keys = end_offset_indexes + offset_ctx.total_commitment_keys; + + if (end_offset_indexes..end_commitment_keys).contains(&(chunk as u64)) { + let alpha = derive_from_bip32_key(offset_ctx.account, STATIC_SPEND_INDEX, KeyType::Spend)?; + let blinding_factor: Zeroizing = + get_key_from_canonical_bytes::(&data[0..32])?.into(); + + let k = alpha_hasher(alpha, blinding_factor)?; + + offset_ctx.add_unique_key(k.clone()); + offset_ctx.total_script_private_key = Zeroizing::new(offset_ctx.total_script_private_key.deref() + k.deref()); + } + + if more { + return Ok(()); + } + + if offset_ctx.unique_keys.len() < MIN_UNIQUE_KEYS { + return Err(AppSW::ScriptOffsetNotUnique); + } + + let script_offset = Zeroizing::new( + offset_ctx.total_script_private_key.deref() - offset_ctx.total_sender_offset_private_key.deref(), + ); + + comm.append(&[RESPONSE_VERSION]); // version + comm.append(&script_offset.to_vec()); + offset_ctx.reset(); + comm.reply_ok(); + + Ok(()) +} diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature.rs new file mode 100644 index 0000000000..fc8d1f34fd --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_script_signature.rs @@ -0,0 +1,123 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use alloc::format; + +use blake2::Blake2b; +use digest::consts::U64; +use ledger_device_sdk::{io::Comm, random::Random, ui::gadgets::SingleMessage}; +use tari_crypto::{ + commitment::HomomorphicCommitmentFactory, + keys::PublicKey, + ristretto::{ + pedersen::{extended_commitment_factory::ExtendedPedersenCommitmentFactory, PedersenCommitment}, + RistrettoComAndPubSig, + RistrettoPublicKey, + RistrettoSecretKey, + }, +}; +use tari_hashing::TransactionHashDomain; +use zeroize::Zeroizing; + +use crate::{ + alloc::string::ToString, + hashing::DomainSeparatedConsensusHasher, + utils::{alpha_hasher, derive_from_bip32_key, get_key_from_canonical_bytes}, + AppSW, + KeyType, + RESPONSE_VERSION, + STATIC_SPEND_INDEX, +}; + +pub fn handler_get_script_signature(comm: &mut Comm) -> Result<(), AppSW> { + let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?; + + let mut account_bytes = [0u8; 8]; + account_bytes.clone_from_slice(&data[0..8]); + let account = u64::from_le_bytes(account_bytes); + + let mut network_bytes = [0u8; 8]; + network_bytes.clone_from_slice(&data[8..16]); + let network = u64::from_le_bytes(network_bytes); + + let mut txi_version_bytes = [0u8; 8]; + txi_version_bytes.clone_from_slice(&data[16..24]); + let txi_version = u64::from_le_bytes(txi_version_bytes); + + let alpha = derive_from_bip32_key(account, STATIC_SPEND_INDEX, KeyType::Spend)?; + let blinding_factor: Zeroizing = + get_key_from_canonical_bytes::(&data[24..56])?.into(); + let script_private_key = alpha_hasher(alpha, blinding_factor)?; + let script_public_key = RistrettoPublicKey::from_secret_key(&script_private_key); + + let value: Zeroizing = + get_key_from_canonical_bytes::(&data[56..88])?.into(); + let spend_private_key: Zeroizing = + get_key_from_canonical_bytes::(&data[88..120])?.into(); + + let commitment: PedersenCommitment = get_key_from_canonical_bytes(&data[120..152])?; + + let mut script_message = [0u8; 32]; + script_message.clone_from_slice(&data[152..184]); + + let r_a = derive_from_bip32_key(account, u32::random().into(), KeyType::Nonce)?; + let r_x = derive_from_bip32_key(account, u32::random().into(), KeyType::Nonce)?; + let r_y = derive_from_bip32_key(account, u32::random().into(), KeyType::Nonce)?; + + let factory = ExtendedPedersenCommitmentFactory::default(); + + let ephemeral_commitment = factory.commit(&r_x, &r_a); + let ephemeral_pubkey = RistrettoPublicKey::from_secret_key(&r_y); + + let challenge = finalize_script_signature_challenge( + txi_version, + network, + &ephemeral_commitment, + &ephemeral_pubkey, + &script_public_key, + &commitment, + &script_message, + ); + + let script_signature = match RistrettoComAndPubSig::sign( + &value, + &spend_private_key, + &script_private_key, + &r_a, + &r_x, + &r_y, + &challenge, + &factory, + ) { + Ok(sig) => sig, + Err(e) => { + SingleMessage::new(&format!("Signing error: {:?}", e.to_string())).show_and_wait(); + return Err(AppSW::ScriptSignatureFail); + }, + }; + + comm.append(&[RESPONSE_VERSION]); // version + comm.append(&script_signature.to_vec()); + comm.reply_ok(); + + Ok(()) +} + +fn finalize_script_signature_challenge( + _version: u64, + network: u64, + ephemeral_commitment: &PedersenCommitment, + ephemeral_pubkey: &RistrettoPublicKey, + script_public_key: &RistrettoPublicKey, + commitment: &PedersenCommitment, + message: &[u8; 32], +) -> [u8; 64] { + DomainSeparatedConsensusHasher::>::new("script_challenge", network) + .chain(ephemeral_commitment) + .chain(ephemeral_pubkey) + .chain(script_public_key) + .chain(commitment) + .chain(message) + .finalize() + .into() +} diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_version.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_version.rs new file mode 100644 index 0000000000..00932f27a0 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_version.rs @@ -0,0 +1,12 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use ledger_device_sdk::io; + +use crate::AppSW; + +pub fn handler_get_version(comm: &mut io::Comm) -> Result<(), AppSW> { + let version = env!("CARGO_PKG_VERSION").as_bytes(); + comm.append(version); + Ok(()) +} diff --git a/applications/minotari_ledger_wallet/wallet/src/handlers/get_view_key.rs b/applications/minotari_ledger_wallet/wallet/src/handlers/get_view_key.rs new file mode 100644 index 0000000000..7fc11b9f27 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/handlers/get_view_key.rs @@ -0,0 +1,26 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use ledger_device_sdk::io::Comm; +use tari_crypto::tari_utilities::ByteArray; + +use crate::{utils::derive_from_bip32_key, AppSW, KeyType, RESPONSE_VERSION, STATIC_VIEW_INDEX}; + +pub fn handler_get_view_key(comm: &mut Comm) -> Result<(), AppSW> { + let data = comm.get_data().map_err(|_| AppSW::WrongApduLength)?; + + let mut account_bytes = [0u8; 8]; + account_bytes.clone_from_slice(&data[0..8]); + let account = u64::from_le_bytes(account_bytes); + + let p = match derive_from_bip32_key(account, STATIC_VIEW_INDEX, KeyType::ViewKey) { + Ok(k) => k, + Err(e) => return Err(e), + }; + + comm.append(&[RESPONSE_VERSION]); // version + comm.append(p.as_bytes()); + comm.reply_ok(); + + Ok(()) +} diff --git a/applications/minotari_ledger_wallet/wallet/src/hashing.rs b/applications/minotari_ledger_wallet/wallet/src/hashing.rs new file mode 100644 index 0000000000..384a7932f0 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/hashing.rs @@ -0,0 +1,78 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use alloc::format; +use core::marker::PhantomData; + +use borsh::{io, io::Write, BorshSerialize}; +use digest::Digest; +use tari_crypto::hashing::DomainSeparation; + +pub struct DomainSeparatedConsensusHasher { + hasher: DomainSeparatedBorshHasher, +} + +impl DomainSeparatedConsensusHasher +where D: Default +{ + pub fn new(label: &'static str, network: u64) -> Self { + let hasher = DomainSeparatedBorshHasher::::new_with_label(&format!("{}.n{}", label, network as u8)); + Self { hasher } + } + + pub fn finalize(self) -> digest::Output { + self.hasher.finalize() + } + + pub fn update_consensus_encode(&mut self, data: &T) { + self.hasher.update_consensus_encode(data); + } + + pub fn chain(mut self, data: &T) -> Self { + self.update_consensus_encode(data); + self + } +} + +/// Domain separated borsh-encoding hasher. +pub struct DomainSeparatedBorshHasher { + writer: WriteHashWrapper, + _m: PhantomData, +} + +impl DomainSeparatedBorshHasher { + #[allow(clippy::new_ret_no_self)] + pub fn new_with_label(label: &str) -> Self { + let mut digest = D::default(); + M::add_domain_separation_tag(&mut digest, label); + Self { + writer: WriteHashWrapper(digest), + _m: PhantomData, + } + } + + pub fn finalize(self) -> digest::Output { + self.writer.0.finalize() + } + + pub fn update_consensus_encode(&mut self, data: &T) { + BorshSerialize::serialize(data, &mut self.writer) + .expect("Incorrect implementation of BorshSerialize encountered. Implementations MUST be infallible."); + } +} + +/// This private struct wraps a Digest and implements the Write trait to satisfy the consensus encoding trait. +/// Do not use the DomainSeparatedHasher with this. +#[derive(Clone)] +struct WriteHashWrapper(D); + +impl Write for WriteHashWrapper { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.update(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} diff --git a/applications/minotari_ledger_wallet/wallet/src/main.rs b/applications/minotari_ledger_wallet/wallet/src/main.rs new file mode 100644 index 0000000000..8974df03ca --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/main.rs @@ -0,0 +1,232 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +#![no_std] +#![no_main] +#![feature(alloc_error_handler)] + +extern crate alloc; + +mod hashing; +mod utils; + +mod app_ui { + pub mod menu; +} +mod handlers { + pub mod get_dh_shared_secret; + pub mod get_public_key; + pub mod get_public_spend_key; + pub mod get_script_offset; + pub mod get_script_signature; + pub mod get_version; + pub mod get_view_key; +} + +use core::mem::MaybeUninit; + +use app_ui::menu::ui_menu_main; +use critical_section::RawRestoreState; +use handlers::{ + get_dh_shared_secret::handler_get_dh_shared_secret, + get_public_key::handler_get_public_key, + get_public_spend_key::handler_get_public_spend_key, + get_script_offset::{handler_get_script_offset, ScriptOffsetCtx}, + get_script_signature::handler_get_script_signature, + get_version::handler_get_version, + get_view_key::handler_get_view_key, +}; +#[cfg(feature = "pending_review_screen")] +use ledger_device_sdk::ui::gadgets::display_pending_review; +use ledger_device_sdk::{ + io::{ApduHeader, Comm, Event, Reply, StatusWords}, + ui::gadgets::SingleMessage, +}; + +ledger_device_sdk::set_panic!(ledger_device_sdk::exiting_panic); + +static BIP32_COIN_TYPE: u32 = 535348; +static CLA: u8 = 0x80; +static RESPONSE_VERSION: u8 = 1; + +/// Allocator heap size +const HEAP_SIZE: usize = 1024 * 26; + +/// Statically allocated heap memory +static mut HEAP_MEM: [MaybeUninit; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE]; + +/// Bind global allocator +#[global_allocator] +static HEAP: embedded_alloc::Heap = embedded_alloc::Heap::empty(); + +/// Error handler for allocation +#[alloc_error_handler] +fn alloc_error(_: core::alloc::Layout) -> ! { + SingleMessage::new("allocation error!").show_and_wait(); + ledger_device_sdk::exit_app(250) +} + +/// Initialise allocator +pub fn init() { + unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) } +} + +struct MyCriticalSection; +critical_section::set_impl!(MyCriticalSection); + +unsafe impl critical_section::Impl for MyCriticalSection { + unsafe fn acquire() -> RawRestoreState { + // nothing, it's all good, don't worry bout it + } + + unsafe fn release(_token: RawRestoreState) { + // nothing, it's all good, don't worry bout it + } +} + +// Application status words. +#[repr(u16)] +pub enum AppSW { + Deny = 0x6985, + WrongP1P2 = 0x6A86, + InsNotSupported = 0x6D00, + ClaNotSupported = 0x6E00, + ScriptSignatureFail = 0xB001, + MetadataSignatureFail = 0xB002, + ScriptOffsetNotUnique = 0xB004, + BadBranchKey = 0xB005, + KeyDeriveFail = 0xB009, + KeyDeriveFromCanonical = 0xB010, + KeyDeriveFromUniform = 0xB011, + VersionParsingFail = 0xB00A, + TooManyPayloads = 0xB003, + WrongApduLength = StatusWords::BadLen as u16, + UserCancelled = StatusWords::UserCancelled as u16, +} + +impl From for Reply { + fn from(sw: AppSW) -> Reply { + Reply(sw as u16) + } +} + +/// Possible input commands received through APDUs. +pub enum Instruction { + GetVersion, + GetAppName, + GetPublicKey, + GetPublicSpendKey, + GetScriptSignature, + GetScriptOffset { chunk: u8, more: bool }, + GetScriptSignatureFromChallenge, + GetViewKey, + GetDHSharedSecret, +} + +const P2_MORE: u8 = 0x01; +const STATIC_SPEND_INDEX: u64 = 42; +const STATIC_VIEW_INDEX: u64 = 57311; // No significance, just a random number by large dice roll +const MAX_PAYLOADS: u8 = 250; + +#[repr(u8)] +pub enum KeyType { + Spend = 0x01, + Nonce = 0x02, + ViewKey = 0x03, + OneSidedSenderOffset = 0x04, +} + +impl KeyType { + pub fn as_byte(self) -> u8 { + self as u8 + } + + fn from_branch_key(n: u64) -> Result { + // These numbers need to match the TransactionKeyManagerBranches in: + // base_layer/core/src/transactions/key_manager/interface.rs + match n { + 7 => Ok(Self::Spend), + 6 => Ok(Self::OneSidedSenderOffset), + _ => Err(AppSW::BadBranchKey), + } + } +} + +impl TryFrom for Instruction { + type Error = AppSW; + + /// APDU parsing logic. + /// + /// Parses INS, P1 and P2 bytes to build an [`Instruction`]. P1 and P2 are translated to + /// strongly typed variables depending on the APDU instruction code. Invalid INS, P1 or P2 + /// values result in errors with a status word, which are automatically sent to the host by the + /// SDK. + /// + /// This design allows a clear separation of the APDU parsing logic and commands handling. + /// + /// Note that CLA is not checked here. Instead the method [`Comm::set_expected_cla`] is used in + /// [`sample_main`] to have this verification automatically performed by the SDK. + fn try_from(value: ApduHeader) -> Result { + match (value.ins, value.p1, value.p2) { + (0x01, 0, 0) => Ok(Instruction::GetVersion), + (0x02, 0, 0) => Ok(Instruction::GetAppName), + (0x03, 0, 0) => Ok(Instruction::GetPublicSpendKey), + (0x04, 0, 0) => Ok(Instruction::GetPublicKey), + (0x05, 0, 0) => Ok(Instruction::GetScriptSignature), + (0x06, 0..=MAX_PAYLOADS, 0 | P2_MORE) => Ok(Instruction::GetScriptOffset { + chunk: value.p1, + more: value.p2 == P2_MORE, + }), + (0x08, 0, 0) => Ok(Instruction::GetScriptSignatureFromChallenge), + (0x09, 0, 0) => Ok(Instruction::GetViewKey), + (0x10, 0, 0) => Ok(Instruction::GetDHSharedSecret), + (0x06, _, _) => Err(AppSW::WrongP1P2), + (_, _, _) => Err(AppSW::InsNotSupported), + } + } +} + +#[no_mangle] +extern "C" fn sample_main() { + init(); + // Create the communication manager, and configure it to accept only APDU from the 0x80 class. + // If any APDU with a wrong class value is received, comm will respond automatically with + // BadCla status word. + let mut comm = Comm::new().set_expected_cla(CLA); + + // Developer mode / pending review popup + // must be cleared with user interaction + #[cfg(feature = "pending_review_screen")] + display_pending_review(&mut comm); + + // This is long-lived over the span the ledger app is open, across multiple interactions + let mut offset_ctx = ScriptOffsetCtx::new(); + + loop { + // Wait for either a specific button push to exit the app + // or an APDU command + if let Event::Command(ins) = ui_menu_main(&mut comm) { + match handle_apdu(&mut comm, ins, &mut offset_ctx) { + Ok(()) => comm.reply_ok(), + Err(sw) => comm.reply(sw), + } + } + } +} + +fn handle_apdu(comm: &mut Comm, ins: Instruction, offset_ctx: &mut ScriptOffsetCtx) -> Result<(), AppSW> { + match ins { + Instruction::GetVersion => handler_get_version(comm), + Instruction::GetAppName => { + comm.append(env!("CARGO_PKG_NAME").as_bytes()); + Ok(()) + }, + Instruction::GetPublicKey => handler_get_public_key(comm), + Instruction::GetPublicSpendKey => handler_get_public_spend_key(comm), + Instruction::GetScriptSignature => handler_get_script_signature(comm), + Instruction::GetScriptOffset { chunk, more } => handler_get_script_offset(comm, chunk, more, offset_ctx), + Instruction::GetScriptSignatureFromChallenge => handler_get_script_signature_from_challenge(comm), + Instruction::GetViewKey => handler_get_view_key(comm), + Instruction::GetDHSharedSecret => handler_get_dh_shared_secret(comm), + } +} diff --git a/applications/minotari_ledger_wallet/wallet/src/utils.rs b/applications/minotari_ledger_wallet/wallet/src/utils.rs new file mode 100644 index 0000000000..9441768268 --- /dev/null +++ b/applications/minotari_ledger_wallet/wallet/src/utils.rs @@ -0,0 +1,260 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use alloc::format; +use core::ops::Deref; + +use blake2::Blake2b; +use digest::{consts::U64, Digest}; +use ledger_device_sdk::{ + ecc::{bip32_derive, make_bip32_path, CurvesId, CxError}, + io::SyscallError, + ui::gadgets::SingleMessage, +}; +use tari_crypto::{ + hashing::DomainSeparatedHasher, + keys::SecretKey, + ristretto::RistrettoSecretKey, + tari_utilities::ByteArray, +}; +use tari_hashing::{KeyManagerTransactionsHashDomain, LedgerHashDomain}; +use zeroize::Zeroizing; + +use crate::{ + alloc::string::{String, ToString}, + AppSW, + KeyType, + BIP32_COIN_TYPE, +}; + +/// BIP32 path stored as an array of [`u32`]. +/// +/// # Generic arguments +/// +/// * `S` - Maximum possible path length, i.e. the capacity of the internal buffer. +pub struct Bip32Path { + buffer: [u32; S], + len: usize, +} + +impl AsRef<[u32]> for Bip32Path { + fn as_ref(&self) -> &[u32] { + &self.buffer[..self.len] + } +} + +impl Default for Bip32Path { + fn default() -> Self { + Self { + buffer: [0u32; S], + len: 0, + } + } +} + +impl TryFrom<&[u8]> for Bip32Path { + type Error = AppSW; + + /// Constructs a [`Bip32Path`] from a given byte array. + /// + /// This method will return an error in the following cases: + /// - the input array is empty, + /// - the number of bytes in the input array is not a multiple of 4, + /// - the input array exceeds the capacity of the [`Bip32Path`] internal buffer. + /// + /// # Arguments + /// + /// * `data` - Encoded BIP32 path. First byte is the length of the path, as encoded by ragger. + fn try_from(data: &[u8]) -> Result { + let input_path_len = (data.len() - 1) / 4; + // Check data length + if data.is_empty() // At least the length byte is required + || (input_path_len > S) + || (data[0] as usize * 4 != data.len() - 1) + { + return Err(AppSW::WrongApduLength); + } + + let mut path = [0; S]; + for (chunk, p) in data[1..].chunks(4).zip(path.iter_mut()) { + *p = u32::from_be_bytes(chunk.try_into().unwrap()); + } + + Ok(Self { + buffer: path, + len: input_path_len, + }) + } +} + +/// Convert a u64 to a string without using the standard library +pub fn u64_to_string(number: u64) -> String { + let mut buffer = [0u8; 20]; // Maximum length for a 64-bit integer (including null terminator) + let mut pos = 0; + + if number == 0 { + buffer[pos] = b'0'; + pos += 1; + } else { + let mut num = number; + + let mut digits = [0u8; 20]; + let mut num_digits = 0; + + while num > 0 { + digits[num_digits] = b'0' + (num % 10) as u8; + num /= 10; + num_digits += 1; + } + + while num_digits > 0 { + num_digits -= 1; + buffer[pos] = digits[num_digits]; + pos += 1; + } + } + + String::from_utf8_lossy(&buffer[..pos]).to_string() +} + +// Convert CxError to a string for display +fn cx_error_to_string(e: CxError) -> String { + let err = match e { + CxError::Carry => "Carry", + CxError::Locked => "Locked", + CxError::Unlocked => "Unlocked", + CxError::NotLocked => "NotLocked", + CxError::NotUnlocked => "NotUnlocked", + CxError::InternalError => "InternalError", + CxError::InvalidParameterSize => "InvalidParameterSize", + CxError::InvalidParameterValue => "InvalidParameterValue", + CxError::InvalidParameter => "InvalidParameter", + CxError::NotInvertible => "NotInvertible", + CxError::Overflow => "Overflow", + CxError::MemoryFull => "MemoryFull", + CxError::NoResidue => "NoResidue", + CxError::PointAtInfinity => "PointAtInfinity", + CxError::InvalidPoint => "InvalidPoint", + CxError::InvalidCurve => "InvalidCurve", + CxError::GenericError => "GenericError", + }; + err.to_string() +} + +// Get a raw 32 byte key hash from the BIP32 path. +// - The wrapper function for the syscall `os_perso_derive_node_bip32`, `bip32_derive`, requires a 96 byte buffer when +// called with `CurvesId::Ed25519` as it checks the consistency of the curve choice and key length in order to prevent +// the underlying syscall from panicking. +// - The syscall `os_perso_derive_node_bip32` returns 96 bytes as: +// private key: 64 bytes +// chain: 32 bytes +// Example: +// d8a57c1be0c52e9643485e77aac56d72fa6c4eb831466c2abd2d320c82d3d14929811c598c13d431bad433e037dbd97265492cea42bc2e3aad15440210a20a2d0000000000000000000000000000000000000000000000000000000000000000 +// - This function applies domain separated hashing to the 64 byte private key of the returned buffer to get 32 +// uniformly distributed random bytes. +fn get_raw_key_hash(path: &[u32]) -> Result, String> { + let mut key_buffer = Zeroizing::new([0u8; 96]); + const BIP32_KEY_LENGTH: usize = 64; + let raw_key_64 = match bip32_derive(CurvesId::Ed25519, path, key_buffer.as_mut(), Some(&mut [])) { + Ok(_) => { + let binding = &key_buffer.as_ref()[..BIP32_KEY_LENGTH]; + let mut key_bytes = Zeroizing::new([0u8; BIP32_KEY_LENGTH]); + // `copy_from_slice` will not panic as the length of the slice is equal to the length of the array + key_bytes.as_mut().copy_from_slice(binding); + key_bytes + }, + Err(e) => return Err(cx_error_to_string(e)), + }; + + let mut raw_key_hashed = Zeroizing::new([0u8; 64]); + DomainSeparatedHasher::, LedgerHashDomain>::new_with_label("raw_key") + .chain(&raw_key_64.as_ref()) + .finalize_into(raw_key_hashed.as_mut().into()); + + Ok(raw_key_hashed) +} + +/// Get a raw 32 byte key hash from the BIP32 path. In cas of an error, display an interactive message on the device. +pub fn get_raw_key(path: &[u32]) -> Result, SyscallError> { + match get_raw_key_hash(&path) { + Ok(val) => Ok(val), + Err(e) => { + let mut msg = "".to_string(); + msg.push_str("Err: raw key >>..."); + SingleMessage::new(&msg).show_and_wait(); + SingleMessage::new(&e).show_and_wait(); + Err(SyscallError::InvalidParameter.into()) + }, + } +} + +pub fn get_key_from_uniform_bytes(bytes: &[u8]) -> Result, AppSW> { + match RistrettoSecretKey::from_uniform_bytes(bytes) { + Ok(val) => Ok(Zeroizing::new(val)), + Err(e) => { + SingleMessage::new(&format!( + "Err: key conversion {:?}. Length: {:?}", + e.to_string(), + &bytes.len() + )) + .show_and_wait(); + SingleMessage::new(&format!("Error Length: {:?}", &bytes.len())).show_and_wait(); + return Err(AppSW::KeyDeriveFromUniform); + }, + } +} + +pub fn get_key_from_canonical_bytes(bytes: &[u8]) -> Result { + match T::from_canonical_bytes(bytes) { + Ok(val) => Ok(val), + Err(e) => { + SingleMessage::new(&format!( + "Err: key conversion {:?}. Length: {:?}", + e.to_string(), + &bytes.len() + )) + .show_and_wait(); + SingleMessage::new(&format!("Error Length: {:?}", &bytes.len())).show_and_wait(); + return Err(AppSW::KeyDeriveFromCanonical); + }, + } +} + +pub fn alpha_hasher( + alpha: Zeroizing, + blinding_factor: Zeroizing, +) -> Result, AppSW> { + let hasher = DomainSeparatedHasher::, KeyManagerTransactionsHashDomain>::new_with_label("script key"); + let hasher = hasher.chain(blinding_factor.as_bytes()).finalize(); + let private_key = get_key_from_uniform_bytes(hasher.as_ref())?; + + Ok(Zeroizing::new(private_key.deref() + alpha.deref())) +} + +pub fn derive_from_bip32_key( + u64_account: u64, + u64_index: u64, + u64_key_type: KeyType, +) -> Result, AppSW> { + let account = u64_to_string(u64_account); + let index = u64_to_string(u64_index); + let key_type = u64_to_string(u64_key_type.as_byte() as u64); + + let mut bip32_path = "m/44'/".to_string(); + bip32_path.push_str(&BIP32_COIN_TYPE.to_string()); + bip32_path.push_str(&"'/"); + bip32_path.push_str(&account); + bip32_path.push_str(&"'/0/"); + bip32_path.push_str(&index); + bip32_path.push_str(&"'/"); + bip32_path.push_str(&key_type); + let path: [u32; 6] = make_bip32_path(bip32_path.as_bytes()); + + match get_raw_key(&path) { + Ok(val) => get_key_from_uniform_bytes(&val.as_ref()), + Err(e) => { + SingleMessage::new(&format!("Key error {:?}", e)).show_and_wait(); + return Err(AppSW::KeyDeriveFail); + }, + } +} diff --git a/applications/minotari_merge_mining_proxy/Cargo.toml b/applications/minotari_merge_mining_proxy/Cargo.toml index 5ccda43132..e11f067cd2 100644 --- a/applications/minotari_merge_mining_proxy/Cargo.toml +++ b/applications/minotari_merge_mining_proxy/Cargo.toml @@ -34,7 +34,7 @@ hex = "0.4.2" hyper = "0.14.12" jsonrpc = "0.12.0" log = { version = "0.4.8", features = ["std"] } -monero = { version = "0.20.0" } +monero = { version = "0.21.0" } reqwest = { version = "0.11.4", features = ["json"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.57" diff --git a/applications/minotari_merge_mining_proxy/src/block_template_data.rs b/applications/minotari_merge_mining_proxy/src/block_template_data.rs index fa90b116e2..14edb3619f 100644 --- a/applications/minotari_merge_mining_proxy/src/block_template_data.rs +++ b/applications/minotari_merge_mining_proxy/src/block_template_data.rs @@ -26,7 +26,7 @@ use std::{collections::HashMap, convert::TryFrom, sync::Arc}; #[cfg(not(test))] use chrono::Duration; -use chrono::{self, DateTime, Utc}; +use chrono::{DateTime, Utc}; use minotari_node_grpc_client::grpc; use tari_common_types::types::FixedHash; use tari_core::proof_of_work::monero_rx::FixedByteArray; @@ -221,6 +221,7 @@ pub struct BlockTemplateData { pub monero_difficulty: u64, pub tari_difficulty: u64, pub tari_merge_mining_hash: FixedHash, + #[allow(dead_code)] pub aux_chain_hashes: Vec, pub new_block_template: grpc::NewBlockTemplate, } diff --git a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs index 08db7354ac..5ffff16c9f 100644 --- a/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs +++ b/applications/minotari_merge_mining_proxy/src/block_template_protocol.rs @@ -33,7 +33,7 @@ use tari_core::{ transactions::{ generate_coinbase, key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, - transaction_components::{TransactionKernel, TransactionOutput}, + transaction_components::{encrypted_data::PaymentId, TransactionKernel, TransactionOutput}, }, }; use tari_utilities::{hex::Hex, ByteArray}; @@ -63,7 +63,7 @@ impl<'a> BlockTemplateProtocol<'a> { consensus_manager: ConsensusManager, wallet_payment_address: TariAddress, ) -> Result, MmProxyError> { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager()?; Ok(Self { config, base_node_client, @@ -271,7 +271,7 @@ impl BlockTemplateProtocol<'_> { let grpc::NewBlockTemplateResponse { miner_data, new_block_template: template, - initial_sync_achieved, + initial_sync_achieved: _, } = self .base_node_client .get_new_block_template(grpc::NewBlockTemplateRequest { @@ -289,11 +289,7 @@ impl BlockTemplateProtocol<'_> { let miner_data = miner_data.ok_or(MmProxyError::GrpcResponseMissingField("miner_data"))?; let template = template.ok_or(MmProxyError::GrpcResponseMissingField("new_block_template"))?; - Ok(NewBlockTemplateData { - template, - miner_data, - initial_sync_achieved, - }) + Ok(NewBlockTemplateData { template, miner_data }) } /// Check if the height is more than the actual tip. So if still makes sense to compute block for that height. @@ -335,9 +331,10 @@ impl BlockTemplateProtocol<'_> { self.config.coinbase_extra.as_bytes(), &self.key_manager, &self.wallet_payment_address, - self.config.stealth_payment, + true, self.consensus_manager.consensus_constants(tari_height), self.config.range_proof_type, + PaymentId::Empty, ) .await?; Ok((coinbase_output, coinbase_kernel)) @@ -438,7 +435,6 @@ fn add_monero_data( pub struct NewBlockTemplateData { pub template: grpc::NewBlockTemplate, pub miner_data: grpc::MinerData, - pub initial_sync_achieved: bool, } impl NewBlockTemplateData { diff --git a/applications/minotari_merge_mining_proxy/src/config.rs b/applications/minotari_merge_mining_proxy/src/config.rs index cc8d75b505..6c4135b20f 100644 --- a/applications/minotari_merge_mining_proxy/src/config.rs +++ b/applications/minotari_merge_mining_proxy/src/config.rs @@ -89,8 +89,6 @@ pub struct MergeMiningProxyConfig { pub config_dir: PathBuf, /// The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned pub wallet_payment_address: String, - /// Stealth payment yes or no - pub stealth_payment: bool, /// Range proof type - revealed_value or bullet_proof_plus: (default = revealed_value) pub range_proof_type: RangeProofType, } @@ -117,8 +115,7 @@ impl Default for MergeMiningProxyConfig { coinbase_extra: "tari_merge_mining_proxy".to_string(), network: Default::default(), config_dir: PathBuf::from("config/merge_mining_proxy"), - wallet_payment_address: TariAddress::default().to_hex(), - stealth_payment: true, + wallet_payment_address: TariAddress::default().to_base58(), range_proof_type: RangeProofType::RevealedValue, } } diff --git a/applications/minotari_merge_mining_proxy/src/monero_fail.rs b/applications/minotari_merge_mining_proxy/src/monero_fail.rs index 1065ce1c80..fb385b003a 100644 --- a/applications/minotari_merge_mining_proxy/src/monero_fail.rs +++ b/applications/minotari_merge_mining_proxy/src/monero_fail.rs @@ -43,10 +43,13 @@ pub struct MonerodEntry { /// Whether the server is currently up pub up: bool, /// Whether the server is web compatible + #[allow(dead_code)] pub web_compatible: bool, /// The network the server is on (mainnet, stagenet, testnet) + #[allow(dead_code)] pub network: String, /// Time since the server was checked + #[allow(dead_code)] pub last_checked: String, /// The history of the server being up pub up_history: Vec, diff --git a/applications/minotari_merge_mining_proxy/src/proxy.rs b/applications/minotari_merge_mining_proxy/src/proxy.rs index ccbcb7c6de..117e30a9e1 100644 --- a/applications/minotari_merge_mining_proxy/src/proxy.rs +++ b/applications/minotari_merge_mining_proxy/src/proxy.rs @@ -24,6 +24,7 @@ use std::{ cmp, convert::TryInto, future::Future, + panic, pin::Pin, sync::{ atomic::{AtomicBool, Ordering}, @@ -642,12 +643,24 @@ impl InnerService { }; // Query the list twice before giving up, starting after the last used entry - let pos = if let Some(index) = self.config.monerod_url.iter().position(|x| x == &last_used_url) { - index - } else { - 0 + let pos = self + .config + .monerod_url + .iter() + .position(|x| x == &last_used_url) + .unwrap_or(0); + // replace after rust stable 1.80 release + // let (left, right) = self + // .config + // .monerod_url + // .split_at_checked(pos) + // .ok_or(MmProxyError::ConversionError("Invalid utf 8 url".to_string()))?; + let url = self.config.monerod_url.clone(); + let result = panic::catch_unwind(|| url.split_at(pos)); + let (left, right) = match result { + Ok((left, right)) => (left, right), + Err(_) => return Err(MmProxyError::ConversionError("Invalid utf 8 url".to_string())), }; - let (left, right) = self.config.monerod_url.split_at(pos); let left = left.to_vec(); let right = right.to_vec(); let iter = right.iter().chain(left.iter()).chain(right.iter()).chain(left.iter()); @@ -858,15 +871,24 @@ impl InnerService { return Ok(monerod_resp.map(|json| json.to_string().into())); } - let response = self.get_proxy_response(request, monerod_resp).await?; - debug!( - "Method: {}, MoneroD Status: {}, Proxy Status: {}, Response Time: {}ms", - method_name, - monerod_status, - response.status(), - start.elapsed().as_millis() - ); - Ok(response) + match self.get_proxy_response(request, monerod_resp).await { + Ok(response) => { + debug!( + "Method: {}, MoneroD Status: {}, Proxy Status: {}, Response Time: {}ms", + method_name, + monerod_status, + response.status(), + start.elapsed().as_millis() + ); + Ok(response) + }, + Err(e) => { + // Monero Server encountered a problem processing the request, reset the current monerod server + let mut lock = self.current_monerod_server.write().expect("Write lock should not fail"); + *lock = None; + Err(e) + }, + } }, Err(e) => { // Monero Server encountered a problem processing the request, reset the current monerod server diff --git a/applications/minotari_miner/Cargo.toml b/applications/minotari_miner/Cargo.toml index 0ceafcf722..88ca253ba4 100644 --- a/applications/minotari_miner/Cargo.toml +++ b/applications/minotari_miner/Cargo.toml @@ -28,14 +28,14 @@ derivative = "2.2.0" futures = "0.3" hex = "0.4.2" log = { version = "0.4", features = ["std"] } -log4rs = { version = "1.3.0", default_features = false, features = ["config_parsing", "threshold_filter", "yaml_format", "console_appender", "rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } +log4rs = { version = "1.3.0", default-features = false, features = ["config_parsing", "threshold_filter", "yaml_format", "console_appender", "rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } native-tls = "0.2" num_cpus = "1.13" rand = "0.8" -serde = { version = "1.0", default_features = false, features = ["derive"] } +serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = "1.0.57" thiserror = "1.0" -tokio = { version = "1.36", default_features = false, features = ["rt-multi-thread"] } +tokio = { version = "1.36", default-features = false, features = ["rt-multi-thread"] } tonic = { version = "0.8.3", features = ["tls", "tls-roots"] } [dev-dependencies] diff --git a/applications/minotari_miner/README.md b/applications/minotari_miner/README.md index 37cc22b185..be11f2b676 100644 --- a/applications/minotari_miner/README.md +++ b/applications/minotari_miner/README.md @@ -33,7 +33,6 @@ Configuration options for the Minotari Miner are as follows: - `network` - "Selected network" - `wait_timeout_on_error` - "Base node reconnect timeout after any gRPC or miner error" - `wallet_payment_address` - "The Tari wallet address where the mining funds will be sent to" -- `stealth_payment` - "Stealth payment yes or no" ### Caveats diff --git a/applications/minotari_miner/build.rs b/applications/minotari_miner/build.rs index b1eafc00aa..d89d53009e 100644 --- a/applications/minotari_miner/build.rs +++ b/applications/minotari_miner/build.rs @@ -1,5 +1,24 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause +// Copyright 2024. 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. #[cfg(windows)] fn main() { diff --git a/applications/minotari_miner/linux/runtime/start_minotari_miner.sh b/applications/minotari_miner/linux/runtime/start_minotari_miner.sh index 988005bc40..f8b045a039 100755 --- a/applications/minotari_miner/linux/runtime/start_minotari_miner.sh +++ b/applications/minotari_miner/linux/runtime/start_minotari_miner.sh @@ -1,4 +1,28 @@ #!/bin/bash +# +# Copyright 2024. 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. +# + # echo echo "Starting Miner" diff --git a/applications/minotari_miner/linux/start_minotari_miner b/applications/minotari_miner/linux/start_minotari_miner index 5c74b19d12..8a971d2c1b 120000 --- a/applications/minotari_miner/linux/start_minotari_miner +++ b/applications/minotari_miner/linux/start_minotari_miner @@ -1 +1 @@ -./runtime/start_tari_miner.sh \ No newline at end of file +./runtime/start_minotari_miner.sh \ No newline at end of file diff --git a/applications/minotari_miner/osx/runtime/start_minotari_miner.sh b/applications/minotari_miner/osx/runtime/start_minotari_miner.sh index eb64726798..57cb14457c 100755 --- a/applications/minotari_miner/osx/runtime/start_minotari_miner.sh +++ b/applications/minotari_miner/osx/runtime/start_minotari_miner.sh @@ -1,4 +1,28 @@ #!/bin/bash +# +# Copyright 2024. 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. +# + # echo echo "Starting Miner" diff --git a/applications/minotari_miner/osx/start_minotari_miner b/applications/minotari_miner/osx/start_minotari_miner index 5c74b19d12..8a971d2c1b 120000 --- a/applications/minotari_miner/osx/start_minotari_miner +++ b/applications/minotari_miner/osx/start_minotari_miner @@ -1 +1 @@ -./runtime/start_tari_miner.sh \ No newline at end of file +./runtime/start_minotari_miner.sh \ No newline at end of file diff --git a/applications/minotari_miner/src/cli.rs b/applications/minotari_miner/src/cli.rs index 1c5f339acc..d8ef4382d0 100644 --- a/applications/minotari_miner/src/cli.rs +++ b/applications/minotari_miner/src/cli.rs @@ -1,4 +1,4 @@ -// Copyright 2022. The Tari Project +// Copyright 2024. The Tari Project // // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the // following conditions are met: diff --git a/applications/minotari_miner/src/config.rs b/applications/minotari_miner/src/config.rs index 2c0412ae63..1467e23a4c 100644 --- a/applications/minotari_miner/src/config.rs +++ b/applications/minotari_miner/src/config.rs @@ -1,38 +1,32 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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. -// -//! Miner specific configuration -//! -//! Minotari Miner Node derives all configuration management +// 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. Minotari Miner Node derives all +// configuration management //! from [tari_common] crate, also extending with few //! specific options: -//! - base_node_grpc_address - is IPv4/IPv6 address including port -//! number, by which Minotari Base Node can be found +//! - base_node_grpc_address - is IPv4/IPv6 address including port number, by which Minotari Base Node can be found //! - num_mining_threads - number of mining threads, defaults to number of cpu cores //! - mine_on_tip_only - will start mining only when node is reporting bootstrapped state -//! - validate_tip_timeout_sec - will check tip with node every N seconds to validate that still -//! mining on a tip -//! All miner options configured under `[miner]` section of -//! Minotari's `config.toml`. +//! - validate_tip_timeout_sec - will check tip with node every N seconds to validate that still mining on a tip All +//! miner options configured under `[miner]` section of Minotari's `config.toml`. use std::{ path::{Path, PathBuf}, @@ -85,10 +79,10 @@ pub struct MinerConfig { pub config_dir: PathBuf, /// The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned pub wallet_payment_address: String, - /// Stealth payment yes or no - pub stealth_payment: bool, /// Range proof type - revealed_value or bullet_proof_plus: (default = revealed_value) pub range_proof_type: RangeProofType, + /// SHA based p2pool decentralized mining enabled or not + pub sha_p2pool_enabled: bool, } /// The proof of work data structure that is included in the block header. For the Minotari miner only `Sha3x` is @@ -123,9 +117,9 @@ impl Default for MinerConfig { network: Default::default(), wait_timeout_on_error: 10, config_dir: PathBuf::from("config/miner"), - wallet_payment_address: TariAddress::default().to_hex(), - stealth_payment: true, + wallet_payment_address: TariAddress::default().to_base58(), range_proof_type: RangeProofType::RevealedValue, + sha_p2pool_enabled: false, } } } diff --git a/applications/minotari_miner/src/difficulty.rs b/applications/minotari_miner/src/difficulty.rs index e2a5213d2d..fa7af277ef 100644 --- a/applications/minotari_miner/src/difficulty.rs +++ b/applications/minotari_miner/src/difficulty.rs @@ -1,24 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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. +// 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; diff --git a/applications/minotari_miner/src/errors.rs b/applications/minotari_miner/src/errors.rs index 2f5226c47a..ac08b6a75b 100644 --- a/applications/minotari_miner/src/errors.rs +++ b/applications/minotari_miner/src/errors.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 minotari_app_grpc::authentication::BasicAuthError; use minotari_app_utilities::parse_miner_input::ParseInputError; use thiserror::Error; diff --git a/applications/minotari_miner/src/lib.rs b/applications/minotari_miner/src/lib.rs index cc5f6b1cd4..90f1db7f0b 100644 --- a/applications/minotari_miner/src/lib.rs +++ b/applications/minotari_miner/src/lib.rs @@ -1,24 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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. +// 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. // non-64-bit not supported minotari_app_utilities::deny_non_64_bit_archs!(); diff --git a/applications/minotari_miner/src/main.rs b/applications/minotari_miner/src/main.rs index 795a4446a8..780a0c605b 100644 --- a/applications/minotari_miner/src/main.rs +++ b/applications/minotari_miner/src/main.rs @@ -1,24 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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. +// 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::io::stdout; diff --git a/applications/minotari_miner/src/miner.rs b/applications/minotari_miner/src/miner.rs index ba42c85cb3..a4591778be 100644 --- a/applications/minotari_miner/src/miner.rs +++ b/applications/minotari_miner/src/miner.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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::{ panic::panic_any, pin::Pin, @@ -57,7 +56,6 @@ pub struct MiningReport { /// Will be set for when mined header is matching required difficulty pub header: Option, pub height: u64, - pub last_nonce: u64, } /// Miner is starting number of mining threads and implements Stream for async reports polling @@ -213,7 +211,6 @@ pub fn mining_task( hashes: hasher.hashes, elapsed: start.elapsed(), height: hasher.height(), - last_nonce: hasher.header.nonce, header: Some(hasher.create_header()), target_difficulty, }) { @@ -236,7 +233,6 @@ pub fn mining_task( hashes: hasher.hashes, elapsed: start.elapsed(), header: None, - last_nonce: hasher.header.nonce, height: hasher.height(), target_difficulty, }); diff --git a/applications/minotari_miner/src/run_miner.rs b/applications/minotari_miner/src/run_miner.rs index e3e41cd4e2..d986276ef9 100644 --- a/applications/minotari_miner/src/run_miner.rs +++ b/applications/minotari_miner/src/run_miner.rs @@ -1,32 +1,40 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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. +// 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 std::{convert::TryFrom, str::FromStr, sync::Arc, thread, time::Instant}; use futures::stream::StreamExt; use log::*; use minotari_app_grpc::{ authentication::ClientAuthenticationInterceptor, - tari_rpc::{base_node_client::BaseNodeClient, TransactionOutput as GrpcTransactionOutput}, + tari_rpc::{ + base_node_client::BaseNodeClient, + sha_p2_pool_client::ShaP2PoolClient, + Block, + GetNewBlockRequest, + SubmitBlockRequest, + SubmitBlockResponse, + TransactionOutput as GrpcTransactionOutput, + }, tls::protocol_string, }; use minotari_app_utilities::parse_miner_input::{ @@ -34,6 +42,7 @@ use minotari_app_utilities::parse_miner_input::{ verify_base_node_grpc_mining_responses, wallet_payment_address, BaseNodeGrpcClient, + ShaP2PoolGrpcClient, }; use tari_common::{ exit_codes::{ExitCode, ExitError}, @@ -48,11 +57,12 @@ use tari_core::{ generate_coinbase, key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, tari_amount::MicroMinotari, + transaction_components::encrypted_data::PaymentId, }, }; use tari_crypto::ristretto::RistrettoPublicKey; use tari_utilities::hex::Hex; -use tokio::time::sleep; +use tokio::{sync::Mutex, time::sleep}; use tonic::transport::{Certificate, ClientTlsConfig, Endpoint}; use crate::{ @@ -74,7 +84,12 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { config.set_base_path(cli.common.get_base_path()); debug!(target: LOG_TARGET_FILE, "{:?}", config); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().map_err(|err| { + ExitError::new( + ExitCode::KeyManagerServiceError, + "'wallet_payment_address' ".to_owned() + &err.to_string(), + ) + })?; let wallet_payment_address = wallet_payment_address(config.wallet_payment_address.clone(), config.network) .map_err(|err| { ExitError::new( @@ -125,10 +140,13 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { Ok(()) } else { - let mut node_conn = connect(&config) + let node_clients = connect(&config) .await .map_err(|e| ExitError::new(ExitCode::GrpcError, e.to_string()))?; - if let Err(e) = verify_base_node_responses(&mut node_conn, &config).await { + let mut base_node_client = node_clients.base_node_client; + let mut p2pool_node_client = node_clients.p2pool_node_client; + + if let Err(e) = verify_base_node_responses(&mut base_node_client, &config).await { if let MinerError::BaseNodeNotResponding(_) = e { error!(target: LOG_TARGET, "{}", e.to_string()); println!(); @@ -146,7 +164,8 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { loop { debug!(target: LOG_TARGET, "Starting new mining cycle"); match mining_cycle( - &mut node_conn, + &mut base_node_client, + p2pool_node_client.clone(), &config, &cli, &key_manager, @@ -163,7 +182,8 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { sleep(config.wait_timeout()).await; match connect(&config).await { Ok(nc) => { - node_conn = nc; + base_node_client = nc.base_node_client; + p2pool_node_client = nc.p2pool_node_client; break; }, Err(err) => { @@ -206,8 +226,14 @@ pub async fn start_miner(cli: Cli) -> Result<(), ExitError> { } } -async fn connect(config: &MinerConfig) -> Result { - let node_conn = match connect_base_node(config).await { +pub struct NodeClientResult { + base_node_client: BaseNodeGrpcClient, + p2pool_node_client: Option, +} + +async fn connect(config: &MinerConfig) -> Result { + // always connect to base node first + let base_node_client = match connect_base_node(config).await { Ok(client) => client, Err(e) => { error!(target: LOG_TARGET, "Could not connect to base node: {}", e); @@ -218,6 +244,59 @@ async fn connect(config: &MinerConfig) -> Result }, }; + // init client to sha p2pool grpc if enabled + let mut p2pool_node_client = None; + if config.sha_p2pool_enabled { + p2pool_node_client = match connect_sha_p2pool(config).await { + Ok(client) => Some(client), + Err(e) => { + error!(target: LOG_TARGET, "Could not connect to base node: {}", e); + let msg = "Could not connect to base node. \nIs the base node's gRPC running? Try running it with \ + `--enable-grpc` or enable it in the config."; + println!("{}", msg); + return Err(e); + }, + }; + } + + Ok(NodeClientResult { + base_node_client, + p2pool_node_client, + }) +} + +async fn connect_sha_p2pool(config: &MinerConfig) -> Result { + let socketaddr = base_node_socket_address(config.base_node_grpc_address.clone(), config.network)?; + let base_node_addr = format!( + "{}{}", + protocol_string(config.base_node_grpc_tls_domain_name.is_some()), + socketaddr, + ); + + info!(target: LOG_TARGET, "👛 Connecting to p2pool node at {}", base_node_addr); + let mut endpoint = Endpoint::from_str(&base_node_addr)?; + + if let Some(domain_name) = config.base_node_grpc_tls_domain_name.as_ref() { + let pem = tokio::fs::read(config.config_dir.join(&config.base_node_grpc_ca_cert_filename)) + .await + .map_err(|e| MinerError::TlsConnectionError(e.to_string()))?; + let ca = Certificate::from_pem(pem); + + let tls = ClientTlsConfig::new().ca_certificate(ca).domain_name(domain_name); + endpoint = endpoint + .tls_config(tls) + .map_err(|e| MinerError::TlsConnectionError(e.to_string()))?; + } + + let channel = endpoint + .connect() + .await + .map_err(|e| MinerError::TlsConnectionError(e.to_string()))?; + let node_conn = ShaP2PoolClient::with_interceptor( + channel, + ClientAuthenticationInterceptor::create(&config.base_node_grpc_authentication)?, + ); + Ok(node_conn) } @@ -266,17 +345,48 @@ async fn verify_base_node_responses( Ok(()) } -#[allow(clippy::too_many_lines)] -async fn mining_cycle( - node_conn: &mut BaseNodeGrpcClient, +struct GetNewBlockResponse { + block: Block, + target_difficulty: u64, +} + +/// Gets a new block from base node or p2pool node if its enabled in config +async fn get_new_block( + base_node_client: &mut BaseNodeGrpcClient, + sha_p2pool_client: Arc>>, config: &MinerConfig, cli: &Cli, key_manager: &MemoryDbKeyManager, wallet_payment_address: &TariAddress, consensus_manager: &ConsensusManager, -) -> Result { +) -> Result { + if config.sha_p2pool_enabled { + if let Some(client) = sha_p2pool_client.lock().await.as_mut() { + return get_new_block_p2pool_node(client).await; + } + } + + get_new_block_base_node( + base_node_client, + config, + cli, + key_manager, + wallet_payment_address, + consensus_manager, + ) + .await +} + +async fn get_new_block_base_node( + base_node_client: &mut BaseNodeGrpcClient, + config: &MinerConfig, + cli: &Cli, + key_manager: &MemoryDbKeyManager, + wallet_payment_address: &TariAddress, + consensus_manager: &ConsensusManager, +) -> Result { debug!(target: LOG_TARGET, "Getting new block template"); - let template_response = node_conn + let template_response = base_node_client .get_new_block_template(config.pow_algo_request()) .await? .into_inner(); @@ -295,7 +405,7 @@ async fn mining_cycle( target: LOG_TARGET, "Checking if base node is synced, because mine_on_tip_only is true" ); - validate_tip(node_conn, height, cli.mine_until_height).await?; + validate_tip(base_node_client, height, cli.mine_until_height).await?; } debug!(target: LOG_TARGET, "Getting coinbase"); @@ -309,9 +419,10 @@ async fn mining_cycle( config.coinbase_extra.as_bytes(), key_manager, wallet_payment_address, - config.stealth_payment, + true, consensus_manager.consensus_constants(height), config.range_proof_type, + PaymentId::Empty, ) .await .map_err(|e| MinerError::CoinbaseError(e.to_string()))?; @@ -328,12 +439,86 @@ async fn mining_cycle( let target_difficulty = 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 block_result = base_node_client.get_new_block(block_template).await?.into_inner(); + Ok(GetNewBlockResponse { + block: block_result.block.ok_or_else(|| err_empty("block"))?, + target_difficulty, + }) +} + +async fn get_new_block_p2pool_node( + sha_p2pool_client: &mut ShaP2PoolGrpcClient, +) -> Result { + let block_result = sha_p2pool_client + .get_new_block(GetNewBlockRequest::default()) + .await? + .into_inner(); + let new_block_result = block_result.block.ok_or_else(|| err_empty("block result"))?; + let block = new_block_result.block.ok_or_else(|| err_empty("block response"))?; + Ok(GetNewBlockResponse { + block, + target_difficulty: block_result.target_difficulty, + }) +} + +async fn submit_block( + config: &MinerConfig, + base_node_client: &mut BaseNodeGrpcClient, + sha_p2pool_client: Option<&mut ShaP2PoolGrpcClient>, + block: Block, + wallet_payment_address: &TariAddress, +) -> Result { + if config.sha_p2pool_enabled { + if let Some(client) = sha_p2pool_client { + return Ok(client + .submit_block(SubmitBlockRequest { + block: Some(block), + wallet_payment_address: wallet_payment_address.to_hex(), + }) + .await + .map_err(MinerError::GrpcStatus)? + .into_inner()); + } + } + + Ok(base_node_client + .submit_block(block) + .await + .map_err(MinerError::GrpcStatus)? + .into_inner()) +} + +#[allow(clippy::too_many_lines)] +async fn mining_cycle( + base_node_client: &mut BaseNodeGrpcClient, + sha_p2pool_client: Option, + config: &MinerConfig, + cli: &Cli, + key_manager: &MemoryDbKeyManager, + wallet_payment_address: &TariAddress, + consensus_manager: &ConsensusManager, +) -> Result { + let sha_p2pool_client = Arc::new(Mutex::new(sha_p2pool_client)); + let block_result = get_new_block( + base_node_client, + sha_p2pool_client.clone(), + config, + cli, + key_manager, + wallet_payment_address, + consensus_manager, + ) + .await?; + let block = block_result.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 reports = Miner::init_mining( + header.clone(), + block_result.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 { @@ -369,7 +554,14 @@ async fn mining_cycle( let mut mined_block = block.clone(); mined_block.header = Some(header); // 5. Sending block to the node - node_conn.submit_block(mined_block).await?; + submit_block( + config, + base_node_client, + sha_p2pool_client.lock().await.as_mut(), + mined_block, + wallet_payment_address, + ) + .await?; block_submitted = true; break; } else { @@ -379,7 +571,7 @@ async fn mining_cycle( 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?; + validate_tip(base_node_client, report.height, cli.mine_until_height).await?; reporting_timeout = Instant::now(); } } diff --git a/applications/minotari_miner/src/stratum/controller.rs b/applications/minotari_miner/src/stratum/controller.rs index e569253b5e..3fd2bcfe07 100644 --- a/applications/minotari_miner/src/stratum/controller.rs +++ b/applications/minotari_miner/src/stratum/controller.rs @@ -1,27 +1,25 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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::{ - self, io::{BufRead, ErrorKind, Write}, sync::mpsc, thread, diff --git a/applications/minotari_miner/src/stratum/error.rs b/applications/minotari_miner/src/stratum/error.rs index d2c2a24e03..ba802be771 100644 --- a/applications/minotari_miner/src/stratum/error.rs +++ b/applications/minotari_miner/src/stratum/error.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 thiserror::Error; diff --git a/applications/minotari_miner/src/stratum/mod.rs b/applications/minotari_miner/src/stratum/mod.rs index fc5c7534e8..3b82bfd181 100644 --- a/applications/minotari_miner/src/stratum/mod.rs +++ b/applications/minotari_miner/src/stratum/mod.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 controller; pub mod error; pub mod stratum_controller; diff --git a/applications/minotari_miner/src/stratum/stratum_controller/controller.rs b/applications/minotari_miner/src/stratum/stratum_controller/controller.rs index 6abe804d5c..14f20e691c 100644 --- a/applications/minotari_miner/src/stratum/stratum_controller/controller.rs +++ b/applications/minotari_miner/src/stratum/stratum_controller/controller.rs @@ -1,26 +1,25 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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::{self, convert::TryFrom, sync::mpsc, thread, time::SystemTime}; +// 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, sync::mpsc, thread, time::SystemTime}; use borsh::BorshDeserialize; use futures::stream::StreamExt; diff --git a/applications/minotari_miner/src/stratum/stratum_controller/mod.rs b/applications/minotari_miner/src/stratum/stratum_controller/mod.rs index ac9690447b..bebf4e8efd 100644 --- a/applications/minotari_miner/src/stratum/stratum_controller/mod.rs +++ b/applications/minotari_miner/src/stratum/stratum_controller/mod.rs @@ -1,23 +1,22 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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(crate) mod controller; diff --git a/applications/minotari_miner/src/stratum/stratum_types/client_message.rs b/applications/minotari_miner/src/stratum/stratum_types/client_message.rs index 417156e900..fe25165fee 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/client_message.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/client_message.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] diff --git a/applications/minotari_miner/src/stratum/stratum_types/job.rs b/applications/minotari_miner/src/stratum/stratum_types/job.rs index 17582c3891..fc331658be 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/job.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/job.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; use tari_core::blocks::Block; diff --git a/applications/minotari_miner/src/stratum/stratum_types/job_params.rs b/applications/minotari_miner/src/stratum/stratum_types/job_params.rs index 3074086252..8cd3a2ccac 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/job_params.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/job_params.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] diff --git a/applications/minotari_miner/src/stratum/stratum_types/login_params.rs b/applications/minotari_miner/src/stratum/stratum_types/login_params.rs index c16987f109..3bde37bdfb 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/login_params.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/login_params.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 derivative::Derivative; use serde::{Deserialize, Serialize}; @@ -28,6 +27,7 @@ use serde::{Deserialize, Serialize}; pub struct LoginParams { pub login: String, #[derivative(Debug = "ignore")] + #[allow(dead_code)] #[serde(skip_serializing)] pub pass: String, pub agent: String, diff --git a/applications/minotari_miner/src/stratum/stratum_types/login_response.rs b/applications/minotari_miner/src/stratum/stratum_types/login_response.rs index 4acda268fd..e6fe5981e2 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/login_response.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/login_response.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; use crate::stratum::stratum_types::job_params::JobParams; diff --git a/applications/minotari_miner/src/stratum/stratum_types/miner_message.rs b/applications/minotari_miner/src/stratum/stratum_types/miner_message.rs index 190293f5f4..a8a557be8e 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/miner_message.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/miner_message.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] diff --git a/applications/minotari_miner/src/stratum/stratum_types/mod.rs b/applications/minotari_miner/src/stratum/stratum_types/mod.rs index 4142b77f46..242f76c916 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/mod.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/mod.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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(crate) mod client_message; pub(crate) mod job; pub(crate) mod job_params; diff --git a/applications/minotari_miner/src/stratum/stratum_types/rpc_error.rs b/applications/minotari_miner/src/stratum/stratum_types/rpc_error.rs index 0b26a4891f..3044533aeb 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/rpc_error.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/rpc_error.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/applications/minotari_miner/src/stratum/stratum_types/rpc_request.rs b/applications/minotari_miner/src/stratum/stratum_types/rpc_request.rs index c5e12eb59f..8278166d26 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/rpc_request.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/rpc_request.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/applications/minotari_miner/src/stratum/stratum_types/rpc_response.rs b/applications/minotari_miner/src/stratum/stratum_types/rpc_response.rs index ac9393cb15..bad69732c2 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/rpc_response.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/rpc_response.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/applications/minotari_miner/src/stratum/stratum_types/submit_params.rs b/applications/minotari_miner/src/stratum/stratum_types/submit_params.rs index 526bc119b4..913a7d0b60 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/submit_params.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/submit_params.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] diff --git a/applications/minotari_miner/src/stratum/stratum_types/submit_response.rs b/applications/minotari_miner/src/stratum/stratum_types/submit_response.rs index 3114baa585..407c334a81 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/submit_response.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/submit_response.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; use crate::stratum::stratum_types::rpc_error::RpcError; diff --git a/applications/minotari_miner/src/stratum/stratum_types/worker_identifier.rs b/applications/minotari_miner/src/stratum/stratum_types/worker_identifier.rs index 9fcbbdf95a..f5cb80562f 100644 --- a/applications/minotari_miner/src/stratum/stratum_types/worker_identifier.rs +++ b/applications/minotari_miner/src/stratum/stratum_types/worker_identifier.rs @@ -1,25 +1,24 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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 serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] diff --git a/applications/minotari_miner/src/stratum/stream.rs b/applications/minotari_miner/src/stratum/stream.rs index 9ce8879055..dba1bc9e06 100644 --- a/applications/minotari_miner/src/stratum/stream.rs +++ b/applications/minotari_miner/src/stratum/stream.rs @@ -1,27 +1,25 @@ -// Copyright 2021. The Tari Project +// Copyright 2024. The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// 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. +// 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. +// 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. +// 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::{ - self, io::{self, BufRead, Read, Write}, net::TcpStream, }; diff --git a/applications/minotari_node/Cargo.toml b/applications/minotari_node/Cargo.toml index 7def30e63f..4141525473 100644 --- a/applications/minotari_node/Cargo.toml +++ b/applications/minotari_node/Cargo.toml @@ -39,7 +39,7 @@ futures = { version = "^0.3.16", default-features = false, features = ["alloc"] qrcode = { version = "0.12" } log = { version = "0.4.8", features = ["std"] } log-mdc = "0.1.0" -log4rs = { version = "1.3.0", default_features = false, features = ["config_parsing", "threshold_filter", "yaml_format", "console_appender", "rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } +log4rs = { version = "1.3.0", default-features = false, features = ["config_parsing", "threshold_filter", "yaml_format", "console_appender", "rolling_file_appender", "compound_policy", "size_trigger", "fixed_window_roller"] } nom = "7.1" rustyline = "9.0" rustyline-derive = "0.5" diff --git a/applications/minotari_node/src/bootstrap.rs b/applications/minotari_node/src/bootstrap.rs index a0d08fbbd0..0988fd7d2f 100644 --- a/applications/minotari_node/src/bootstrap.rs +++ b/applications/minotari_node/src/bootstrap.rs @@ -20,15 +20,10 @@ // 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::{ - cmp, - str::FromStr, - sync::{Arc, RwLock}, - time::Duration, -}; +use std::{cmp, str::FromStr, sync::Arc}; use log::*; -use minotari_app_utilities::{identity_management, identity_management::load_from_json}; +use minotari_app_utilities::{consts, identity_management, identity_management::load_from_json}; use tari_common::{ configuration::bootstrap::ApplicationType, exit_codes::{ExitCode, ExitError}, @@ -57,7 +52,6 @@ use tari_core::{ mempool::{service::MempoolHandle, Mempool, MempoolServiceInitializer, MempoolSyncInitializer}, proof_of_work::randomx_factory::RandomXFactory, transactions::CryptoFactories, - OutputSmt, }; use tari_p2p::{ auto_update::SoftwareUpdaterService, @@ -87,7 +81,6 @@ pub struct BaseNodeBootstrapper<'a, B> { pub factories: CryptoFactories, pub randomx_factory: RandomXFactory, pub interrupt_signal: ShutdownSignal, - pub smt: Arc>, } impl BaseNodeBootstrapper<'_, B> @@ -122,11 +115,12 @@ where B: BlockchainBackend + 'static let tor_identity = load_from_json(&base_node_config.tor_identity_file) .map_err(|e| ExitError::new(ExitCode::ConfigError, e))?; p2p_config.transport.tor.identity = tor_identity; - p2p_config.listener_liveness_check_interval = Some(Duration::from_secs(15)); + let user_agent = format!("tari/basenode/{}", consts::APP_VERSION_NUMBER); let mut handles = StackBuilder::new(self.interrupt_signal) .add_initializer(P2pInitializer::new( p2p_config.clone(), + user_agent, peer_seeds.clone(), base_node_config.network, self.node_identity.clone(), diff --git a/applications/minotari_node/src/builder.rs b/applications/minotari_node/src/builder.rs index 09d27335c0..171372b04d 100644 --- a/applications/minotari_node/src/builder.rs +++ b/applications/minotari_node/src/builder.rs @@ -265,7 +265,6 @@ async fn build_node_context( factories: factories.clone(), randomx_factory, interrupt_signal: interrupt_signal.clone(), - smt, } .bootstrap() .await?; diff --git a/applications/minotari_node/src/commands/command/get_peer.rs b/applications/minotari_node/src/commands/command/get_peer.rs index f9242244f9..39ec022d2d 100644 --- a/applications/minotari_node/src/commands/command/get_peer.rs +++ b/applications/minotari_node/src/commands/command/get_peer.rs @@ -81,14 +81,14 @@ impl CommandContext { }; for peer in peers { - let eid = EmojiId::from_public_key(&peer.public_key).to_emoji_string(); + let eid = EmojiId::from(&peer.public_key).to_string(); println!("Emoji ID: {}", eid); println!("Public Key: {}", peer.public_key); println!("NodeId: {}", peer.node_id); println!("Addresses:"); peer.addresses.addresses().iter().for_each(|a| { println!( - "- {} Score: {} - Source: {} Latency: {:?} - Last Seen: {} - Last Failure:{}", + "- {} Score: {:?} - Source: {} Latency: {:?} - Last Seen: {} - Last Failure:{}", a.address(), a.quality_score(), a.source(), diff --git a/applications/minotari_node/src/commands/command/status.rs b/applications/minotari_node/src/commands/command/status.rs index d32350d699..67b867ddf0 100644 --- a/applications/minotari_node/src/commands/command/status.rs +++ b/applications/minotari_node/src/commands/command/status.rs @@ -26,7 +26,8 @@ use anyhow::{anyhow, Error}; use async_trait::async_trait; use chrono::{DateTime, NaiveDateTime, Utc}; use clap::Parser; -use tari_comms::connection_manager::LivenessStatus; +use minotari_app_utilities::consts; +use tari_comms::connection_manager::SelfLivenessStatus; use tokio::time; use super::{CommandContext, HandleCommand}; @@ -58,7 +59,7 @@ impl CommandContext { } let mut status_line = StatusLine::new(); - status_line.add_field("", format!("v{}", env!("CARGO_PKG_VERSION"))); + status_line.add_field("", format!("v{}", consts::APP_VERSION_NUMBER)); status_line.add_field("", self.config.network()); status_line.add_field("State", self.state_machine_info.borrow().state_info.short_desc()); @@ -126,14 +127,14 @@ impl CommandContext { ); match self.comms.liveness_status() { - LivenessStatus::Disabled => {}, - LivenessStatus::Checking => { + SelfLivenessStatus::Disabled => {}, + SelfLivenessStatus::Checking => { status_line.add("⏳️️"); }, - LivenessStatus::Unreachable => { + SelfLivenessStatus::Unreachable => { status_line.add("️🔌"); }, - LivenessStatus::Live(latency) => { + SelfLivenessStatus::Live(latency) => { status_line.add(format!("⚡️ {:.2?}", latency)); }, } diff --git a/applications/minotari_node/src/commands/parser.rs b/applications/minotari_node/src/commands/parser.rs index 372c86b499..bbba15894c 100644 --- a/applications/minotari_node/src/commands/parser.rs +++ b/applications/minotari_node/src/commands/parser.rs @@ -36,10 +36,6 @@ use thiserror::Error; use super::command::Command; -#[derive(Debug, Error)] -#[error("invalid format '{0}'")] -pub struct FormatParseError(String); - #[derive(Debug, Display, EnumString)] #[strum(serialize_all = "kebab-case")] pub enum Format { diff --git a/applications/minotari_node/src/config.rs b/applications/minotari_node/src/config.rs index f433a30a21..ad0fe955ec 100644 --- a/applications/minotari_node/src/config.rs +++ b/applications/minotari_node/src/config.rs @@ -148,7 +148,6 @@ impl Default for BaseNodeConfig { fn default() -> Self { let p2p = P2pConfig { datastore_path: PathBuf::from("peer_db/base_node"), - user_agent: format!("tari/basenode/{}", env!("CARGO_PKG_VERSION")), ..Default::default() }; Self { diff --git a/applications/minotari_node/src/grpc/base_node_grpc_server.rs b/applications/minotari_node/src/grpc/base_node_grpc_server.rs index 40e4936f4b..034bb834bd 100644 --- a/applications/minotari_node/src/grpc/base_node_grpc_server.rs +++ b/applications/minotari_node/src/grpc/base_node_grpc_server.rs @@ -61,6 +61,7 @@ use tari_core::{ TxoStage, }, transaction_components::{ + encrypted_data::PaymentId, KernelBuilder, RangeProofType, Transaction, @@ -235,10 +236,12 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let (start_height, end_height) = get_heights(&request, handler.clone()) .await .map_err(|e| obscure_error_if_true(report_error_flag, e))?; - let num_requested = end_height.checked_sub(start_height).ok_or(obscure_error_if_true( - report_error_flag, - Status::invalid_argument("Start height is more than end height"), - ))?; + let num_requested = end_height.checked_sub(start_height).ok_or_else(|| { + obscure_error_if_true( + report_error_flag, + Status::invalid_argument("Start height is more than end height"), + ) + })?; if num_requested > GET_DIFFICULTY_MAX_HEIGHTS { return Err(obscure_error_if_true( report_error_flag, @@ -788,30 +791,48 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let mut coinbases: Vec = request.coinbases; // let validate the coinbase amounts; - let reward = self - .consensus_rules - .calculate_coinbase_and_fees(new_template.header.height, new_template.body.kernels()) + let reward = u128::from( + self.consensus_rules + .calculate_coinbase_and_fees(new_template.header.height, new_template.body.kernels()) + .map_err(|_| { + obscure_error_if_true( + report_error_flag, + Status::internal("Could not calculate the amount of fees in the block".to_string()), + ) + })? + .as_u64(), + ); + let mut total_shares = 0u128; + for coinbase in &coinbases { + total_shares += u128::from(coinbase.value); + } + let mut cur_share_sum = 0u128; + let mut prev_coinbase_value = 0u128; + for coinbase in &mut coinbases { + cur_share_sum += u128::from(coinbase.value); + coinbase.value = u64::try_from( + (cur_share_sum.saturating_mul(reward)) + .checked_div(total_shares) + .ok_or_else(|| { + obscure_error_if_true(report_error_flag, Status::internal("total shares are zero".to_string())) + })? - + prev_coinbase_value, + ) .map_err(|_| { obscure_error_if_true( report_error_flag, - Status::internal("Could not calculate the amount of fees in the block".to_string()), + Status::internal("Single coinbase fees exceeded u64".to_string()), ) - })? - .as_u64(); - let mut total_shares = 0u64; - for coinbase in &coinbases { - total_shares += coinbase.value; - } - let mut remainder = reward - ((reward / total_shares) * total_shares); - for coinbase in &mut coinbases { - coinbase.value *= reward / total_shares; - if remainder > 0 { - coinbase.value += 1; - remainder -= 1; - } + })?; + prev_coinbase_value = u128::from(coinbase.value); } - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().map_err(|e| { + obscure_error_if_true( + report_error_flag, + Status::internal(format!("Key manager error: '{}'", e)), + ) + })?; let height = new_template.header.height; // The script key is not used in the Diffie-Hellmann protocol, so we assign default. let script_key_id = TariKeyId::default(); @@ -822,7 +843,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let mut kernel_message = [0; 32]; let mut last_kernel = Default::default(); for coinbase in coinbases { - let address = TariAddress::from_hex(&coinbase.address) + let address = TariAddress::from_base58(&coinbase.address) .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; let range_proof_type = if coinbase.revealed_value_proof { RangeProofType::RevealedValue @@ -840,17 +861,18 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { coinbase.stealth_payment, self.consensus_rules.consensus_constants(height), range_proof_type, + PaymentId::Empty, ) .await .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; new_template.body.add_output(coinbase_output); - let (new_private_nonce, pub_nonce) = key_manager + let new_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; - total_nonce = &total_nonce + &pub_nonce; + total_nonce = &total_nonce + &new_nonce.pub_key; total_excess = &total_excess + &coinbase_kernel.excess; - private_keys.push((wallet_output.spending_key_id, new_private_nonce)); + private_keys.push((wallet_output.spending_key_id, new_nonce.key_id)); kernel_message = TransactionKernel::build_kernel_signature_message( &TransactionKernelVersion::get_current_version(), coinbase_kernel.fee, @@ -967,10 +989,12 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { debug!(target: LOG_TARGET, "Incoming GRPC request for get new block with coinbases"); let mut block_template: NewBlockTemplate = request .new_template - .ok_or(obscure_error_if_true( - report_error_flag, - Status::invalid_argument("Malformed block template provided".to_string()), - ))? + .ok_or_else(|| { + obscure_error_if_true( + report_error_flag, + Status::invalid_argument("Malformed block template provided".to_string()), + ) + })? .try_into() .map_err(|s| { obscure_error_if_true( @@ -1003,7 +1027,9 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { Status::invalid_argument("Malformed coinbase amounts".to_string()), )); } - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().map_err(|s| { + obscure_error_if_true(report_error_flag, Status::internal(format!("Key manager error: {}", s))) + })?; let height = block_template.header.height; // The script key is not used in the Diffie-Hellmann protocol, so we assign default. let script_key_id = TariKeyId::default(); @@ -1014,7 +1040,7 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { let mut kernel_message = [0; 32]; let mut last_kernel = Default::default(); for coinbase in coinbases { - let address = TariAddress::from_hex(&coinbase.address) + let address = TariAddress::from_base58(&coinbase.address) .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; let range_proof_type = if coinbase.revealed_value_proof { RangeProofType::RevealedValue @@ -1032,17 +1058,18 @@ impl tari_rpc::base_node_server::BaseNode for BaseNodeGrpcServer { coinbase.stealth_payment, self.consensus_rules.consensus_constants(height), range_proof_type, + PaymentId::Empty, ) .await .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; block_template.body.add_output(coinbase_output); - let (new_private_nonce, pub_nonce) = key_manager + let new_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .map_err(|e| obscure_error_if_true(report_error_flag, Status::internal(e.to_string())))?; - total_nonce = &total_nonce + &pub_nonce; + total_nonce = &total_nonce + &new_nonce.pub_key; total_excess = &total_excess + &coinbase_kernel.excess; - private_keys.push((wallet_output.spending_key_id, new_private_nonce)); + private_keys.push((wallet_output.spending_key_id, new_nonce.key_id)); kernel_message = TransactionKernel::build_kernel_signature_message( &TransactionKernelVersion::get_current_version(), coinbase_kernel.fee, diff --git a/base_layer/chat_ffi/chat.h b/base_layer/chat_ffi/chat.h index 89893c9b37..fdf383ee28 100644 --- a/base_layer/chat_ffi/chat.h +++ b/base_layer/chat_ffi/chat.h @@ -67,11 +67,12 @@ extern "C" { * The ```destroy_chat_client``` method must be called when finished with a ClientFFI to prevent a memory leak */ struct ChatClient *create_chat_client(struct ApplicationConfig *config, - int *error_out, CallbackContactStatusChange callback_contact_status_change, CallbackMessageReceived callback_message_received, CallbackDeliveryConfirmationReceived callback_delivery_confirmation_received, - CallbackReadConfirmationReceived callback_read_confirmation_received); + CallbackReadConfirmationReceived callback_read_confirmation_received, + struct TariAddress *tari_address, + int *error_out); /** * Side loads a chat client @@ -98,11 +99,12 @@ struct ChatClient *create_chat_client(struct ApplicationConfig *config, */ struct ChatClient *sideload_chat_client(struct ApplicationConfig *config, struct ContactsServiceHandle *contacts_handle, - int *error_out, CallbackContactStatusChange callback_contact_status_change, CallbackMessageReceived callback_message_received, CallbackDeliveryConfirmationReceived callback_delivery_confirmation_received, - CallbackReadConfirmationReceived callback_read_confirmation_received); + CallbackReadConfirmationReceived callback_read_confirmation_received, + struct TariAddress *tari_address, + int *error_out); /** * Frees memory for a ChatClient @@ -490,6 +492,7 @@ void destroy_conversationalists_vector(struct ConversationalistsVector *ptr); * The ```Message``` received should be destroyed after use */ struct Message *create_chat_message(struct TariAddress *receiver, + struct TariAddress *sender, const char *message, int *error_out); @@ -507,6 +510,26 @@ struct Message *create_chat_message(struct TariAddress *receiver, */ void destroy_chat_message(struct Message *ptr); +/** + * Get a ptr to a message from a message_id + * + * ## Arguments + * `client` - The ChatClient pointer + * `message_id` - A pointer to a byte vector representing a message id + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `*mut Message` - A pointer to a message + * + * # Safety + * The returned pointer to ```Message``` should be destroyed after use + * ```client``` should be destroyed after use + * ```message_id``` should be destroyed after use + */ +struct Message *get_chat_message(struct ChatClient *client, + struct ChatByteVector *message_id, + int *error_out); + /** * Sends a message over a client * @@ -587,7 +610,23 @@ struct ChatByteVector *read_chat_message_body(struct Message *message, int *erro * `message` should be destroyed eventually * the returned `TariAddress` should be destroyed eventually */ -struct TariAddress *read_chat_message_address(struct Message *message, int *error_out); +struct TariAddress *read_chat_message_sender_address(struct Message *message, int *error_out); + +/** + * Returns a pointer to a TariAddress + * + * ## Arguments + * `message` - A pointer to a Message + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `*mut TariAddress` - A ptr to a TariAddress + * + * ## Safety + * `message` should be destroyed eventually + * the returned `TariAddress` should be destroyed eventually + */ +struct TariAddress *read_chat_message_receiver_address(struct Message *message, int *error_out); /** * Returns a c_uchar representation of the Direction enum @@ -621,6 +660,21 @@ unsigned char read_chat_message_direction(struct Message *message, int *error_ou */ unsigned long long read_chat_message_stored_at(struct Message *message, int *error_out); +/** + * Returns a c_ulonglong representation of the sent at timestamp as seconds since epoch + * + * ## Arguments + * `message` - A pointer to a Message + * `error_out` - Pointer to an int which will be modified + * + * ## Returns + * `c_ulonglong` - The stored_at timestamp, seconds since epoch. Returns 0 if message is null. + * + * ## Safety + * `message` should be destroyed eventually + */ +unsigned long long read_chat_message_sent_at(struct Message *message, int *error_out); + /** * Returns a c_ulonglong representation of the delivery confirmation timestamp as seconds since epoch * diff --git a/base_layer/chat_ffi/src/byte_vector.rs b/base_layer/chat_ffi/src/byte_vector.rs index 233840c66d..cc666adbb5 100644 --- a/base_layer/chat_ffi/src/byte_vector.rs +++ b/base_layer/chat_ffi/src/byte_vector.rs @@ -100,7 +100,7 @@ pub unsafe extern "C" fn chat_byte_vector_destroy(bytes: *mut ChatByteVector) { /// /// # Safety /// None -// converting between here is fine as its used to clamp the the array to length +// converting between here is fine as its used to clamp the array to length #[allow(clippy::cast_possible_wrap)] #[no_mangle] pub unsafe extern "C" fn chat_byte_vector_get_at( diff --git a/base_layer/chat_ffi/src/callback_handler.rs b/base_layer/chat_ffi/src/callback_handler.rs index 1c1e0268dd..9b36f5bd5a 100644 --- a/base_layer/chat_ffi/src/callback_handler.rs +++ b/base_layer/chat_ffi/src/callback_handler.rs @@ -134,7 +134,7 @@ impl CallbackHandler { debug!( target: LOG_TARGET, "Calling MessageReceived callback function for sender {}", - message.address, + message.sender_address, ); unsafe { diff --git a/base_layer/chat_ffi/src/contacts_liveness_data.rs b/base_layer/chat_ffi/src/contacts_liveness_data.rs index c1bb9b305a..85b0be7093 100644 --- a/base_layer/chat_ffi/src/contacts_liveness_data.rs +++ b/base_layer/chat_ffi/src/contacts_liveness_data.rs @@ -147,8 +147,6 @@ pub unsafe extern "C" fn destroy_contacts_liveness_data(ptr: *mut ContactsLivene #[cfg(test)] mod test { - use std::convert::TryFrom; - use chrono::NaiveDateTime; use tari_contacts::contacts_service::service::{ContactMessageType, ContactOnlineStatus}; use tari_utilities::epoch_time::EpochTime; @@ -158,8 +156,10 @@ mod test { #[test] fn test_reading_address() { - let address = - TariAddress::from_hex("0c017c5cd01385f34ac065e3b05948326dc55d2494f120c6f459a07389011b4ec1").unwrap(); + let address = TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(); let liveness = ContactsLivenessData::new( address.clone(), Default::default(), @@ -174,7 +174,7 @@ mod test { unsafe { let address_ptr = read_liveness_data_address(liveness_ptr, error_out); - assert_eq!(address.to_bytes(), (*address_ptr).to_bytes()); + assert_eq!(address.to_vec(), (*address_ptr).to_vec()); destroy_contacts_liveness_data(liveness_ptr); destroy_tari_address(address_ptr); diff --git a/base_layer/chat_ffi/src/conversationalists.rs b/base_layer/chat_ffi/src/conversationalists.rs index c012956abb..30d02b18e4 100644 --- a/base_layer/chat_ffi/src/conversationalists.rs +++ b/base_layer/chat_ffi/src/conversationalists.rs @@ -177,7 +177,7 @@ mod test { #[test] fn test_retrieving_conversationalists_from_vector() { let (_, pk) = PublicKey::random_keypair(&mut OsRng); - let a = TariAddress::from_public_key(&pk, Network::LocalNet); + let a = TariAddress::new_single_address_with_interactive_only(pk, Network::LocalNet); let conversationalists = ConversationalistsVector(vec![TariAddress::default(), TariAddress::default(), a.clone()]); diff --git a/base_layer/chat_ffi/src/error.rs b/base_layer/chat_ffi/src/error.rs index 69d46ce71b..8af8cc8f7a 100644 --- a/base_layer/chat_ffi/src/error.rs +++ b/base_layer/chat_ffi/src/error.rs @@ -47,6 +47,7 @@ pub enum InterfaceError { #[derive(Debug, Clone)] pub struct LibChatError { pub code: i32, + #[allow(dead_code)] pub message: String, } diff --git a/base_layer/chat_ffi/src/lib.rs b/base_layer/chat_ffi/src/lib.rs index eed41679ff..d7cb02890b 100644 --- a/base_layer/chat_ffi/src/lib.rs +++ b/base_layer/chat_ffi/src/lib.rs @@ -29,6 +29,7 @@ use libc::c_int; use log::info; use minotari_app_utilities::identity_management::setup_node_identity; use tari_chat_client::{config::ApplicationConfig, networking::PeerFeatures, ChatClient as ChatClientTrait, Client}; +use tari_common_types::tari_address::TariAddress; use tari_contacts::contacts_service::handle::ContactsServiceHandle; use tokio::runtime::Runtime; @@ -88,11 +89,12 @@ pub struct ChatClient { #[no_mangle] pub unsafe extern "C" fn create_chat_client( config: *mut ApplicationConfig, - error_out: *mut c_int, callback_contact_status_change: CallbackContactStatusChange, callback_message_received: CallbackMessageReceived, callback_delivery_confirmation_received: CallbackDeliveryConfirmationReceived, callback_read_confirmation_received: CallbackReadConfirmationReceived, + tari_address: *mut TariAddress, + error_out: *mut c_int, ) -> *mut ChatClient { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -142,8 +144,9 @@ pub unsafe extern "C" fn create_chat_client( return ptr::null_mut(); }, }; - - let mut client = Client::new(identity, (*config).clone()); + let user_agent = format!("tari/chat_ffi/{}", env!("CARGO_PKG_VERSION")); + let tari_address = (*tari_address).clone(); + let mut client = Client::new(identity, tari_address, (*config).clone(), user_agent); if let Ok(()) = runtime.block_on(client.initialize()) { let contacts_handler = match client.contacts.clone() { @@ -200,11 +203,12 @@ pub unsafe extern "C" fn create_chat_client( pub unsafe extern "C" fn sideload_chat_client( config: *mut ApplicationConfig, contacts_handle: *mut ContactsServiceHandle, - error_out: *mut c_int, callback_contact_status_change: CallbackContactStatusChange, callback_message_received: CallbackMessageReceived, callback_delivery_confirmation_received: CallbackDeliveryConfirmationReceived, callback_read_confirmation_received: CallbackReadConfirmationReceived, + tari_address: *mut TariAddress, + error_out: *mut c_int, ) -> *mut ChatClient { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -242,8 +246,9 @@ pub unsafe extern "C" fn sideload_chat_client( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } - - let mut client = Client::sideload((*config).clone(), (*contacts_handle).clone()); + let user_agent = format!("tari/chat_ffi/{}", env!("CARGO_PKG_VERSION")); + let tari_address = (*tari_address).clone(); + let mut client = Client::sideload((*config).clone(), (*contacts_handle).clone(), user_agent, tari_address); if let Ok(()) = runtime.block_on(client.initialize()) { let mut callback_handler = CallbackHandler::new( (*contacts_handle).clone(), diff --git a/base_layer/chat_ffi/src/message.rs b/base_layer/chat_ffi/src/message.rs index 9be70ab11b..ea1ee64289 100644 --- a/base_layer/chat_ffi/src/message.rs +++ b/base_layer/chat_ffi/src/message.rs @@ -29,7 +29,7 @@ use tari_contacts::contacts_service::types::{Message, MessageBuilder, MessageMet use tari_utilities::ByteArray; use crate::{ - byte_vector::{chat_byte_vector_create, ChatByteVector}, + byte_vector::{chat_byte_vector_create, process_vector, ChatByteVector}, error::{InterfaceError, LibChatError}, ChatClient, }; @@ -50,6 +50,7 @@ use crate::{ #[no_mangle] pub unsafe extern "C" fn create_chat_message( receiver: *mut TariAddress, + sender: *mut TariAddress, message: *const c_char, error_out: *mut c_int, ) -> *mut Message { @@ -60,6 +61,10 @@ pub unsafe extern "C" fn create_chat_message( error = LibChatError::from(InterfaceError::NullError("receiver".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); } + if sender.is_null() { + error = LibChatError::from(InterfaceError::NullError("sender".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + } let message_str = match CStr::from_ptr(message).to_str() { Ok(str) => str.to_string(), @@ -71,7 +76,8 @@ pub unsafe extern "C" fn create_chat_message( }; let message_out = MessageBuilder::new() - .address((*receiver).clone()) + .receiver_address((*receiver).clone()) + .sender_address((*sender).clone()) .message(message_str) .build(); @@ -95,6 +101,53 @@ pub unsafe extern "C" fn destroy_chat_message(ptr: *mut Message) { } } +/// Get a ptr to a message from a message_id +/// +/// ## Arguments +/// `client` - The ChatClient pointer +/// `message_id` - A pointer to a byte vector representing a message id +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `*mut Message` - A pointer to a message +/// +/// # Safety +/// The returned pointer to ```Message``` should be destroyed after use +/// ```client``` should be destroyed after use +/// ```message_id``` should be destroyed after use +#[no_mangle] +pub unsafe extern "C" fn get_chat_message( + client: *mut ChatClient, + message_id: *mut ChatByteVector, + error_out: *mut c_int, +) -> *mut Message { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if client.is_null() { + error = LibChatError::from(InterfaceError::NullError("client".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + } + + if message_id.is_null() { + error = LibChatError::from(InterfaceError::NullError("message_id".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + } + + let id = process_vector(message_id, error_out); + + let result = (*client).runtime.block_on((*client).client.get_message(&id)); + + match result { + Ok(message) => Box::into_raw(Box::new(message)), + Err(e) => { + error = LibChatError::from(InterfaceError::ContactServiceError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + /// Sends a message over a client /// /// ## Arguments @@ -259,7 +312,10 @@ pub unsafe extern "C" fn read_chat_message_body(message: *mut Message, error_out /// `message` should be destroyed eventually /// the returned `TariAddress` should be destroyed eventually #[no_mangle] -pub unsafe extern "C" fn read_chat_message_address(message: *mut Message, error_out: *mut c_int) -> *mut TariAddress { +pub unsafe extern "C" fn read_chat_message_sender_address( + message: *mut Message, + error_out: *mut c_int, +) -> *mut TariAddress { let mut error = 0; ptr::swap(error_out, &mut error as *mut c_int); @@ -269,7 +325,37 @@ pub unsafe extern "C" fn read_chat_message_address(message: *mut Message, error_ return ptr::null_mut(); } - let address = (*message).address.clone(); + let address = (*message).sender_address.clone(); + Box::into_raw(Box::new(address)) +} + +/// Returns a pointer to a TariAddress +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `*mut TariAddress` - A ptr to a TariAddress +/// +/// ## Safety +/// `message` should be destroyed eventually +/// the returned `TariAddress` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_receiver_address( + message: *mut Message, + error_out: *mut c_int, +) -> *mut TariAddress { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if message.is_null() { + error = LibChatError::from(InterfaceError::NullError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + + let address = (*message).receiver_address.clone(); Box::into_raw(Box::new(address)) } @@ -332,6 +418,31 @@ pub unsafe extern "C" fn read_chat_message_stored_at(message: *mut Message, erro (*message).stored_at as c_ulonglong } +/// Returns a c_ulonglong representation of the sent at timestamp as seconds since epoch +/// +/// ## Arguments +/// `message` - A pointer to a Message +/// `error_out` - Pointer to an int which will be modified +/// +/// ## Returns +/// `c_ulonglong` - The stored_at timestamp, seconds since epoch. Returns 0 if message is null. +/// +/// ## Safety +/// `message` should be destroyed eventually +#[no_mangle] +pub unsafe extern "C" fn read_chat_message_sent_at(message: *mut Message, error_out: *mut c_int) -> c_ulonglong { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + + if message.is_null() { + error = LibChatError::from(InterfaceError::NullError("message".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + + (*message).sent_at as c_ulonglong +} + /// Returns a c_ulonglong representation of the delivery confirmation timestamp as seconds since epoch /// /// ## Arguments @@ -427,13 +538,12 @@ pub unsafe extern "C" fn read_chat_message_id(message: *mut Message, error_out: #[cfg(test)] mod test { - use tari_contacts::contacts_service::types::{Direction, MessageBuilder}; + use tari_contacts::contacts_service::types::Direction; use tari_utilities::epoch_time::EpochTime; use super::*; use crate::{ byte_vector::{chat_byte_vector_destroy, chat_byte_vector_get_at, chat_byte_vector_get_length}, - message::read_chat_message_id, tari_address::destroy_tari_address, }; @@ -489,17 +599,26 @@ mod test { #[test] fn test_reading_message_address() { - let address = - TariAddress::from_hex("0c017c5cd01385f34ac065e3b05948326dc55d2494f120c6f459a07389011b4ec1").unwrap(); - let message = MessageBuilder::new().address(address.clone()).build(); + let receiver_address = TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(); + let sender_address = TariAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(); + let message = MessageBuilder::new() + .receiver_address(receiver_address.clone()) + .sender_address(sender_address.clone()) + .build(); let message_ptr = Box::into_raw(Box::new(message)); let error_out = Box::into_raw(Box::new(0)); unsafe { - let address_ptr = read_chat_message_address(message_ptr, error_out); + let address_ptr = read_chat_message_sender_address(message_ptr, error_out); - assert_eq!(address.to_bytes(), (*address_ptr).to_bytes()); + assert_eq!(sender_address.to_vec(), (*address_ptr).to_vec()); destroy_chat_message(message_ptr); destroy_tari_address(address_ptr); diff --git a/base_layer/chat_ffi/src/message_metadata.rs b/base_layer/chat_ffi/src/message_metadata.rs index c93d4a6398..3d4a3c07a5 100644 --- a/base_layer/chat_ffi/src/message_metadata.rs +++ b/base_layer/chat_ffi/src/message_metadata.rs @@ -169,20 +169,12 @@ pub unsafe extern "C" fn destroy_chat_message_metadata(ptr: *mut MessageMetadata #[cfg(test)] mod test { - use std::convert::TryFrom; - - use libc::c_uint; use tari_common_types::tari_address::TariAddress; use tari_contacts::contacts_service::types::MessageBuilder; use super::*; use crate::{ - byte_vector::{ - chat_byte_vector_create, - chat_byte_vector_destroy, - chat_byte_vector_get_at, - chat_byte_vector_get_length, - }, + byte_vector::{chat_byte_vector_destroy, chat_byte_vector_get_at, chat_byte_vector_get_length}, message::{chat_metadata_get_at, destroy_chat_message}, }; @@ -219,7 +211,8 @@ mod test { let message_ptr = Box::into_raw(Box::new( MessageBuilder::new() .message("hello".to_string()) - .address(address) + .receiver_address(address.clone()) + .sender_address(address) .build(), )); let error_out = Box::into_raw(Box::new(0)); diff --git a/base_layer/common_types/Cargo.toml b/base_layer/common_types/Cargo.toml index 6f07dd69d7..165611e509 100644 --- a/base_layer/common_types/Cargo.toml +++ b/base_layer/common_types/Cargo.toml @@ -13,7 +13,9 @@ tari_common = { workspace = true } chacha20poly1305 = "0.10.1" +bitflags = { version = "2.4", features = ["serde"] } borsh = "1.2" +bs58 = "0.5.1" digest = "0.10" newtype-ops = "0.1" once_cell = "1.8.0" @@ -26,5 +28,8 @@ base64 = "0.21.0" blake2 = "0.10" primitive-types = { version = "0.12", features = ["serde"] } +[features] +default = [] + [package.metadata.cargo-machete] ignored = ["strum", "strum_macros"] # this is so we can run cargo machete without getting false positive about macro dependancies diff --git a/base_layer/common_types/src/dammsum.rs b/base_layer/common_types/src/dammsum.rs index 959b11387a..b862cda995 100644 --- a/base_layer/common_types/src/dammsum.rs +++ b/base_layer/common_types/src/dammsum.rs @@ -20,16 +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 once_cell::sync::Lazy; use thiserror::Error; /// Calculates a checksum using the [DammSum](https://github.com/cypherstack/dammsum) algorithm. /// /// This approach uses a dictionary whose size must be `2^k` for some `k > 0`. -/// The algorithm accepts an array of arbitrary size, each of whose elements are integers in the range `[0, 2^k)`. +/// The algorithm accepts a slice of arbitrary size, each of whose elements are integers in the range `[0, 2^k)`. /// The checksum is a single element also within this range. /// DammSum detects all single transpositions and substitutions. /// -/// Note that for this implementation, we add the additional restriction that `k == 8`. +/// Note that for this implementation, we add the additional restriction that `k == 8` to handle byte slices. /// This is only because DammSum requires us to provide the coefficients for a certain type of polynomial, and /// because it's unlikely for the alphabet size to change for this use case. /// See the linked repository for more information, or if you need a different dictionary size. @@ -42,18 +43,27 @@ pub enum ChecksumError { InvalidChecksum, } -// Fixed for a dictionary size of `2^8 == 256` -const COEFFICIENTS: [u8; 3] = [4, 3, 1]; +/// The number of bytes used for the checksum +/// This is included for applications that need to know it for encodings +pub const CHECKSUM_BYTES: usize = 1; -/// Compute the DammSum checksum for an array, each of whose elements are in the range `[0, 2^8)` -pub fn compute_checksum(data: &Vec) -> u8 { +// Set up the mask, fixed for a dictionary size of `2^8 == 256` +// This can fail on invalid coefficients, which will cause a panic +// To ensure this doesn't happen in production, it is directly tested +const COEFFICIENTS: [u8; 3] = [4, 3, 1]; +static MASK: Lazy = Lazy::new(|| { let mut mask = 1u8; - // Compute the bitmask (if possible) for bit in COEFFICIENTS { - mask += 1u8 << bit; + let shift = 1u8.checked_shl(u32::from(bit)).unwrap(); + mask = mask.checked_add(shift).unwrap(); } + mask +}); + +/// Compute the DammSum checksum for a byte slice +pub fn compute_checksum(data: &[u8]) -> u8 { // Perform the Damm algorithm let mut result = 0u8; @@ -63,23 +73,24 @@ pub fn compute_checksum(data: &Vec) -> u8 { result <<= 1; // double if overflow { // reduce - result ^= mask; + result ^= *MASK; } } result } -/// Determine whether the array ends with a valid checksum -pub fn validate_checksum(data: &Vec) -> Result<(), ChecksumError> { +/// Determine whether a byte slice ends with a valid checksum +/// If it is valid, returns the underlying data slice (without the checksum) +pub fn validate_checksum(data: &[u8]) -> Result<&[u8], ChecksumError> { // Empty data is not allowed, nor data only consisting of a checksum if data.len() < 2 { return Err(ChecksumError::InputDataTooShort); } - // It's sufficient to check the entire array against a zero checksum + // It's sufficient to check the entire slice against a zero checksum match compute_checksum(data) { - 0u8 => Ok(()), + 0u8 => Ok(&data[..data.len() - 1]), _ => Err(ChecksumError::InvalidChecksum), } } @@ -88,7 +99,13 @@ pub fn validate_checksum(data: &Vec) -> Result<(), ChecksumError> { mod test { use rand::Rng; - use crate::dammsum::{compute_checksum, validate_checksum, ChecksumError}; + use crate::dammsum::*; + + #[test] + /// Check that mask initialization doesn't panic + fn no_mask_panic() { + let _mask = *MASK; + } #[test] /// Check that valid checksums validate @@ -97,13 +114,14 @@ mod test { // Generate random data let mut rng = rand::thread_rng(); - let mut data: Vec = (0..SIZE).map(|_| rng.gen::()).collect(); + let data: Vec = (0..SIZE).map(|_| rng.gen::()).collect(); // Compute and append the checksum - data.push(compute_checksum(&data)); + let mut data_with_checksum = data.clone(); + data_with_checksum.push(compute_checksum(&data)); - // Validate - assert!(validate_checksum(&data).is_ok()); + // Validate and ensure we get the same data back + assert_eq!(validate_checksum(&data_with_checksum).unwrap(), data); } #[test] diff --git a/base_layer/common_types/src/emoji.rs b/base_layer/common_types/src/emoji.rs index 403d6d1801..34928a5044 100644 --- a/base_layer/common_types/src/emoji.rs +++ b/base_layer/common_types/src/emoji.rs @@ -25,6 +25,7 @@ use std::{ convert::TryFrom, fmt::{Display, Error, Formatter}, iter, + str::FromStr, }; use once_cell::sync::Lazy; @@ -32,7 +33,7 @@ use tari_crypto::tari_utilities::ByteArray; use thiserror::Error; use crate::{ - dammsum::{compute_checksum, validate_checksum}, + dammsum::{compute_checksum, validate_checksum, CHECKSUM_BYTES}, types::PublicKey, }; @@ -49,49 +50,49 @@ use crate::{ /// # Example /// /// ``` +/// use std::str::FromStr; /// use tari_common_types::emoji::EmojiId; /// /// // Construct an emoji ID from an emoji string (this can fail) -/// let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒👡"; -/// let emoji_id_from_emoji_string = EmojiId::from_emoji_string(emoji_string); +/// let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🥊"; +/// let emoji_id_from_emoji_string = EmojiId::from_str(emoji_string); /// assert!(emoji_id_from_emoji_string.is_ok()); /// /// // Get the public key -/// let public_key = emoji_id_from_emoji_string.unwrap().to_public_key(); +/// let public_key = emoji_id_from_emoji_string.unwrap().as_public_key().clone(); /// /// // Reconstruct the emoji ID from the public key (this cannot fail) -/// let emoji_id_from_public_key = EmojiId::from_public_key(&public_key); +/// let emoji_id_from_public_key = EmojiId::from(&public_key); /// /// // An emoji ID is deterministic -/// assert_eq!(emoji_id_from_public_key.to_emoji_string(), emoji_string); +/// assert_eq!(emoji_id_from_public_key.to_string(), emoji_string); /// /// // Oh no! We swapped the first two emoji characters by mistake, so this should fail -/// let invalid_emoji_string = "🐩🌴🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒👡"; -/// assert!(EmojiId::from_emoji_string(invalid_emoji_string).is_err()); +/// let invalid_emoji_string = "🦀🌴🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🥊"; +/// assert!(EmojiId::from_str(invalid_emoji_string).is_err()); /// ``` #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub struct EmojiId(PublicKey); const DICT_SIZE: usize = 256; // number of elements in the symbol dictionary -const INTERNAL_SIZE: usize = 32; // number of bytes used for the internal representation (without checksum) -const CHECKSUM_SIZE: usize = 1; // number of bytes in the checksum +const DATA_BYTES: usize = 32; // number of bytes used for the key data // The emoji table, mapping byte values to emoji characters pub const EMOJI: [char; DICT_SIZE] = [ - '🌀', '🌂', '🌈', '🌊', '🌋', '🌍', '🌙', '🌝', '🌞', '🌟', '🌠', '🌰', '🌴', '🌵', '🌷', '🌸', '🌹', '🌻', '🌽', - '🍀', '🍁', '🍄', '🍅', '🍆', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍐', '🍑', '🍒', '🍓', '🍔', '🍕', - '🍗', '🍚', '🍞', '🍟', '🍠', '🍣', '🍦', '🍩', '🍪', '🍫', '🍬', '🍭', '🍯', '🍰', '🍳', '🍴', '🍵', '🍶', '🍷', - '🍸', '🍹', '🍺', '🍼', '🎀', '🎁', '🎂', '🎃', '🎄', '🎈', '🎉', '🎒', '🎓', '🎠', '🎡', '🎢', '🎣', '🎤', '🎥', + '🐢', '📟', '🌈', '🌊', '🎯', '🐋', '🌙', '🤔', '🌕', '⭐', '🎋', '🌰', '🌴', '🌵', '🌲', '🌸', '🌹', '🌻', '🌽', + '🍀', '🍁', '🍄', '🥑', '🍆', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍐', '🍑', '🍒', '🍓', '🍔', '🍕', + '🍗', '🍚', '🍞', '🍟', '🥝', '🍣', '🍦', '🍩', '🍪', '🍫', '🍬', '🍭', '🍯', '🥐', '🍳', '🥄', '🍵', '🍶', '🍷', + '🍸', '🍾', '🍺', '🍼', '🎀', '🎁', '🎂', '🎃', '🤖', '🎈', '🎉', '🎒', '🎓', '🎠', '🎡', '🎢', '🎣', '🎤', '🎥', '🎧', '🎨', '🎩', '🎪', '🎬', '🎭', '🎮', '🎰', '🎱', '🎲', '🎳', '🎵', '🎷', '🎸', '🎹', '🎺', '🎻', '🎼', '🎽', - '🎾', '🎿', '🏀', '🏁', '🏆', '🏈', '🏉', '🏠', '🏥', '🏦', '🏭', '🏰', '🐀', '🐉', '🐊', '🐌', '🐍', '🐎', '🐐', - '🐑', '🐓', '🐖', '🐗', '🐘', '🐙', '🐚', '🐛', '🐜', '🐝', '🐞', '🐢', '🐣', '🐨', '🐩', '🐪', '🐬', '🐭', '🐮', - '🐯', '🐰', '🐲', '🐳', '🐴', '🐵', '🐶', '🐷', '🐸', '🐺', '🐻', '🐼', '🐽', '🐾', '👀', '👅', '👑', '👒', '👓', - '👔', '👕', '👖', '👗', '👘', '👙', '👚', '👛', '👞', '👟', '👠', '👡', '👢', '👣', '👹', '👻', '👽', '👾', '👿', - '💀', '💄', '💈', '💉', '💊', '💋', '💌', '💍', '💎', '💐', '💔', '💕', '💘', '💡', '💣', '💤', '💦', '💨', '💩', - '💭', '💯', '💰', '💳', '💸', '💺', '💻', '💼', '📈', '📉', '📌', '📎', '📚', '📝', '📡', '📣', '📱', '📷', '🔋', - '🔌', '🔎', '🔑', '🔔', '🔥', '🔦', '🔧', '🔨', '🔩', '🔪', '🔫', '🔬', '🔭', '🔮', '🔱', '🗽', '😂', '😇', '😈', - '😉', '😍', '😎', '😱', '😷', '😹', '😻', '😿', '🚀', '🚁', '🚂', '🚌', '🚑', '🚒', '🚓', '🚕', '🚗', '🚜', '🚢', - '🚦', '🚧', '🚨', '🚪', '🚫', '🚲', '🚽', '🚿', '🛁', + '🎾', '🎿', '🏀', '🏁', '🏆', '🏈', '⚽', '🏠', '🏥', '🏦', '🏭', '🏰', '🐀', '🐉', '🐊', '🐌', '🐍', '🦁', '🐐', + '🐑', '🐔', '🙈', '🐗', '🐘', '🐙', '🐚', '🐛', '🐜', '🐝', '🐞', '🦋', '🐣', '🐨', '🦀', '🐪', '🐬', '🐭', '🐮', + '🐯', '🐰', '🦆', '🦂', '🐴', '🐵', '🐶', '🐷', '🐸', '🐺', '🐻', '🐼', '🐽', '🐾', '👀', '👅', '👑', '👒', '🧢', + '💅', '👕', '👖', '👗', '👘', '👙', '💃', '👛', '👞', '👟', '👠', '🥊', '👢', '👣', '🤡', '👻', '👽', '👾', '🤠', + '👃', '💄', '💈', '💉', '💊', '💋', '👂', '💍', '💎', '💐', '💔', '🔒', '🧩', '💡', '💣', '💤', '💦', '💨', '💩', + '➕', '💯', '💰', '💳', '💵', '💺', '💻', '💼', '📈', '📜', '📌', '📎', '📖', '📿', '📡', '⏰', '📱', '📷', '🔋', + '🔌', '🚰', '🔑', '🔔', '🔥', '🔦', '🔧', '🔨', '🔩', '🔪', '🔫', '🔬', '🔭', '🔮', '🔱', '🗽', '😂', '😇', '😈', + '🤑', '😍', '😎', '😱', '😷', '🤢', '👍', '👶', '🚀', '🚁', '🚂', '🚚', '🚑', '🚒', '🚓', '🛵', '🚗', '🚜', '🚢', + '🚦', '🚧', '🚨', '🚪', '🚫', '🚲', '🚽', '🚿', '🧲', ]; // The reverse table, mapping emoji to characters to byte values @@ -121,16 +122,24 @@ pub enum EmojiIdError { } impl EmojiId { - /// Construct an emoji ID from an emoji string with checksum - pub fn from_emoji_string(emoji: &str) -> Result { + /// Get the public key from an emoji ID + pub fn as_public_key(&self) -> &PublicKey { + &self.0 + } +} + +impl FromStr for EmojiId { + type Err = EmojiIdError; + + fn from_str(s: &str) -> Result { // The string must be the correct size, including the checksum - if emoji.chars().count() != INTERNAL_SIZE + CHECKSUM_SIZE { + if s.chars().count() != DATA_BYTES + CHECKSUM_BYTES { return Err(EmojiIdError::InvalidSize); } // Convert the emoji string to a byte array - let mut bytes = Vec::::with_capacity(INTERNAL_SIZE + CHECKSUM_SIZE); - for c in emoji.chars() { + let mut bytes = Vec::::with_capacity(DATA_BYTES + CHECKSUM_BYTES); + for c in s.chars() { if let Some(i) = REVERSE_EMOJI.get(&c) { bytes.push(*i); } else { @@ -138,58 +147,61 @@ impl EmojiId { } } - // Assert the checksum is valid - if validate_checksum(&bytes).is_err() { - return Err(EmojiIdError::InvalidChecksum); - } - - // Remove the checksum - bytes.pop(); + // Assert the checksum is valid and get the underlying data + let data = validate_checksum(&bytes).map_err(|_| EmojiIdError::InvalidChecksum)?; // Convert to a public key - match PublicKey::from_canonical_bytes(&bytes) { + match PublicKey::from_canonical_bytes(data) { Ok(public_key) => Ok(Self(public_key)), Err(_) => Err(EmojiIdError::CannotRecoverPublicKey), } } +} - /// Construct an emoji ID from a public key - pub fn from_public_key(public_key: &PublicKey) -> Self { - Self(public_key.clone()) +impl From<&PublicKey> for EmojiId { + fn from(value: &PublicKey) -> Self { + Self::from(value.clone()) } +} - /// Convert the emoji ID to an emoji string with checksum - pub fn to_emoji_string(&self) -> String { - // Convert the public key to bytes and compute the checksum - let bytes = self.0.as_bytes().to_vec(); - bytes - .iter() - .chain(iter::once(&compute_checksum(&bytes))) - .map(|b| EMOJI[*b as usize]) - .collect::() +impl From for EmojiId { + fn from(value: PublicKey) -> Self { + Self(value) } +} - /// Convert the emoji ID to a public key - pub fn to_public_key(&self) -> PublicKey { - self.0.clone() +impl From<&EmojiId> for PublicKey { + fn from(value: &EmojiId) -> Self { + value.as_public_key().clone() } } impl Display for EmojiId { fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> { - fmt.write_str(&self.to_emoji_string()) + // Convert the public key to bytes and compute the checksum + let bytes = self.as_public_key().as_bytes(); + let emoji = bytes + .iter() + .chain(iter::once(&compute_checksum(bytes))) + .map(|b| EMOJI[*b as usize]) + .collect::(); + + fmt.write_str(&emoji) } } #[cfg(test)] mod test { - use std::iter; + use std::{iter, str::FromStr}; - use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey}; + use tari_crypto::{ + keys::{PublicKey as PublicKeyTrait, SecretKey}, + tari_utilities::ByteArray, + }; use crate::{ - dammsum::compute_checksum, - emoji::{emoji_set, EmojiId, EmojiIdError, CHECKSUM_SIZE, INTERNAL_SIZE}, + dammsum::{compute_checksum, CHECKSUM_BYTES}, + emoji::{emoji_set, EmojiId, EmojiIdError, DATA_BYTES}, types::{PrivateKey, PublicKey}, }; @@ -201,57 +213,52 @@ mod test { let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); // Generate an emoji ID from the public key and ensure we recover it - let emoji_id_from_public_key = EmojiId::from_public_key(&public_key); - assert_eq!(emoji_id_from_public_key.to_public_key(), public_key); + let emoji_id_from_public_key = EmojiId::from(&public_key); + assert_eq!(emoji_id_from_public_key.as_public_key(), &public_key); // Check the size of the corresponding emoji string - let emoji_string = emoji_id_from_public_key.to_emoji_string(); - assert_eq!(emoji_string.chars().count(), INTERNAL_SIZE + CHECKSUM_SIZE); + let emoji_string = emoji_id_from_public_key.to_string(); + assert_eq!(emoji_string.chars().count(), DATA_BYTES + CHECKSUM_BYTES); // Generate an emoji ID from the emoji string and ensure we recover it - let emoji_id_from_emoji_string = EmojiId::from_emoji_string(&emoji_string).unwrap(); - assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + let emoji_id_from_emoji_string = EmojiId::from_str(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_string(), emoji_string); // Return to the original public key for good measure - assert_eq!(emoji_id_from_emoji_string.to_public_key(), public_key); + assert_eq!(emoji_id_from_emoji_string.as_public_key(), &public_key); } #[test] /// Test invalid size fn invalid_size() { // This emoji string is too short to be a valid emoji ID - let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒"; - assert_eq!(EmojiId::from_emoji_string(emoji_string), Err(EmojiIdError::InvalidSize)); + let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒"; + assert_eq!(EmojiId::from_str(emoji_string), Err(EmojiIdError::InvalidSize)); } #[test] /// Test invalid emoji fn invalid_emoji() { // This emoji string contains an invalid emoji character - let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒🎅"; - assert_eq!( - EmojiId::from_emoji_string(emoji_string), - Err(EmojiIdError::InvalidEmoji) - ); + let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎅"; + assert_eq!(EmojiId::from_str(emoji_string), Err(EmojiIdError::InvalidEmoji)); } #[test] /// Test invalid checksum fn invalid_checksum() { // This emoji string contains an invalid checksum - let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒🎒"; - assert_eq!( - EmojiId::from_emoji_string(emoji_string), - Err(EmojiIdError::InvalidChecksum) - ); + let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎒"; + assert_eq!(EmojiId::from_str(emoji_string), Err(EmojiIdError::InvalidChecksum)); } #[test] /// Test invalid public key fn invalid_public_key() { // This byte representation does not represent a valid public key - let mut bytes = vec![0u8; INTERNAL_SIZE]; + let mut bytes = vec![0u8; DATA_BYTES]; bytes[0] = 1; + assert!(PublicKey::from_canonical_bytes(&bytes).is_err()); // Convert to an emoji string and manually add a valid checksum let emoji_set = emoji_set(); @@ -262,8 +269,14 @@ mod test { .collect::(); assert_eq!( - EmojiId::from_emoji_string(&emoji_string), + EmojiId::from_str(&emoji_string), Err(EmojiIdError::CannotRecoverPublicKey) ); } + + #[test] + /// Test that the data size is correct for the underlying key type + fn data_size() { + assert_eq!(PublicKey::default().as_bytes().len(), DATA_BYTES); + } } diff --git a/base_layer/common_types/src/encryption.rs b/base_layer/common_types/src/encryption.rs index 7de8e7bd90..68533e8ff0 100644 --- a/base_layer/common_types/src/encryption.rs +++ b/base_layer/common_types/src/encryption.rs @@ -109,11 +109,7 @@ pub fn encrypt_bytes_integral_nonce( #[cfg(test)] mod test { - use std::mem::size_of; - - use chacha20poly1305::{Key, KeyInit, Tag, XChaCha20Poly1305, XNonce}; - use rand::{rngs::OsRng, RngCore}; - use tari_utilities::{ByteArray, Hidden}; + use chacha20poly1305::{Key, KeyInit}; use super::*; diff --git a/base_layer/common_types/src/lib.rs b/base_layer/common_types/src/lib.rs index f1cccdc01b..d77413e2af 100644 --- a/base_layer/common_types/src/lib.rs +++ b/base_layer/common_types/src/lib.rs @@ -20,6 +20,9 @@ // 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. +// This is the string used to derive the comms/spend key of the wallet +pub const WALLET_COMMS_AND_SPEND_KEY_BRANCH: &str = "comms"; + pub mod burnt_proof; pub mod chain_metadata; pub mod dammsum; diff --git a/base_layer/common_types/src/tari_address.rs b/base_layer/common_types/src/tari_address.rs deleted file mode 100644 index b84403f9eb..0000000000 --- a/base_layer/common_types/src/tari_address.rs +++ /dev/null @@ -1,343 +0,0 @@ -// 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::TryFrom, - fmt::{Display, Error, Formatter}, - str::FromStr, -}; - -use serde::{Deserialize, Serialize}; -use tari_common::configuration::Network; -use tari_crypto::tari_utilities::ByteArray; -use tari_utilities::hex::{from_hex, Hex}; -use thiserror::Error; - -use crate::{ - dammsum::{compute_checksum, validate_checksum}, - emoji::{EMOJI, REVERSE_EMOJI}, - types::PublicKey, -}; - -const INTERNAL_SIZE: usize = 33; // number of bytes used for the internal representation - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -pub struct TariAddress { - network: Network, - public_key: PublicKey, -} - -#[derive(Debug, Error, PartialEq)] -pub enum TariAddressError { - #[error("Invalid size")] - InvalidSize, - #[error("Invalid network or checksum")] - InvalidNetworkOrChecksum, - #[error("Invalid emoji character")] - InvalidEmoji, - #[error("Cannot recover public key")] - CannotRecoverPublicKey, -} - -impl TariAddress { - /// Creates a new Tari Address from the provided public key and network while using the current version - pub fn new(public_key: PublicKey, network: Network) -> Self { - TariAddress { network, public_key } - } - - /// helper function to convert emojis to u8 - fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { - // The string must be the correct size, including the checksum - if emoji.chars().count() != INTERNAL_SIZE { - return Err(TariAddressError::InvalidSize); - } - - // Convert the emoji string to a byte array - let mut bytes = Vec::::with_capacity(INTERNAL_SIZE); - for c in emoji.chars() { - if let Some(i) = REVERSE_EMOJI.get(&c) { - bytes.push(*i); - } else { - return Err(TariAddressError::InvalidEmoji); - } - } - Ok(bytes) - } - - /// Construct an TariAddress from an emoji string with checksum and network - pub fn from_emoji_string_with_network(emoji: &str, network: Network) -> Result { - let bytes = TariAddress::emoji_to_bytes(emoji)?; - - TariAddress::from_bytes_with_network(&bytes, network) - } - - /// Construct an TariAddress from an emoji string with checksum trying to calculate the network - pub fn from_emoji_string(emoji: &str) -> Result { - let bytes = TariAddress::emoji_to_bytes(emoji)?; - - TariAddress::from_bytes(&bytes) - } - - /// Construct an Tari Address from a public key - pub fn from_public_key(public_key: &PublicKey, network: Network) -> Self { - Self { - network, - public_key: public_key.clone(), - } - } - - /// Gets the network from the Tari Address - pub fn network(&self) -> Network { - self.network - } - - /// Convert Tari Address to an emoji string with checksum - pub fn to_emoji_string(&self) -> String { - // Convert the public key to bytes and compute the checksum - let bytes = self.to_bytes(); - bytes.iter().map(|b| EMOJI[*b as usize]).collect::() - } - - /// Return the public key of an Tari Address - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } - - /// Construct Tari Address from bytes with network - pub fn from_bytes_with_network(bytes: &[u8], network: Network) -> Result - where Self: Sized { - if bytes.len() != INTERNAL_SIZE { - return Err(TariAddressError::InvalidSize); - } - let mut fixed_data = bytes.to_vec(); - fixed_data[32] ^= network.as_byte(); - // Assert the checksum is valid - if validate_checksum(&fixed_data).is_err() { - return Err(TariAddressError::InvalidNetworkOrChecksum); - } - let key = - PublicKey::from_canonical_bytes(&bytes[0..32]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - Ok(TariAddress { - public_key: key, - network, - }) - } - - /// Construct Tari Address from bytes and try to calculate the network - pub fn from_bytes(bytes: &[u8]) -> Result - where Self: Sized { - if bytes.len() != INTERNAL_SIZE { - return Err(TariAddressError::InvalidSize); - } - let checksum = compute_checksum(&bytes[0..32].to_vec()); - // if the network is a valid network number, we can assume that the checksum as valid - let network = - Network::try_from(checksum ^ bytes[32]).map_err(|_| TariAddressError::InvalidNetworkOrChecksum)?; - let key = - PublicKey::from_canonical_bytes(&bytes[0..32]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - Ok(TariAddress { - public_key: key, - network, - }) - } - - /// Convert Tari Address to bytes - pub fn to_bytes(&self) -> [u8; INTERNAL_SIZE] { - let mut buf = [0u8; INTERNAL_SIZE]; - buf[0..32].copy_from_slice(self.public_key.as_bytes()); - let checksum = compute_checksum(&buf[0..32].to_vec()); - buf[32] = self.network.as_byte() ^ checksum; - buf - } - - /// Construct Tari Address from hex with network - pub fn from_hex_with_network(hex_str: &str, network: Network) -> Result { - let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - TariAddress::from_bytes_with_network(buf.as_slice(), network) - } - - /// Construct Tari Address from hex and try to calculate the network - pub fn from_hex(hex_str: &str) -> Result { - let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; - TariAddress::from_bytes(buf.as_slice()) - } - - /// Convert Tari Address to bytes - pub fn to_hex(&self) -> String { - let buf = self.to_bytes(); - buf.to_hex() - } -} - -impl FromStr for TariAddress { - type Err = TariAddressError; - - fn from_str(key: &str) -> Result { - if let Ok(address) = TariAddress::from_emoji_string(&key.trim().replace('|', "")) { - Ok(address) - } else if let Ok(address) = TariAddress::from_hex(key) { - Ok(address) - } else { - Err(TariAddressError::CannotRecoverPublicKey) - } - } -} - -impl Display for TariAddress { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> { - fmt.write_str(&self.to_emoji_string()) - } -} - -#[cfg(test)] -mod test { - use tari_crypto::keys::{PublicKey, SecretKey}; - - use super::*; - use crate::types::PrivateKey; - - #[test] - /// Test valid tari address - fn valid_emoji_id() { - // Generate random public key - let mut rng = rand::thread_rng(); - let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); - - // Generate an emoji ID from the public key and ensure we recover it - let emoji_id_from_public_key = TariAddress::from_public_key(&public_key, Network::Esmeralda); - assert_eq!(emoji_id_from_public_key.public_key(), &public_key); - - // Check the size of the corresponding emoji string - let emoji_string = emoji_id_from_public_key.to_emoji_string(); - assert_eq!(emoji_string.chars().count(), INTERNAL_SIZE); - - // Generate an emoji ID from the emoji string and ensure we recover it - let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); - assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); - - // Return to the original public key for good measure - assert_eq!(emoji_id_from_emoji_string.public_key(), &public_key); - } - - #[test] - /// Test encoding for tari address - fn encoding() { - // Generate random public key - let mut rng = rand::thread_rng(); - let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); - - // Generate an emoji ID from the public key and ensure we recover it - let address = TariAddress::from_public_key(&public_key, Network::Esmeralda); - - let buff = address.to_bytes(); - let hex = address.to_hex(); - - let address_buff = TariAddress::from_bytes(&buff); - assert_eq!(address_buff, Ok(address.clone())); - - let address_buff = TariAddress::from_bytes_with_network(&buff, Network::Esmeralda); - assert_eq!(address_buff, Ok(address.clone())); - - let address_hex = TariAddress::from_hex(&hex); - assert_eq!(address_hex, Ok(address.clone())); - - let address_hex = TariAddress::from_hex_with_network(&hex, Network::Esmeralda); - assert_eq!(address_hex, Ok(address)); - } - - #[test] - /// Test invalid size - fn invalid_size() { - // This emoji string is too short to be a valid emoji ID - let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidSize) - ); - // This emoji string is too long to be a valid emoji ID - let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒🎒🎒🎒🎒"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidSize) - ); - } - - #[test] - /// Test invalid emoji - fn invalid_emoji() { - // This emoji string contains an invalid emoji character - let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒🎅"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidEmoji) - ); - } - - #[test] - /// Test invalid checksum - fn invalid_checksum() { - // This emoji string contains an invalid checksum - let emoji_string = "🌴🐩🔌📌🚑🌰🎓🌴🐊🐌💕💡🐜📉👛🍵👛🐽🎂🐻🌀🍓😿🐭🐼🏀🎪💔💸🍅🔋🎒🎒"; - assert_eq!( - TariAddress::from_emoji_string(emoji_string), - Err(TariAddressError::InvalidNetworkOrChecksum) - ); - } - - #[test] - /// Test invalid network - fn invalid_network() { - let mut rng = rand::thread_rng(); - let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); - - // Generate an address using a valid network and ensure it's not valid on another network - let address = TariAddress::from_public_key(&public_key, Network::Esmeralda); - assert_eq!( - TariAddress::from_bytes_with_network(&address.to_bytes(), Network::Igor), - Err(TariAddressError::InvalidNetworkOrChecksum) - ); - - // Generate an address using a valid network, mutate it, and ensure it's not valid on the same network - let mut address_bytes = address.to_bytes(); - address_bytes[32] ^= 0xFF; - assert_eq!( - TariAddress::from_bytes_with_network(&address_bytes, Network::Esmeralda), - Err(TariAddressError::InvalidNetworkOrChecksum) - ); - } - - #[test] - /// Test invalid public key - fn invalid_public_key() { - let mut bytes = [0; 33].to_vec(); - bytes[0] = 1; - let checksum = compute_checksum(&bytes[0..32].to_vec()); - bytes[32] = Network::Esmeralda.as_byte() ^ checksum; - let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); - - // This emoji string contains an invalid checksum - assert_eq!( - TariAddress::from_emoji_string(&emoji_string), - Err(TariAddressError::CannotRecoverPublicKey) - ); - } -} diff --git a/base_layer/common_types/src/tari_address/dual_address.rs b/base_layer/common_types/src/tari_address/dual_address.rs new file mode 100644 index 0000000000..3b5c3e8120 --- /dev/null +++ b/base_layer/common_types/src/tari_address/dual_address.rs @@ -0,0 +1,520 @@ +// 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::TryFrom, panic}; + +use serde::{Deserialize, Serialize}; +use tari_common::configuration::Network; +use tari_crypto::tari_utilities::ByteArray; +use tari_utilities::hex::{from_hex, Hex}; + +use crate::{ + dammsum::{compute_checksum, validate_checksum}, + emoji::{EMOJI, REVERSE_EMOJI}, + tari_address::{TariAddressError, TariAddressFeatures, INTERNAL_DUAL_SIZE}, + types::PublicKey, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct DualAddress { + network: Network, + features: TariAddressFeatures, + public_view_key: PublicKey, + public_spend_key: PublicKey, +} + +impl DualAddress { + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new( + view_key: PublicKey, + spend_key: PublicKey, + network: Network, + features: TariAddressFeatures, + ) -> DualAddress { + Self { + network, + features, + public_view_key: view_key, + public_spend_key: spend_key, + } + } + + /// Creates a new Tari Address from the provided public keys and network while using the default features + pub fn new_with_default_features(view_key: PublicKey, spend_key: PublicKey, network: Network) -> DualAddress { + Self { + network, + features: TariAddressFeatures::default(), + public_view_key: view_key, + public_spend_key: spend_key, + } + } + + /// helper function to convert emojis to u8 + pub fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { + // The string must be the correct size, including the checksum + if emoji.chars().count() != INTERNAL_DUAL_SIZE { + return Err(TariAddressError::InvalidSize); + } + + // Convert the emoji string to a byte array + let mut bytes = Vec::::with_capacity(INTERNAL_DUAL_SIZE); + for c in emoji.chars() { + if let Some(i) = REVERSE_EMOJI.get(&c) { + bytes.push(*i); + } else { + return Err(TariAddressError::InvalidEmoji); + } + } + Ok(bytes) + } + + /// Construct an TariAddress from an emoji string + pub fn from_emoji_string(emoji: &str) -> Result { + let bytes = Self::emoji_to_bytes(emoji)?; + + Self::from_bytes(&bytes) + } + + /// Gets the network from the Tari Address + pub fn network(&self) -> Network { + self.network + } + + /// Gets the features from the Tari Address + pub fn features(&self) -> TariAddressFeatures { + self.features + } + + /// Convert Tari Address to an emoji string + pub fn to_emoji_string(&self) -> String { + // Convert the public key to bytes and compute the checksum + let bytes = self.to_bytes(); + bytes.iter().map(|b| EMOJI[*b as usize]).collect::() + } + + /// Return the public view key of a Tari Address + pub fn public_view_key(&self) -> &PublicKey { + &self.public_view_key + } + + /// Return the public spend key of a Tari Address + pub fn public_spend_key(&self) -> &PublicKey { + &self.public_spend_key + } + + /// Construct Tari Address from bytes + pub fn from_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if bytes.len() != INTERNAL_DUAL_SIZE { + return Err(TariAddressError::InvalidSize); + } + if validate_checksum(bytes).is_err() { + return Err(TariAddressError::InvalidChecksum); + } + let network = Network::try_from(bytes[0]).map_err(|_| TariAddressError::InvalidNetwork)?; + let features = TariAddressFeatures::from_bits(bytes[1]).ok_or(TariAddressError::InvalidFeatures)?; + let public_view_key = + PublicKey::from_canonical_bytes(&bytes[2..34]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + let public_spend_key = + PublicKey::from_canonical_bytes(&bytes[34..66]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + Ok(Self { + network, + features, + public_view_key, + public_spend_key, + }) + } + + /// Convert Tari Address to bytes + pub fn to_bytes(&self) -> [u8; INTERNAL_DUAL_SIZE] { + let mut buf = [0u8; INTERNAL_DUAL_SIZE]; + buf[0] = self.network.as_byte(); + buf[1] = self.features.0; + buf[2..34].copy_from_slice(self.public_view_key.as_bytes()); + buf[34..66].copy_from_slice(self.public_spend_key.as_bytes()); + let checksum = compute_checksum(&buf[0..66]); + buf[66] = checksum; + buf + } + + /// Construct Tari Address from Base58 + pub fn from_base58(hex_str: &str) -> Result { + // Due to the byte length, it can be encoded as 90 or 91 + if hex_str.len() != 90 && hex_str.len() != 91 { + return Err(TariAddressError::InvalidSize); + } + let result = panic::catch_unwind(|| hex_str.split_at(2)); + let (first, rest) = match result { + Ok((first, rest)) => (first, rest), + Err(_) => return Err(TariAddressError::InvalidCharacter), + }; + let result = panic::catch_unwind(|| first.split_at(1)); + let (network, features) = match result { + Ok((network, features)) => (network, features), + Err(_) => return Err(TariAddressError::InvalidCharacter), + }; + // let (first, rest) = hex_str.split_at_checked(2).ok_or(TariAddressError::InvalidCharacter)?; + // let (network, features) = first.split_at_checked(1).ok_or(TariAddressError::InvalidCharacter)?; + let mut result = bs58::decode(network) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverNetwork)?; + let mut features = bs58::decode(features) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverFeature)?; + let mut rest = bs58::decode(rest) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + result.append(&mut features); + result.append(&mut rest); + Self::from_bytes(result.as_slice()) + } + + /// Convert Tari Address to Base58 string + pub fn to_base58(&self) -> String { + let bytes = self.to_bytes(); + let mut network = bs58::encode(&bytes[0..1]).into_string(); + let features = bs58::encode(&bytes[1..2].to_vec()).into_string(); + let rest = bs58::encode(&bytes[2..]).into_string(); + network.push_str(&features); + network.push_str(&rest); + network + } + + /// Convert Tari dual Address to hex + pub fn to_hex(&self) -> String { + let buf = self.to_bytes(); + buf.to_hex() + } + + /// Creates Tari dual Address from hex + pub fn from_hex(hex_str: &str) -> Result { + let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + DualAddress::from_bytes(buf.as_slice()) + } +} + +#[cfg(test)] +mod test { + use tari_crypto::keys::{PublicKey as pk, SecretKey}; + + use super::*; + use crate::types::PrivateKey; + + #[test] + /// Test valid dual tari address + fn valid_emoji_id() { + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = + DualAddress::new_with_default_features(view_key.clone(), spend_key.clone(), Network::Esmeralda); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_and_one_sided()); + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = DualAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = DualAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = DualAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), &view_key); + } + + #[test] + /// Test encoding for dual tari address + fn encoding() { + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = DualAddress::new_with_default_features(view_key.clone(), spend_key.clone(), Network::Esmeralda); + + let buff = address.to_bytes(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = DualAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = DualAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.public_view_key(), address.public_view_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = DualAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = DualAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.public_view_key(), address.public_view_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + + let buff = address.to_bytes(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = DualAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = DualAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.public_view_key(), address.public_view_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = DualAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = DualAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.public_view_key(), address.public_view_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = DualAddress::new( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + + let buff = address.to_bytes(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = DualAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = DualAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.public_view_key(), address.public_view_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = DualAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = DualAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.public_view_key(), address.public_view_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + } + + #[test] + /// Test invalid size + fn invalid_size() { + // This emoji string is too short to be a valid emoji ID + let emoji_string = "🍗🌊🦂🍎🐛🔱🍟🚦🦆👃🐛🎼🛵🔮💋👙💦🍷👠🦀🐺🍪🚀🎮🎩👅🐔🐉🍍🥑💔📌🚧🐊💄🎥🎓🚗🎳🐛🚿💉🌴🧢🐵🎩👾👽🎃🤡👍🔮👒👽🎵👀🚨😷🎒👂👶🍄🏰🚑🌸🍁"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "🍗🌊🦂🍎🐛🔱🍟🚦🦆👃🐛🎼🛵🔮💋👙💦🍷👠🦀🐺🍪🚀🎮🎩👅🐔🐉🍍🥑💔📌🚧🐊💄🎥🎓🚗🎳🐛🚿💉🌴🧢🐵🎩👾👽🎃🤡👍🔮👒👽🎵👀🚨😷🎒👂👶🍄🏰🚑🌸🍁👂🎒"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + } + + #[test] + /// Test invalid emoji + fn invalid_emoji() { + // This emoji string contains an invalid emoji character + let emoji_string = "🍗🌊🦂🍎🐛🔱🍟🚦🦆👃🐛🎼🛵🔮💋👙💦🍷👠🦀🐺🍪🚀🎮🎩👅🐔🐉🍍🥑💔📌🚧🐊💄🎥🎓🚗🎳🐛🚿💉🌴🧢🐵🎩👾👽🎃🤡👍🔮👒👽🎵👀🚨😷🎒👂👶🍄🏰🚑🌸🍁🎅"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidEmoji) + ); + } + + #[test] + /// Test invalid checksum + fn invalid_checksum() { + // This emoji string contains an invalid checksum + let emoji_string = "🍗🌊🦂🍎🐛🔱🍟🚦🦆👃🐛🎼🛵🔮💋👙💦🍷👠🦀🐺🍪🚀🎮🎩👅🐔🐉🍍🥑💔📌🚧🐊💄🎥🎓🚗🎳🐛🚿💉🌴🧢🐵🎩👾👽🎃🤡👍🔮👒👽🎵👀🚨😷🎒👂👶🍄🏰🚑🌸🍁🎒"; + assert_eq!( + DualAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + } + + #[test] + /// Test invalid features + fn invalid_features() { + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let mut address = + DualAddress::new_with_default_features(view_key.clone(), spend_key.clone(), Network::Esmeralda); + address.features = TariAddressFeatures(5); + + let emoji_string = address.to_emoji_string(); + assert_eq!( + DualAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::InvalidFeatures) + ); + } + + #[test] + /// Test invalid network + fn invalid_network() { + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = DualAddress::new_with_default_features(view_key, spend_key, Network::Esmeralda); + let mut bytes = address.to_bytes(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + assert_eq!(DualAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + } + + #[test] + /// Test invalid public key + fn invalid_public_key() { + let mut bytes = [0; 67].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + DualAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + } +} diff --git a/base_layer/common_types/src/tari_address/mod.rs b/base_layer/common_types/src/tari_address/mod.rs new file mode 100644 index 0000000000..3eb0df5a8b --- /dev/null +++ b/base_layer/common_types/src/tari_address/mod.rs @@ -0,0 +1,858 @@ +// 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. + +pub mod dual_address; +mod single_address; + +use std::{ + fmt, + fmt::{Display, Error, Formatter}, + panic, + str::FromStr, +}; + +use bitflags::bitflags; +use serde::{Deserialize, Serialize}; +use tari_common::configuration::Network; +use tari_crypto::tari_utilities::ByteArray; +use tari_utilities::hex::{from_hex, Hex}; +use thiserror::Error; + +use crate::{ + emoji::EMOJI, + tari_address::{dual_address::DualAddress, single_address::SingleAddress}, + types::PublicKey, +}; + +const INTERNAL_DUAL_SIZE: usize = 67; // number of bytes used for the internal representation +const INTERNAL_SINGLE_SIZE: usize = 35; // number of bytes used for the internal representation + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TariAddressFeatures(u8); + +bitflags! { + impl TariAddressFeatures: u8 { + const INTERACTIVE = 2u8; + ///one sided payment + const ONE_SIDED = 1u8; + } +} + +impl TariAddressFeatures { + pub fn create_interactive_only() -> TariAddressFeatures { + TariAddressFeatures::INTERACTIVE + } + + pub fn create_one_sided_only() -> TariAddressFeatures { + TariAddressFeatures::ONE_SIDED + } + + pub fn create_interactive_and_one_sided() -> TariAddressFeatures { + TariAddressFeatures::INTERACTIVE | TariAddressFeatures::ONE_SIDED + } + + pub fn as_u8(&self) -> u8 { + self.0 + } +} + +impl Default for TariAddressFeatures { + fn default() -> TariAddressFeatures { + TariAddressFeatures::INTERACTIVE | TariAddressFeatures::ONE_SIDED + } +} + +impl fmt::Display for TariAddressFeatures { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.contains(TariAddressFeatures::INTERACTIVE) { + write!(f, "Interactive,")?; + } + if self.contains(TariAddressFeatures::ONE_SIDED) { + write!(f, "One-sided,")?; + } + Ok(()) + } +} + +#[derive(Debug, Error, PartialEq)] +pub enum TariAddressError { + #[error("Invalid size")] + InvalidSize, + #[error("Invalid network")] + InvalidNetwork, + #[error("Invalid features")] + InvalidFeatures, + #[error("Invalid checksum")] + InvalidChecksum, + #[error("Invalid emoji character")] + InvalidEmoji, + #[error("Invalid text character")] + InvalidCharacter, + #[error("Cannot recover public key")] + CannotRecoverPublicKey, + #[error("Cannot recover network")] + CannotRecoverNetwork, + #[error("Cannot recover feature")] + CannotRecoverFeature, + #[error("Could not recover TariAddress from string")] + InvalidAddressString, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum TariAddress { + Dual(DualAddress), + Single(SingleAddress), +} + +impl TariAddress { + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new_dual_address( + view_key: PublicKey, + spend_key: PublicKey, + network: Network, + features: TariAddressFeatures, + ) -> Self { + TariAddress::Dual(DualAddress::new(view_key, spend_key, network, features)) + } + + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new_single_address(spend_key: PublicKey, network: Network, features: TariAddressFeatures) -> Self { + TariAddress::Single(SingleAddress::new(spend_key, network, features)) + } + + /// Creates a new Tari Address from the provided public keys and network while using the default features + pub fn new_dual_address_with_default_features(view_key: PublicKey, spend_key: PublicKey, network: Network) -> Self { + TariAddress::Dual(DualAddress::new_with_default_features(view_key, spend_key, network)) + } + + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new_single_address_with_interactive_only(spend_key: PublicKey, network: Network) -> Self { + TariAddress::Single(SingleAddress::new_with_interactive_only(spend_key, network)) + } + + /// helper function to convert emojis to u8 + fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { + // The string must be the correct size, including the checksum + if !(emoji.chars().count() == INTERNAL_SINGLE_SIZE || emoji.chars().count() == INTERNAL_DUAL_SIZE) { + return Err(TariAddressError::InvalidSize); + } + if emoji.chars().count() == INTERNAL_SINGLE_SIZE { + SingleAddress::emoji_to_bytes(emoji) + } else { + DualAddress::emoji_to_bytes(emoji) + } + } + + /// Construct an TariAddress from an emoji string + pub fn from_emoji_string(emoji: &str) -> Result { + let bytes = TariAddress::emoji_to_bytes(emoji)?; + + TariAddress::from_bytes(&bytes) + } + + /// Gets the network from the Tari Address + pub fn network(&self) -> Network { + match self { + TariAddress::Dual(v) => v.network(), + TariAddress::Single(v) => v.network(), + } + } + + /// Gets the features from the Tari Address + pub fn features(&self) -> TariAddressFeatures { + match self { + TariAddress::Dual(v) => v.features(), + TariAddress::Single(v) => v.features(), + } + } + + /// Gets the checksum from the Tari Address + pub fn calculate_checksum(&self) -> u8 { + let bytes = self.to_vec(); + // -1 is safe as this the len will always be greater than 0 + bytes[bytes.len() - 1] + } + + /// Convert Tari Address to an emoji string + pub fn to_emoji_string(&self) -> String { + // Convert the public key to bytes and compute the checksum + let bytes = self.to_vec(); + bytes.iter().map(|b| EMOJI[*b as usize]).collect::() + } + + /// Return the public view key of an Tari Address + pub fn public_view_key(&self) -> Option<&PublicKey> { + match self { + TariAddress::Dual(v) => Some(v.public_view_key()), + TariAddress::Single(_) => None, + } + } + + /// Return the public spend key of an Tari Address + pub fn public_spend_key(&self) -> &PublicKey { + match self { + TariAddress::Dual(v) => v.public_spend_key(), + TariAddress::Single(v) => v.public_spend_key(), + } + } + + /// Return the public comms key of an Tari Address, which is the public spend key + pub fn comms_public_key(&self) -> &PublicKey { + match self { + TariAddress::Dual(v) => v.public_spend_key(), + TariAddress::Single(v) => v.public_spend_key(), + } + } + + /// Construct Tari Address from bytes + pub fn from_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if !(bytes.len() == INTERNAL_SINGLE_SIZE || bytes.len() == INTERNAL_DUAL_SIZE) { + return Err(TariAddressError::InvalidSize); + } + if bytes.len() == INTERNAL_SINGLE_SIZE { + Ok(TariAddress::Single(SingleAddress::from_bytes(bytes)?)) + } else { + Ok(TariAddress::Dual(DualAddress::from_bytes(bytes)?)) + } + } + + /// Convert Tari Address to bytes + pub fn to_vec(&self) -> Vec { + match self { + TariAddress::Dual(v) => v.to_bytes().to_vec(), + TariAddress::Single(v) => v.to_bytes().to_vec(), + } + } + + /// Construct Tari Address from hex + pub fn from_base58(hex_str: &str) -> Result { + if hex_str.len() < 47 { + return Err(TariAddressError::InvalidSize); + } + let result = panic::catch_unwind(|| hex_str.split_at(2)); + let (first, rest) = match result { + Ok((first, rest)) => (first, rest), + Err(_) => return Err(TariAddressError::InvalidCharacter), + }; + let result = panic::catch_unwind(|| first.split_at(1)); + let (network, features) = match result { + Ok((network, features)) => (network, features), + Err(_) => return Err(TariAddressError::InvalidCharacter), + }; + // replace this after 1.80 stable + // let (first, rest) = hex_str.split_at_checked(2).ok_or(TariAddressError::InvalidCharacter)?; + // let (network, features) = first.split_at_checked(1).ok_or(TariAddressError::InvalidCharacter)?; + let mut result = bs58::decode(network) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverNetwork)?; + let mut features = bs58::decode(features) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverFeature)?; + let mut rest = bs58::decode(rest) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + result.append(&mut features); + result.append(&mut rest); + Self::from_bytes(result.as_slice()) + } + + /// Convert Tari Address to bytes + pub fn to_base58(&self) -> String { + let bytes = self.to_vec(); + let mut network = bs58::encode(&bytes[0..1]).into_string(); + let features = bs58::encode(&bytes[1..2].to_vec()).into_string(); + let rest = bs58::encode(&bytes[2..]).into_string(); + network.push_str(&features); + network.push_str(&rest); + network + } + + /// Convert Tari Address to hex + pub fn to_hex(&self) -> String { + let buf = self.to_vec(); + buf.to_hex() + } + + /// Creates Tari Address from hex + pub fn from_hex(hex_str: &str) -> Result { + let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + TariAddress::from_bytes(buf.as_slice()) + } +} + +impl FromStr for TariAddress { + type Err = TariAddressError; + + fn from_str(key: &str) -> Result { + if let Ok(address) = TariAddress::from_emoji_string(&key.trim().replace('|', "")) { + Ok(address) + } else if let Ok(address) = TariAddress::from_base58(key) { + Ok(address) + } else if let Ok(address) = TariAddress::from_hex(key) { + Ok(address) + } else { + Err(TariAddressError::InvalidAddressString) + } + } +} + +impl Display for TariAddress { + fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> { + fmt.write_str(&self.to_emoji_string()) + } +} + +impl Default for TariAddress { + fn default() -> Self { + Self::Dual(DualAddress::default()) + } +} + +#[cfg(test)] +mod test { + use tari_crypto::keys::{PublicKey as pk, SecretKey}; + + use super::*; + use crate::{dammsum::compute_checksum, types::PrivateKey}; + + #[test] + /// Test valid single tari address + fn valid_emoji_id_single() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = + TariAddress::new_single_address_with_interactive_only(public_key.clone(), Network::Esmeralda); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + } + + #[test] + /// Test valid dual tari address + fn valid_emoji_id_dual() { + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_dual_address_with_default_features( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_and_one_sided()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Generate random public key + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_DUAL_SIZE); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = TariAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public keys for good measure + assert_eq!(emoji_id_from_public_key.public_spend_key(), &spend_key); + assert_eq!(emoji_id_from_public_key.public_view_key(), Some(&view_key)); + } + + #[test] + /// Test encoding for single tari address + fn encoding_single() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_single_address_with_interactive_only(public_key.clone(), Network::Esmeralda); + + let buff = address.to_vec(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = TariAddress::from_bytes(&buff); + assert_eq!(address_buff, Ok(address.clone())); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = TariAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = TariAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + let address_base58_string = TariAddress::from_str(&base58).unwrap(); + assert_eq!(address_base58_string, address_base58); + let address_hex_string = TariAddress::from_str(&hex).unwrap(); + assert_eq!(address_hex_string, address_hex); + let address_emoji_string = TariAddress::from_str(&emoji).unwrap(); + assert_eq!(address_emoji_string, address_emoji); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + + let buff = address.to_vec(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = TariAddress::from_bytes(&buff); + assert_eq!(address_buff, Ok(address.clone())); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = TariAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = TariAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + let address_base58_string = TariAddress::from_str(&base58).unwrap(); + assert_eq!(address_base58_string, address_base58); + let address_hex_string = TariAddress::from_str(&hex).unwrap(); + assert_eq!(address_hex_string, address_hex); + let address_emoji_string = TariAddress::from_str(&emoji).unwrap(); + assert_eq!(address_emoji_string, address_emoji); + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_single_address( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + + let buff = address.to_vec(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = TariAddress::from_bytes(&buff); + assert_eq!(address_buff, Ok(address.clone())); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = TariAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = TariAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + let address_base58_string = TariAddress::from_str(&base58).unwrap(); + assert_eq!(address_base58_string, address_base58); + let address_hex_string = TariAddress::from_str(&hex).unwrap(); + assert_eq!(address_hex_string, address_hex); + let address_emoji_string = TariAddress::from_str(&emoji).unwrap(); + assert_eq!(address_emoji_string, address_emoji); + } + + #[test] + /// Test encoding for dual tari address + fn encoding_dual() { + fn test_addres(address: TariAddress) { + let buff = address.to_vec(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = TariAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.public_view_key(), address.public_view_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = TariAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.public_view_key(), address.public_view_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = TariAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.public_view_key(), address.public_view_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = TariAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.public_view_key(), address.public_view_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + let address_base58_string = TariAddress::from_str(&base58).unwrap(); + assert_eq!(address_base58_string, address_base58); + let address_hex_string = TariAddress::from_str(&hex).unwrap(); + assert_eq!(address_hex_string, address_hex); + let address_emoji_string = TariAddress::from_str(&emoji).unwrap(); + assert_eq!(address_emoji_string, address_emoji); + } + // Generate random public key + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_dual_address_with_default_features( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + ); + test_addres(address); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + test_addres(address); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + test_addres(address); + } + + #[test] + /// Test invalid size + fn invalid_size() { + // This emoji string is too short to be a valid emoji ID + let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🦋🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎒🎒"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🦋🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎒🎒🎒🎒"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + + // This emoji string is too short to be a valid emoji ID + let emoji_string = "🍗🌊🦂🍎🐛🔱🍟🚦🦆👃🐛🎼🛵🔮💋👙💦🍷👠🦀🐺🍪🚀🎮🎩👅🐔🐉🍍🥑💔📌🚧🐊💄🎥🎓🚗🎳🐛🚿💉🌴🧢🐵🎩👾👽🎃🤡👍🔮👒👽🎵👀🚨😷🎒👂👶🍄🏰🚑🌸🍁"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "🍗🌊🦂🍎🐛🔱🍟🚦🦆👃🐛🎼🛵🔮💋👙💦🍷👠🦀🐺🍪🚀🎮🎩👅🐔🐉🍍🥑💔📌🚧🐊💄🎥🎓🚗🎳🐛🚿💉🌴🧢🐵🎩👾👽🎃🤡👍🔮👒👽🎵👀🚨😷🎒👂👶🍄🏰🚑🌸🍁👂🎒"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + } + + #[test] + /// Test invalid emoji + fn invalid_emoji() { + // This emoji string contains an invalid emoji character + let emoji_string = "🍗🌊🐉🦋🎪👛🌲🐭🦂🔨💺🎺🌕💦🚨🎼🍪⏰🍬🍚🎱💳🔱🐵🛵💡📱🌻📎🎻🐌😎👙🎹🎅"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidEmoji) + ); + } + + #[test] + /// Test invalid checksum + fn invalid_checksum() { + // This emoji string contains an invalid checksum + let emoji_string = "🍗🌈🚓🧲📌🐺🐣🙈💰🍇🎓👂📈⚽🚧🚧🚢🍫💋👽🌈🎪🚽🍪🎳💼🙈🎪😎🏠🎳👍📷🎲🎒"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + + // This emoji string contains an invalid checksum + let emoji_string = "🍗🌊🦂🍎🐛🔱🍟🚦🦆👃🐛🎼🛵🔮💋👙💦🍷👠🦀🐺🍪🚀🎮🎩👅🐔🐉🍍🥑💔📌🚧🐊💄🎥🎓🚗🎳🐛🚿💉🌴🧢🐵🎩👾👽🎃🤡👍🔮👒👽🎵👀🚨😷🎒👂👶🍄🏰🚑🌸🍁🎒"; + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + } + + #[test] + /// Test invalid network + fn invalid_network() { + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = SingleAddress::new_with_interactive_only(public_key, Network::Esmeralda); + let mut bytes = address.to_bytes(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + assert_eq!(TariAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = TariAddress::new_dual_address_with_default_features(view_key, spend_key, Network::Esmeralda); + let mut bytes = address.to_vec(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + assert_eq!(TariAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + } + + #[test] + /// Test invalid public key + fn invalid_public_key() { + let mut bytes = [0; 35].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + SingleAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + + let mut bytes = [0; 67].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..66]); + bytes[66] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + DualAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + } + + #[test] + /// Test invalid emoji + fn invalid_utf8_char() { + // This emoji string contains an invalid utf8 character + let emoji_string = "🦊 | 🦊 | 🦊 | 🦊 | 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | \ + 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | 🦊| 🦊 | \ + 🦊| 🦊 | 🦊| 🦊 | 🦊"; + assert_eq!( + TariAddress::from_base58(emoji_string), + Err(TariAddressError::InvalidCharacter) + ); + assert_eq!( + TariAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + assert_eq!( + TariAddress::from_str(emoji_string), + Err(TariAddressError::InvalidAddressString) + ); + } +} diff --git a/base_layer/common_types/src/tari_address/single_address.rs b/base_layer/common_types/src/tari_address/single_address.rs new file mode 100644 index 0000000000..0bb2027afa --- /dev/null +++ b/base_layer/common_types/src/tari_address/single_address.rs @@ -0,0 +1,472 @@ +// 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::TryFrom, panic}; + +use serde::{Deserialize, Serialize}; +use tari_common::configuration::Network; +use tari_crypto::tari_utilities::ByteArray; +use tari_utilities::hex::{from_hex, Hex}; + +use crate::{ + dammsum::{compute_checksum, validate_checksum}, + emoji::{EMOJI, REVERSE_EMOJI}, + tari_address::{TariAddressError, TariAddressFeatures, INTERNAL_SINGLE_SIZE}, + types::PublicKey, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct SingleAddress { + network: Network, + features: TariAddressFeatures, + public_spend_key: PublicKey, +} + +impl SingleAddress { + /// Creates a new Tari Address from the provided public keys, network and features + pub fn new(spend_key: PublicKey, network: Network, features: TariAddressFeatures) -> SingleAddress { + Self { + network, + features, + public_spend_key: spend_key, + } + } + + /// Creates a new Tari Address from the provided public keys and network while using the default features + pub fn new_with_interactive_only(spend_key: PublicKey, network: Network) -> SingleAddress { + Self { + network, + features: TariAddressFeatures::create_interactive_only(), + public_spend_key: spend_key, + } + } + + /// helper function to convert emojis to u8 + pub fn emoji_to_bytes(emoji: &str) -> Result, TariAddressError> { + // The string must be the correct size, including the checksum + if emoji.chars().count() != INTERNAL_SINGLE_SIZE { + return Err(TariAddressError::InvalidSize); + } + + // Convert the emoji string to a byte array + let mut bytes = Vec::::with_capacity(INTERNAL_SINGLE_SIZE); + for c in emoji.chars() { + if let Some(i) = REVERSE_EMOJI.get(&c) { + bytes.push(*i); + } else { + return Err(TariAddressError::InvalidEmoji); + } + } + Ok(bytes) + } + + /// Construct an TariAddress from an emoji string + pub fn from_emoji_string(emoji: &str) -> Result { + let bytes = Self::emoji_to_bytes(emoji)?; + + Self::from_bytes(&bytes) + } + + /// Gets the network from the Tari Address + pub fn network(&self) -> Network { + self.network + } + + /// Gets the features from the Tari Address + pub fn features(&self) -> TariAddressFeatures { + self.features + } + + /// Convert Tari Address to an emoji string + pub fn to_emoji_string(&self) -> String { + // Convert the public key to bytes and compute the checksum + let bytes = self.to_bytes(); + bytes.iter().map(|b| EMOJI[*b as usize]).collect::() + } + + /// Return the public spend key of a Tari Address + pub fn public_spend_key(&self) -> &PublicKey { + &self.public_spend_key + } + + /// Construct Tari Address from bytes + pub fn from_bytes(bytes: &[u8]) -> Result + where Self: Sized { + if bytes.len() != INTERNAL_SINGLE_SIZE { + return Err(TariAddressError::InvalidSize); + } + if validate_checksum(bytes).is_err() { + return Err(TariAddressError::InvalidChecksum); + } + let network = Network::try_from(bytes[0]).map_err(|_| TariAddressError::InvalidNetwork)?; + let features = TariAddressFeatures::from_bits(bytes[1]).ok_or(TariAddressError::InvalidFeatures)?; + let public_spend_key = + PublicKey::from_canonical_bytes(&bytes[2..34]).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + Ok(Self { + network, + features, + public_spend_key, + }) + } + + /// Convert Tari Address to bytes + pub fn to_bytes(&self) -> [u8; INTERNAL_SINGLE_SIZE] { + let mut buf = [0u8; INTERNAL_SINGLE_SIZE]; + buf[0] = self.network.as_byte(); + buf[1] = self.features.0; + buf[2..34].copy_from_slice(self.public_spend_key.as_bytes()); + let checksum = compute_checksum(&buf[0..34]); + buf[34] = checksum; + buf + } + + /// Construct Tari Address from Base58 + pub fn from_base58(hex_str: &str) -> Result { + // Due to the byte length, it can be encoded as 46, 47 or 48 chars + if hex_str.len() != 46 && hex_str.len() != 47 && hex_str.len() != 48 { + return Err(TariAddressError::InvalidSize); + } + let result = panic::catch_unwind(|| hex_str.split_at(2)); + let (first, rest) = match result { + Ok((first, rest)) => (first, rest), + Err(_) => return Err(TariAddressError::InvalidCharacter), + }; + let result = panic::catch_unwind(|| first.split_at(1)); + let (network, features) = match result { + Ok((network, features)) => (network, features), + Err(_) => return Err(TariAddressError::InvalidCharacter), + }; + // let (first, rest) = hex_str.split_at_checked(2).ok_or(TariAddressError::InvalidCharacter)?; + // let (network, features) = first.split_at_checked(1).ok_or(TariAddressError::InvalidCharacter)?; + let mut result = bs58::decode(network) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverNetwork)?; + let mut features = bs58::decode(features) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverFeature)?; + let mut rest = bs58::decode(rest) + .into_vec() + .map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + result.append(&mut features); + result.append(&mut rest); + Self::from_bytes(result.as_slice()) + } + + /// Convert Tari Address to Base58 + pub fn to_base58(&self) -> String { + let bytes = self.to_bytes(); + let mut network = bs58::encode(&bytes[0..1]).into_string(); + let features = bs58::encode(&bytes[1..2].to_vec()).into_string(); + let rest = bs58::encode(&bytes[2..]).into_string(); + network.push_str(&features); + network.push_str(&rest); + network + } + + /// Convert Tari single Address to hex + pub fn to_hex(&self) -> String { + let buf = self.to_bytes(); + buf.to_hex() + } + + /// Creates Tari single Address from hex + pub fn from_hex(hex_str: &str) -> Result { + let buf = from_hex(hex_str).map_err(|_| TariAddressError::CannotRecoverPublicKey)?; + SingleAddress::from_bytes(buf.as_slice()) + } +} +#[cfg(test)] +mod test { + use tari_crypto::keys::{PublicKey as pk, SecretKey}; + + use super::*; + use crate::types::PrivateKey; + + #[test] + /// Test valid single tari address + fn valid_emoji_id() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = SingleAddress::new_with_interactive_only(public_key.clone(), Network::Esmeralda); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = SingleAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_interactive_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = SingleAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let emoji_id_from_public_key = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + assert_eq!(emoji_id_from_public_key.public_spend_key(), &public_key); + + let features = emoji_id_from_public_key.features(); + assert_eq!(features, TariAddressFeatures::create_one_sided_only()); + + // Check the size of the corresponding emoji string + let emoji_string = emoji_id_from_public_key.to_emoji_string(); + assert_eq!(emoji_string.chars().count(), INTERNAL_SINGLE_SIZE); + + // Generate an emoji ID from the emoji string and ensure we recover it + let emoji_id_from_emoji_string = SingleAddress::from_emoji_string(&emoji_string).unwrap(); + assert_eq!(emoji_id_from_emoji_string.to_emoji_string(), emoji_string); + + // Return to the original public key for good measure + assert_eq!(emoji_id_from_emoji_string.public_spend_key(), &public_key); + } + + #[test] + /// Test encoding for single tari address + fn encoding() { + // Generate random public key + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = SingleAddress::new_with_interactive_only(public_key.clone(), Network::Esmeralda); + + let buff = address.to_bytes(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = SingleAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = SingleAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = SingleAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = SingleAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_interactive_only(), + ); + + let buff = address.to_bytes(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = SingleAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = SingleAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = SingleAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = SingleAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + + // Generate random public key + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an emoji ID from the public key and ensure we recover it + let address = SingleAddress::new( + public_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + + let buff = address.to_bytes(); + let base58 = address.to_base58(); + let hex = address.to_hex(); + let emoji = address.to_emoji_string(); + + let address_buff = SingleAddress::from_bytes(&buff).unwrap(); + assert_eq!(address_buff.public_spend_key(), address.public_spend_key()); + assert_eq!(address_buff.network(), address.network()); + assert_eq!(address_buff.features(), address.features()); + + let address_base58 = SingleAddress::from_base58(&base58).unwrap(); + assert_eq!(address_base58.public_spend_key(), address.public_spend_key()); + assert_eq!(address_base58.network(), address.network()); + assert_eq!(address_base58.features(), address.features()); + + let address_hex = SingleAddress::from_hex(&hex).unwrap(); + assert_eq!(address_hex.public_spend_key(), address.public_spend_key()); + assert_eq!(address_hex.network(), address.network()); + assert_eq!(address_hex.features(), address.features()); + + let address_emoji = SingleAddress::from_emoji_string(&emoji).unwrap(); + assert_eq!(address_emoji.public_spend_key(), address.public_spend_key()); + assert_eq!(address_emoji.network(), address.network()); + assert_eq!(address_emoji.features(), address.features()); + } + + #[test] + /// Test invalid size + fn invalid_size() { + // This emoji string is too short to be a valid emoji ID + let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🦋🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎒🎒"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + // This emoji string is too long to be a valid emoji ID + let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🦋🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎒🎒🎒🎒"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidSize) + ); + } + + #[test] + /// Test invalid emoji + fn invalid_emoji() { + // This emoji string contains an invalid emoji character + let emoji_string = "🍗🌊🐉🦋🎪👛🌲🐭🦂🔨💺🎺🌕💦🚨🎼🍪⏰🍬🍚🎱💳🔱🐵🛵💡📱🌻📎🎻🐌😎👙🎹🎅"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidEmoji) + ); + } + + #[test] + /// Test invalid features + fn invalid_features() { + let mut rng = rand::thread_rng(); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let mut address = SingleAddress::new_with_interactive_only(spend_key.clone(), Network::Esmeralda); + address.features = TariAddressFeatures(5); + + let emoji_string = address.to_emoji_string(); + assert_eq!( + SingleAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::InvalidFeatures) + ); + } + + #[test] + /// Test invalid checksum + fn invalid_checksum() { + // This emoji string contains an invalid checksum + let emoji_string = "🍗🌈🚓🧲📌🐺🐣🙈💰🍇🎓👂📈⚽🚧🚧🚢🍫💋👽🌈🎪🚽🍪🎳💼🙈🎪😎🏠🎳👍📷🎲🎒"; + assert_eq!( + SingleAddress::from_emoji_string(emoji_string), + Err(TariAddressError::InvalidChecksum) + ); + } + + #[test] + /// Test invalid network + fn invalid_network() { + let mut rng = rand::thread_rng(); + let public_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + // Generate an address using a valid network and ensure it's not valid on another network + let address = SingleAddress::new_with_interactive_only(public_key, Network::Esmeralda); + let mut bytes = address.to_bytes(); + // this is an invalid network + bytes[0] = 123; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + assert_eq!(SingleAddress::from_bytes(&bytes), Err(TariAddressError::InvalidNetwork)); + } + + #[test] + /// Test invalid public key + fn invalid_public_key() { + let mut bytes = [0; 35].to_vec(); + bytes[0] = Network::Esmeralda.as_byte(); + bytes[1] = TariAddressFeatures::create_interactive_and_one_sided().0; + bytes[2] = 1; + let checksum = compute_checksum(&bytes[0..34]); + bytes[34] = checksum; + let emoji_string = bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + + // This emoji string contains an invalid checksum + assert_eq!( + SingleAddress::from_emoji_string(&emoji_string), + Err(TariAddressError::CannotRecoverPublicKey) + ); + } +} diff --git a/base_layer/common_types/src/wallet_types.rs b/base_layer/common_types/src/wallet_types.rs index 052df957ea..8eac9c5a05 100644 --- a/base_layer/common_types/src/wallet_types.rs +++ b/base_layer/common_types/src/wallet_types.rs @@ -26,19 +26,71 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use strum_macros::EnumString; +use tari_common::configuration::Network; +use tari_crypto::keys::PublicKey as PublicKeyTrait; -#[derive(Debug, EnumString, Clone, Copy, Serialize, Deserialize)] +use crate::types::{PrivateKey, PublicKey}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub enum WalletType { - Software, - Ledger(usize), + #[default] + DerivedKeys, + Ledger(LedgerWallet), + ProvidedKeys(ProvidedKeysWallet), } impl Display for WalletType { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - WalletType::Software => write!(f, "Software"), - WalletType::Ledger(account) => write!(f, "Ledger({account})"), + WalletType::DerivedKeys => write!(f, "Derived wallet"), + WalletType::Ledger(ledger_wallet) => write!(f, "Ledger({ledger_wallet})"), + WalletType::ProvidedKeys(provided_keys_wallet) => write!(f, "Provided Keys ({provided_keys_wallet})"), } } } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProvidedKeysWallet { + pub public_spend_key: PublicKey, + pub private_spend_key: Option, + pub view_key: PrivateKey, +} + +impl Display for ProvidedKeysWallet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "public spend key {}", self.public_spend_key)?; + write!(f, "public view key{}", PublicKey::from_secret_key(&self.view_key))?; + Ok(()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LedgerWallet { + account: u64, + pub public_alpha: Option, + pub network: Network, + pub view_key: Option, +} + +impl Display for LedgerWallet { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "account {}", self.account)?; + write!(f, "pubkey {}", self.public_alpha.is_some())?; + Ok(()) + } +} + +impl LedgerWallet { + pub fn new(account: u64, network: Network, public_alpha: Option, view_key: Option) -> Self { + Self { + account, + public_alpha, + network, + view_key, + } + } + + pub fn account_bytes(&self) -> Vec { + self.account.to_le_bytes().to_vec() + } +} diff --git a/base_layer/contacts/Cargo.toml b/base_layer/contacts/Cargo.toml index 74d866f5e7..d4e59f0cbb 100644 --- a/base_layer/contacts/Cargo.toml +++ b/base_layer/contacts/Cargo.toml @@ -23,7 +23,7 @@ diesel = { version = "2.0.3", features = ["sqlite", "serde_json", "chrono", "64- diesel_migrations = "2.0.0" futures = { version = "^0.3.1", features = ["compat", "std"] } log = "0.4.6" -num-derive = "0.3.3" +num-derive = "0.4.2" num-traits = "0.2.15" prost = "0.11.9" rand = "0.8" diff --git a/base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/down.sql b/base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/down.sql new file mode 100644 index 0000000000..ec471b151f --- /dev/null +++ b/base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/down.sql @@ -0,0 +1 @@ +ALTER TABLE messages drop sent_at; diff --git a/base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/up.sql b/base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/up.sql new file mode 100644 index 0000000000..37bad863b2 --- /dev/null +++ b/base_layer/contacts/migrations/2024-04-30-155100_add_sent_timestamp/up.sql @@ -0,0 +1 @@ + ALTER TABLE messages ADD sent_at TIMESTAMP; diff --git a/base_layer/contacts/migrations/2024-05-27-145200_add_from_to_fields/up.sql b/base_layer/contacts/migrations/2024-05-27-145200_add_from_to_fields/up.sql new file mode 100644 index 0000000000..435865ecf1 --- /dev/null +++ b/base_layer/contacts/migrations/2024-05-27-145200_add_from_to_fields/up.sql @@ -0,0 +1,9 @@ +DROP INDEX idx_messages_address; + +ALTER TABLE messages DROP address; + +ALTER TABLE messages ADD receiver_address BLOB NOT NULL; +ALTER TABLE messages ADD sender_address BLOB NOT NULL; + +CREATE INDEX idx_messages_receiver_address ON messages (receiver_address); +CREATE INDEX idx_messages_sender_address ON messages (sender_address); diff --git a/base_layer/contacts/proto/message.proto b/base_layer/contacts/proto/message.proto index 92c3b7d7fc..f1ca2553b5 100644 --- a/base_layer/contacts/proto/message.proto +++ b/base_layer/contacts/proto/message.proto @@ -7,9 +7,10 @@ package tari.contacts.chat; message Message { bytes body = 1; repeated MessageMetadata metadata = 2; - bytes address = 3; - DirectionEnum direction = 4; - bytes message_id = 5; + bytes receiver_address = 3; + bytes sender_address = 4; + DirectionEnum direction = 5; + bytes message_id = 6; } enum DirectionEnum { diff --git a/base_layer/contacts/src/chat_client/src/client.rs b/base_layer/contacts/src/chat_client/src/client.rs index 801f318fc7..e04ecaf078 100644 --- a/base_layer/contacts/src/chat_client/src/client.rs +++ b/base_layer/contacts/src/chat_client/src/client.rs @@ -49,6 +49,7 @@ pub trait ChatClient { async fn check_online_status(&self, address: &TariAddress) -> Result; fn create_message(&self, receiver: &TariAddress, message: String) -> Message; async fn get_messages(&self, sender: &TariAddress, limit: u64, page: u64) -> Result, Error>; + async fn get_message(&self, id: &[u8]) -> Result; async fn send_message(&self, message: Message) -> Result<(), Error>; async fn send_read_receipt(&self, message: Message) -> Result<(), Error>; async fn get_conversationalists(&self) -> Result, Error>; @@ -58,9 +59,11 @@ pub trait ChatClient { pub struct Client { pub config: ApplicationConfig, + pub user_agent: String, pub contacts: Option, pub identity: Arc, pub shutdown: Shutdown, + pub address: TariAddress, } impl Debug for Client { @@ -80,16 +83,28 @@ impl Drop for Client { } impl Client { - pub fn new(identity: Arc, config: ApplicationConfig) -> Self { + pub fn new( + identity: Arc, + address: TariAddress, + config: ApplicationConfig, + user_agent: String, + ) -> Self { Self { config, + user_agent, contacts: None, identity, shutdown: Shutdown::new(), + address, } } - pub fn sideload(config: ApplicationConfig, contacts: ContactsServiceHandle) -> Self { + pub fn sideload( + config: ApplicationConfig, + contacts: ContactsServiceHandle, + user_agent: String, + address: TariAddress, + ) -> Self { // Create a placeholder ID. It won't be written or used when sideloaded. let identity = Arc::new(NodeIdentity::random( &mut OsRng, @@ -99,9 +114,11 @@ impl Client { Self { config, + user_agent, contacts: Some(contacts), identity, shutdown: Shutdown::new(), + address, } } @@ -112,9 +129,14 @@ impl Client { if self.contacts.is_none() { let signal = self.shutdown.to_signal(); - let (contacts, comms_node) = networking::start(self.identity.clone(), self.config.clone(), signal) - .await - .map_err(|e| Error::InitializationError(e.to_string()))?; + let (contacts, comms_node) = networking::start( + self.identity.clone(), + self.config.clone(), + signal, + self.user_agent.clone(), + ) + .await + .map_err(|e| Error::InitializationError(e.to_string()))?; if !self.config.peer_seeds.peer_seeds.is_empty() { loop { @@ -141,14 +163,6 @@ impl Client { #[async_trait] impl ChatClient for Client { - fn address(&self) -> TariAddress { - TariAddress::from_public_key(self.identity.public_key(), self.config.chat_client.network) - } - - fn shutdown(&mut self) { - self.shutdown.trigger(); - } - async fn add_contact(&self, address: &TariAddress) -> Result<(), Error> { if let Some(mut contacts_service) = self.contacts.clone() { contacts_service.upsert_contact(address.into()).await?; @@ -157,6 +171,24 @@ impl ChatClient for Client { Ok(()) } + fn add_metadata(&self, mut message: Message, key: String, data: String) -> Message { + let metadata = MessageMetadata { + key: key.into_bytes(), + data: data.into_bytes(), + }; + + message.push(metadata); + message + } + + fn create_message(&self, receiver: &TariAddress, message: String) -> Message { + MessageBuilder::new() + .receiver_address(receiver.clone()) + .sender_address(self.address().clone()) + .message(message) + .build() + } + async fn check_online_status(&self, address: &TariAddress) -> Result { if let Some(mut contacts_service) = self.contacts.clone() { let contact = contacts_service.get_contact(address.clone()).await?; @@ -167,47 +199,42 @@ impl ChatClient for Client { Ok(ContactOnlineStatus::Offline) } - async fn send_message(&self, message: Message) -> Result<(), Error> { + async fn get_messages(&self, sender: &TariAddress, limit: u64, page: u64) -> Result, Error> { + let mut messages = vec![]; if let Some(mut contacts_service) = self.contacts.clone() { - contacts_service.send_message(message).await?; + messages = contacts_service.get_messages(sender.clone(), limit, page).await?; } - Ok(()) + Ok(messages) } - async fn get_messages(&self, sender: &TariAddress, limit: u64, page: u64) -> Result, Error> { - let mut messages = vec![]; + async fn get_message(&self, message_id: &[u8]) -> Result { + match self.contacts.clone() { + Some(mut contacts_service) => contacts_service.get_message(message_id).await.map_err(|e| e.into()), + None => Err(Error::InitializationError( + "ContactsServiceHandle unavailable".to_string(), + )), + } + } + + async fn send_message(&self, message: Message) -> Result<(), Error> { if let Some(mut contacts_service) = self.contacts.clone() { - messages = contacts_service.get_messages(sender.clone(), limit, page).await?; + contacts_service.send_message(message).await?; } - Ok(messages) + Ok(()) } async fn send_read_receipt(&self, message: Message) -> Result<(), Error> { if let Some(mut contacts_service) = self.contacts.clone() { contacts_service - .send_read_confirmation(message.address.clone(), message.message_id) + .send_read_confirmation(message.receiver_address.clone(), message.message_id) .await?; } Ok(()) } - fn create_message(&self, receiver: &TariAddress, message: String) -> Message { - MessageBuilder::new().address(receiver.clone()).message(message).build() - } - - fn add_metadata(&self, mut message: Message, key: String, data: String) -> Message { - let metadata = MessageMetadata { - key: key.into_bytes(), - data: data.into_bytes(), - }; - - message.push(metadata); - message - } - async fn get_conversationalists(&self) -> Result, Error> { let mut addresses = vec![]; if let Some(mut contacts_service) = self.contacts.clone() { @@ -216,6 +243,14 @@ impl ChatClient for Client { Ok(addresses) } + + fn address(&self) -> TariAddress { + self.address.clone() + } + + fn shutdown(&mut self) { + self.shutdown.trigger(); + } } pub async fn wait_for_connectivity(comms: CommsNode) -> anyhow::Result<()> { diff --git a/base_layer/contacts/src/chat_client/src/config.rs b/base_layer/contacts/src/chat_client/src/config.rs index 07363ad8ce..c8f0aee7bc 100644 --- a/base_layer/contacts/src/chat_client/src/config.rs +++ b/base_layer/contacts/src/chat_client/src/config.rs @@ -99,7 +99,6 @@ impl Default for ChatClientConfig { fn default() -> Self { let p2p = P2pConfig { datastore_path: PathBuf::from("peer_db/chat_client"), - user_agent: format!("tari/chat_client/{}", env!("CARGO_PKG_VERSION")), dht: DhtConfig { database_url: DbConnectionUrl::file("data/chat_client/dht.sqlite"), ..Default::default() @@ -162,11 +161,11 @@ impl ChatClientConfig { log_verbosity: Some(5), // Trace p2p: P2pConfig { datastore_path: PathBuf::from("peer_db/chat_client"), - user_agent: format!("tari/chat_client/{}", env!("CARGO_PKG_VERSION")), dht: DhtConfig { database_url: DbConnectionUrl::file("data/chat_client/dht.sqlite"), network_discovery: NetworkDiscoveryConfig { enabled: true, + initial_peer_sync_delay: None, ..NetworkDiscoveryConfig::default() }, saf: SafConfig { diff --git a/base_layer/contacts/src/chat_client/src/networking.rs b/base_layer/contacts/src/chat_client/src/networking.rs index 0dc3a0f124..fcedb1fdc8 100644 --- a/base_layer/contacts/src/chat_client/src/networking.rs +++ b/base_layer/contacts/src/chat_client/src/networking.rs @@ -53,6 +53,7 @@ pub async fn start( node_identity: Arc, config: ApplicationConfig, shutdown_signal: ShutdownSignal, + user_agent: String, ) -> Result<(ContactsServiceHandle, CommsNode), NetworkingError> { create_chat_storage(&config.chat_client.db_file)?; let backend = connect_to_db(config.chat_client.db_file)?; @@ -69,6 +70,7 @@ pub async fn start( let fut = StackBuilder::new(shutdown_signal) .add_initializer(P2pInitializer::new( p2p_config.clone(), + user_agent, config.peer_seeds.clone(), config.chat_client.network, node_identity, diff --git a/base_layer/contacts/src/contacts_service/handle.rs b/base_layer/contacts/src/contacts_service/handle.rs index 35282f40ea..8e39fa647f 100644 --- a/base_layer/contacts/src/contacts_service/handle.rs +++ b/base_layer/contacts/src/contacts_service/handle.rs @@ -138,6 +138,7 @@ pub enum ContactsServiceRequest { GetContactOnlineStatus(Contact), SendMessage(TariAddress, Message), GetMessages(TariAddress, i64, i64), + GetMessage(Vec), SendReadConfirmation(TariAddress, Confirmation), GetConversationalists, } @@ -150,6 +151,7 @@ pub enum ContactsServiceResponse { Contacts(Vec), OnlineStatus(ContactOnlineStatus), Messages(Vec), + Message(Message), MessageSent, ReadConfirmationSent, Conversationalists(Vec), @@ -277,10 +279,24 @@ impl ContactsServiceHandle { } } + pub async fn get_message(&mut self, message_id: &[u8]) -> Result { + match self + .request_response_service + .call(ContactsServiceRequest::GetMessage(message_id.to_vec())) + .await?? + { + ContactsServiceResponse::Message(message) => Ok(message), + _ => Err(ContactsServiceError::UnexpectedApiResponse), + } + } + pub async fn send_message(&mut self, message: Message) -> Result<(), ContactsServiceError> { match self .request_response_service - .call(ContactsServiceRequest::SendMessage(message.address.clone(), message)) + .call(ContactsServiceRequest::SendMessage( + message.receiver_address.clone(), + message, + )) .await?? { ContactsServiceResponse::MessageSent => Ok(()), diff --git a/base_layer/contacts/src/contacts_service/service.rs b/base_layer/contacts/src/contacts_service/service.rs index 87f6379dbb..006976e877 100644 --- a/base_layer/contacts/src/contacts_service/service.rs +++ b/base_layer/contacts/src/contacts_service/service.rs @@ -224,9 +224,8 @@ where T: ContactsBackend + 'static error!(target: LOG_TARGET, "Error handling request: {:?}", e); e }); - let _result = reply_tx.send(response).map_err(|e| { + let _result = reply_tx.send(response).inspect_err(|_| { error!(target: LOG_TARGET, "Failed to send reply"); - e }); }, @@ -256,8 +255,8 @@ where T: ContactsBackend + 'static request: ContactsServiceRequest, ) -> Result { match request { - ContactsServiceRequest::GetContact(pk) => { - let result = self.db.get_contact(pk.clone()); + ContactsServiceRequest::GetContact(address) => { + let result = self.db.get_contact(address.clone()); if let Ok(ref contact) = result { self.liveness.check_add_monitored_peer(contact.node_id.clone()).await?; }; @@ -299,6 +298,7 @@ where T: ContactsBackend + 'static Ok(result.map(ContactsServiceResponse::Messages)?) }, ContactsServiceRequest::SendMessage(address, mut message) => { + message.sent_at = Utc::now().naive_utc().timestamp() as u64; let ob_message = OutboundDomainMessage::from(MessageDispatch::Message(message.clone())); message.stored_at = Utc::now().naive_utc().timestamp() as u64; @@ -349,6 +349,10 @@ where T: ContactsBackend + 'static let result = self.db.get_conversationlists(); Ok(result.map(ContactsServiceResponse::Conversationalists)?) }, + ContactsServiceRequest::GetMessage(message_id) => { + let result = self.db.get_message(message_id); + Ok(result.map(ContactsServiceResponse::Message)?) + }, } } @@ -563,7 +567,7 @@ where T: ContactsBackend + 'static fn handle_connectivity_event(&mut self, event: ConnectivityEvent) { use ConnectivityEvent::{PeerBanned, PeerDisconnected}; match event { - PeerDisconnected(node_id) | PeerBanned(node_id) => { + PeerDisconnected(node_id, _) | PeerBanned(node_id) => { if let Some(pos) = self.liveness_data.iter().position(|p| *p.node_id() == node_id) { debug!( target: LOG_TARGET, @@ -581,8 +585,10 @@ where T: ContactsBackend + 'static message: Message, source_public_key: CommsPublicKey, ) -> Result<(), ContactsServiceError> { + if source_public_key != *message.sender_address.comms_public_key() { + return Err(ContactsServiceError::MessageSourceDoesNotMatchOrigin); + } let our_message = Message { - address: TariAddress::from_public_key(&source_public_key, message.address.network()), stored_at: EpochTime::now().as_u64(), ..message }; @@ -613,7 +619,7 @@ where T: ContactsBackend + 'static &mut self, message: &Message, ) -> Result<(), ContactsServiceError> { - let address = &message.address; + let address = &message.sender_address; let confirmation = MessageDispatch::DeliveryConfirmation(Confirmation { message_id: message.message_id.clone(), timestamp: message.stored_at, @@ -660,7 +666,7 @@ where T: ContactsBackend + 'static Ok(contact) => contact, Err(_) => Contact::from(&address), }; - let encryption = OutboundEncryption::EncryptFor(Box::new(address.public_key().clone())); + let encryption = OutboundEncryption::EncryptFor(Box::new(address.public_spend_key().clone())); match self.get_online_status(&contact).await { Ok(ContactOnlineStatus::Online) => { @@ -669,7 +675,7 @@ where T: ContactsBackend + 'static comms_outbound .send_direct_encrypted( - address.public_key().clone(), + address.public_spend_key().clone(), message, encryption, "contact service messaging".to_string(), @@ -681,7 +687,7 @@ where T: ContactsBackend + 'static info!(target: LOG_TARGET, "Chat message being sent via closest broadcast"); let mut comms_outbound = self.dht.outbound_requester(); comms_outbound - .closest_broadcast(address.public_key().clone(), encryption, vec![], message) + .closest_broadcast(address.public_spend_key().clone(), encryption, vec![], message) .await?; }, }; diff --git a/base_layer/contacts/src/contacts_service/storage/database.rs b/base_layer/contacts/src/contacts_service/storage/database.rs index e7dbc80180..593403c678 100644 --- a/base_layer/contacts/src/contacts_service/storage/database.rs +++ b/base_layer/contacts/src/contacts_service/storage/database.rs @@ -187,6 +187,11 @@ where T: ContactsBackend + 'static } } + pub fn get_message(&self, message_id: Vec) -> Result { + let db_clone = self.db.clone(); + fetch!(db_clone, message_id, Message) + } + pub fn save_message(&self, message: Message) -> Result<(), ContactsServiceStorageError> { self.db .write(WriteOperation::Insert(Box::new(DbValue::Message(Box::new(message)))))?; diff --git a/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs b/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs index 40f9bf5e50..d783e74a49 100644 --- a/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs +++ b/base_layer/contacts/src/contacts_service/storage/sqlite_db.rs @@ -95,7 +95,7 @@ where TContactServiceDbConnection: PooledDbConnection match ContactSql::find_by_address(&address.to_bytes(), &mut conn) { + DbKey::Contact(address) => match ContactSql::find_by_address(&address.to_vec(), &mut conn) { Ok(c) => Some(DbValue::Contact(Box::new(Contact::try_from(c)?))), Err(ContactsServiceStorageError::DieselError(DieselError::NotFound)) => None, Err(e) => return Err(e), @@ -112,7 +112,7 @@ where TContactServiceDbConnection: PooledDbConnection, _>>()?, )), DbKey::Messages(address, limit, page) => { - match MessagesSql::find_by_address(&address.to_bytes(), *limit, *page, &mut conn) { + match MessagesSql::find_by_address(&address.to_vec(), *limit, *page, &mut conn) { Ok(messages_sql) => { let mut messages = vec![]; for m in &messages_sql { @@ -156,7 +156,7 @@ where TContactServiceDbConnection: PooledDbConnection { - if ContactSql::find_by_address_and_update(&mut conn, &k.to_bytes(), UpdateContact { + if ContactSql::find_by_address_and_update(&mut conn, &k.to_vec(), UpdateContact { alias: Some(c.clone().alias), last_seen: None, latency: None, @@ -189,7 +189,7 @@ where TContactServiceDbConnection: PooledDbConnection match k { - DbKey::Contact(k) => match ContactSql::find_by_address_and_delete(&mut conn, &k.to_bytes()) { + DbKey::Contact(k) => match ContactSql::find_by_address_and_delete(&mut conn, &k.to_vec()) { Ok(c) => { return Ok(Some(DbValue::Contact(Box::new(Contact::try_from(c)?)))); }, @@ -221,23 +221,16 @@ where TContactServiceDbConnection: PooledDbConnection for Contact { let address = TariAddress::from_bytes(&o.address).map_err(|_| ContactsServiceStorageError::ConversionError)?; Ok(Self { // Public key must always be the master data source for node ID here - node_id: NodeId::from_key(address.public_key()), + node_id: NodeId::from_key(address.public_spend_key()), address, alias: o.alias, last_seen: o.last_seen, @@ -164,8 +164,8 @@ impl From for ContactSql { fn from(o: Contact) -> Self { Self { // Public key must always be the master data source for node ID here - node_id: NodeId::from_key(o.address.public_key()).to_vec(), - address: o.address.to_bytes().to_vec(), + node_id: NodeId::from_key(o.address.public_spend_key()).to_vec(), + address: o.address.to_vec(), alias: o.alias, last_seen: o.last_seen, latency: o.latency.map(|val| val as i32), diff --git a/base_layer/contacts/src/contacts_service/storage/types/messages.rs b/base_layer/contacts/src/contacts_service/storage/types/messages.rs index 83c1339836..1f7669de3a 100644 --- a/base_layer/contacts/src/contacts_service/storage/types/messages.rs +++ b/base_layer/contacts/src/contacts_service/storage/types/messages.rs @@ -23,8 +23,7 @@ use std::convert::TryFrom; use chrono::NaiveDateTime; -use diesel::{prelude::*, SqliteConnection}; -use serde_json; +use diesel::prelude::*; use tari_common_sqlite::util::diesel_ext::ExpectedRowsExtension; use tari_common_types::tari_address::TariAddress; @@ -41,11 +40,13 @@ use crate::{ #[diesel(table_name = messages)] #[diesel(primary_key(message_id))] pub struct MessagesSqlInsert { - pub address: Vec, + pub receiver_address: Vec, + pub sender_address: Vec, pub message_id: Vec, pub body: Vec, pub metadata: Vec, pub stored_at: NaiveDateTime, + pub sent_at: NaiveDateTime, pub direction: i32, } @@ -53,11 +54,13 @@ pub struct MessagesSqlInsert { #[diesel(table_name = messages)] #[diesel(primary_key(message_id))] pub struct MessagesSql { - pub address: Vec, + pub receiver_address: Vec, + pub sender_address: Vec, pub message_id: Vec, pub body: Vec, pub metadata: Vec, pub stored_at: NaiveDateTime, + pub sent_at: NaiveDateTime, pub delivery_confirmation_at: Option, pub read_confirmation_at: Option, pub direction: i32, @@ -82,6 +85,34 @@ impl MessagesSqlInsert { impl MessagesSql { /// Find a particular message by their address, if it exists + pub fn find_by_receiver_address( + address: &[u8], + limit: i64, + page: i64, + conn: &mut SqliteConnection, + ) -> Result, ContactsServiceStorageError> { + Ok(messages::table + .filter(messages::receiver_address.eq(address)) + .order(messages::stored_at.desc()) + .offset(limit * page) + .limit(limit) + .load::(conn)?) + } + + pub fn find_by_sender_address( + address: &[u8], + limit: i64, + page: i64, + conn: &mut SqliteConnection, + ) -> Result, ContactsServiceStorageError> { + Ok(messages::table + .filter(messages::sender_address.eq(address)) + .order(messages::stored_at.desc()) + .offset(limit * page) + .limit(limit) + .load::(conn)?) + } + pub fn find_by_address( address: &[u8], limit: i64, @@ -89,7 +120,11 @@ impl MessagesSql { conn: &mut SqliteConnection, ) -> Result, ContactsServiceStorageError> { Ok(messages::table - .filter(messages::address.eq(address)) + .filter( + messages::sender_address + .eq(address) + .or(messages::receiver_address.eq(address)), + ) .order(messages::stored_at.desc()) .offset(limit * page) .limit(limit) @@ -123,7 +158,11 @@ impl MessagesSql { pub fn find_all_conversationlists( conn: &mut SqliteConnection, ) -> Result>, ContactsServiceStorageError> { - Ok(messages::table.select(messages::address).distinct().load(conn)?) + Ok(messages::table + .select(messages::sender_address) + .select(messages::receiver_address) + .distinct() + .load(conn)?) } } @@ -133,23 +172,28 @@ impl TryFrom for Message { #[allow(clippy::cast_sign_loss)] fn try_from(o: MessagesSql) -> Result { - let address = TariAddress::from_bytes(&o.address).map_err(|_| ContactsServiceStorageError::ConversionError)?; + let receiver_address = + TariAddress::from_bytes(&o.receiver_address).map_err(|_| ContactsServiceStorageError::ConversionError)?; + let sender_address = + TariAddress::from_bytes(&o.sender_address).map_err(|_| ContactsServiceStorageError::ConversionError)?; let metadata: Vec = serde_json::from_str( &String::from_utf8(o.metadata.clone()).map_err(|_| ContactsServiceStorageError::ConversionError)?, ) .map_err(|_| ContactsServiceStorageError::ConversionError)?; Ok(Self { - address, + metadata, + body: o.body, + receiver_address, + sender_address, direction: Direction::from_byte( u8::try_from(o.direction).map_err(|_| ContactsServiceStorageError::ConversionError)?, ) .ok_or(ContactsServiceStorageError::ConversionError)?, + sent_at: o.sent_at.timestamp() as u64, stored_at: o.stored_at.timestamp() as u64, delivery_confirmation_at: Some(o.stored_at.timestamp() as u64), read_confirmation_at: Some(o.stored_at.timestamp() as u64), - body: o.body, - metadata, message_id: o.message_id, }) } @@ -162,13 +206,16 @@ impl TryFrom for MessagesSqlInsert { fn try_from(o: Message) -> Result { let metadata = serde_json::to_string(&o.metadata).map_err(|_| ContactsServiceStorageError::ConversionError)?; - Ok(Self { - address: o.address.to_bytes().to_vec(), + receiver_address: o.receiver_address.to_vec(), + sender_address: o.sender_address.to_vec(), message_id: o.message_id, body: o.body, metadata: metadata.into_bytes().to_vec(), - stored_at: NaiveDateTime::from_timestamp_opt(o.stored_at as i64, 0).unwrap(), + stored_at: NaiveDateTime::from_timestamp_opt(o.stored_at as i64, 0) + .ok_or(ContactsServiceStorageError::ConversionError)?, + sent_at: NaiveDateTime::from_timestamp_opt(o.sent_at as i64, 0) + .ok_or(ContactsServiceStorageError::ConversionError)?, direction: i32::from(o.direction.as_byte()), }) } diff --git a/base_layer/contacts/src/contacts_service/types/contact.rs b/base_layer/contacts/src/contacts_service/types/contact.rs index 19153a786c..c75aeb3806 100644 --- a/base_layer/contacts/src/contacts_service/types/contact.rs +++ b/base_layer/contacts/src/contacts_service/types/contact.rs @@ -44,7 +44,7 @@ impl Contact { ) -> Self { Self { alias, - node_id: NodeId::from_key(address.public_key()), + node_id: NodeId::from_key(address.public_spend_key()), address, last_seen, latency, @@ -58,7 +58,7 @@ impl From<&TariAddress> for Contact { Self { alias: address.to_emoji_string(), address: address.clone(), - node_id: NodeId::from_key(address.public_key()), + node_id: NodeId::from_key(address.public_spend_key()), last_seen: None, latency: None, favourite: false, diff --git a/base_layer/contacts/src/contacts_service/types/message.rs b/base_layer/contacts/src/contacts_service/types/message.rs index d259f8da78..1238e714ad 100644 --- a/base_layer/contacts/src/contacts_service/types/message.rs +++ b/base_layer/contacts/src/contacts_service/types/message.rs @@ -28,7 +28,6 @@ use serde::{Deserialize, Serialize}; use tari_common_types::tari_address::TariAddress; use tari_comms_dht::domain_message::OutboundDomainMessage; use tari_p2p::tari_message::TariMessageType; -use tari_utilities::ByteArray; use crate::contacts_service::proto; @@ -36,8 +35,10 @@ use crate::contacts_service::proto; pub struct Message { pub body: Vec, pub metadata: Vec, - pub address: TariAddress, + pub receiver_address: TariAddress, + pub sender_address: TariAddress, pub direction: Direction, + pub sent_at: u64, pub stored_at: u64, pub delivery_confirmation_at: Option, pub read_confirmation_at: Option, @@ -86,7 +87,8 @@ impl TryFrom for Message { Ok(Self { body: message.body, metadata, - address: TariAddress::from_bytes(&message.address).map_err(|e| e.to_string())?, + receiver_address: TariAddress::from_bytes(&message.receiver_address).map_err(|e| e.to_string())?, + sender_address: TariAddress::from_bytes(&message.sender_address).map_err(|e| e.to_string())?, // A Message from a proto::Message will always be an inbound message direction: Direction::Inbound, message_id: message.message_id, @@ -104,7 +106,8 @@ impl From for proto::Message { .iter() .map(|m| proto::MessageMetadata::from(m.clone())) .collect(), - address: message.address.to_bytes().to_vec(), + receiver_address: message.receiver_address.to_vec(), + sender_address: message.sender_address.to_vec(), direction: i32::from(message.direction.as_byte()), message_id: message.message_id, } diff --git a/base_layer/contacts/src/contacts_service/types/message_builder.rs b/base_layer/contacts/src/contacts_service/types/message_builder.rs index 638c20362a..68511e34cd 100644 --- a/base_layer/contacts/src/contacts_service/types/message_builder.rs +++ b/base_layer/contacts/src/contacts_service/types/message_builder.rs @@ -44,10 +44,19 @@ impl MessageBuilder { } } - pub fn address(&self, address: TariAddress) -> Self { + pub fn receiver_address(&self, receiver_address: TariAddress) -> Self { Self { inner: Message { - address, + receiver_address, + ..self.inner.clone() + }, + } + } + + pub fn sender_address(&self, sender_address: TariAddress) -> Self { + Self { + inner: Message { + sender_address, ..self.inner.clone() }, } diff --git a/base_layer/contacts/src/schema.rs b/base_layer/contacts/src/schema.rs index 9a57007c44..5e28c72b74 100644 --- a/base_layer/contacts/src/schema.rs +++ b/base_layer/contacts/src/schema.rs @@ -13,11 +13,13 @@ diesel::table! { diesel::table! { messages (message_id) { - address -> Binary, + receiver_address -> Binary, + sender_address -> Binary, message_id -> Binary, body -> Binary, metadata -> Binary, stored_at -> Timestamp, + sent_at -> Timestamp, delivery_confirmation_at -> Nullable, read_confirmation_at -> Nullable, direction -> Integer, diff --git a/base_layer/contacts/tests/contacts_service.rs b/base_layer/contacts/tests/contacts_service.rs index 215d0d7456..98a8eb1a99 100644 --- a/base_layer/contacts/tests/contacts_service.rs +++ b/base_layer/contacts/tests/contacts_service.rs @@ -93,16 +93,17 @@ pub fn setup_contacts_service( allow_test_addresses: true, listener_liveness_allowlist_cidrs: StringList::new(), listener_liveness_max_sessions: 0, - user_agent: "tari/test-contacts-service".to_string(), rpc_max_simultaneous_sessions: 0, rpc_max_sessions_per_peer: 0, - listener_liveness_check_interval: None, + listener_self_liveness_check_interval: None, }; let peer_message_subscription_factory = Arc::new(subscription_factory); let shutdown = Shutdown::new(); + let user_agent = format!("tari/tests/{}", env!("CARGO_PKG_VERSION")); let fut = StackBuilder::new(shutdown.to_signal()) .add_initializer(P2pInitializer::new( comms_config, + user_agent, PeerSeedsConfig::default(), Network::LocalNet, node_identity.clone(), @@ -150,7 +151,7 @@ pub fn test_contacts_service() { let mut contacts = Vec::new(); for i in 0..5 { let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::default()); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::default()); contacts.push(Contact::new(random::string(8), address, None, None, false)); @@ -168,7 +169,7 @@ pub fn test_contacts_service() { assert_eq!(contact, contacts[0]); let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::default()); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::default()); let contact = runtime.block_on(contacts_service.get_contact(address.clone())); match contact { @@ -233,7 +234,7 @@ pub fn test_message_pagination() { let (mut contacts_service, _node_identity, _shutdown) = setup_contacts_service(&mut runtime, backend); let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::default()); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::default()); let contact = Contact::new(random::string(8), address.clone(), None, None, false); runtime.block_on(contacts_service.upsert_contact(contact)).unwrap(); @@ -242,7 +243,8 @@ pub fn test_message_pagination() { for num in 0..8 { let message = MessageBuilder::new() .message(format!("Test {:?}", num)) - .address(address.clone()) + .receiver_address(address.clone()) + .sender_address(address.clone()) .build(); contacts_db.save_message(message.clone()).expect("Message to be saved"); @@ -272,7 +274,8 @@ pub fn test_message_pagination() { for num in 0..3000 { let message = MessageBuilder::new() .message(format!("Test {:?}", num)) - .address(address.clone()) + .receiver_address(address.clone()) + .sender_address(address.clone()) .build(); contacts_db.save_message(message.clone()).expect("Message to be saved"); diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index b7d681d821..d61d1a2116 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -24,12 +24,12 @@ base_node = [ base_node_proto = [] benches = ["base_node"] ledger = [ - "ledger-transport", - "ledger-transport-hid" + "minotari_ledger_wallet_comms", ] metrics = ["tari_metrics"] [dependencies] +minotari_ledger_wallet_comms = { workspace = true, optional = true } tari_common = { workspace = true } tari_common_types = { workspace = true } tari_comms = { workspace = true } @@ -66,15 +66,13 @@ fs2 = "0.4.0" futures = { version = "^0.3.16", features = ["async-await"] } hex = "0.4.2" integer-encoding = "3.0" -ledger-transport = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20", optional = true } -ledger-transport-hid = { git = "https://github.com/Zondax/ledger-rs", rev = "20e2a20", optional = true } lmdb-zero = "0.4.4" log = "0.4" log-mdc = "0.1.0" -monero = { version = "0.20.0", features = ["serde-crate"], optional = true } +monero = { version = "0.21.0", features = ["serde-crate"], optional = true } newtype-ops = "0.1.4" num-traits = "0.2.15" -num-derive = "0.3.3" +num-derive = "0.4.2" num-format = "0.4.0" once_cell = "1.8.0" prost = "0.11.9" @@ -100,7 +98,6 @@ tiny-keccak = { package = "tari-tiny-keccak", version = "2.0.2", features = [ criterion = { version = "0.4.0" } tari_p2p = { workspace = true, features = ["test-mocks"] } tari_test_utils = { workspace = true } -curve25519-dalek = { package = "tari-curve25519-dalek", version = "4.0.3" } # SQLite required for the integration tests libsqlite3-sys = { version = "0.25.1", features = ["bundled"] } config = { version = "0.14.0" } @@ -116,3 +113,6 @@ tari_features = { workspace = true } [[bench]] name = "mempool" harness = false + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tari_target_network_mainnet)', 'cfg(tari_target_network_nextnet)', 'cfg(tari_target_network_testnet)'] } diff --git a/base_layer/core/benches/mempool.rs b/base_layer/core/benches/mempool.rs index 7bf8393368..511e8f1905 100644 --- a/base_layer/core/benches/mempool.rs +++ b/base_layer/core/benches/mempool.rs @@ -54,7 +54,7 @@ mod benches { num_outputs: usize, features: OutputFeatures, ) -> std::io::Result>> { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut txs = Vec::new(); for _ in 0..num_txs { let (tx, _, _) = 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 3b0e88f082..58d8768ac2 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 @@ -230,12 +230,11 @@ mod test { mock::{create_p2p_liveness_mock, LivenessMockState}, LivenessRequest, Metadata, - PingPongEvent, }; use tari_service_framework::reply_channel; use tari_test_utils::unpack_enum; use tari_utilities::epoch_time::EpochTime; - use tokio::{sync::broadcast, task}; + use tokio::task; use super::*; use crate::base_node::comms_interface::{CommsInterfaceError, NodeCommsRequest, NodeCommsResponse}; diff --git a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs index da100c5e16..161a0c8f07 100644 --- a/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs +++ b/base_layer/core/src/base_node/comms_interface/inbound_handlers.rs @@ -257,6 +257,15 @@ where B: BlockchainBackend + 'static }, NodeCommsRequest::GetNewBlockTemplate(request) => { let best_block_header = self.blockchain_db.fetch_tip_header().await?; + let last_seen_hash = self.mempool.get_last_seen_hash().await?; + if last_seen_hash != FixedHash::default() && best_block_header.hash() != &last_seen_hash { + warn!( + target: LOG_TARGET, + "Mempool out of sync - last seen hash '{}' does not match the tip hash '{}'.", + last_seen_hash, best_block_header.hash() + ); + return Err(CommsInterfaceError::InternalError("Mempool out of sync".to_string())); + } let mut header = BlockHeader::from_previous(best_block_header.header()); let constants = self.consensus_manager.consensus_constants(header.height); header.version = constants.blockchain_version(); @@ -600,15 +609,16 @@ where B: BlockchainBackend + 'static ) -> Result { let NewBlock { header, - coinbase_kernel, - coinbase_output, + coinbase_kernels, + coinbase_outputs, kernel_excess_sigs: excess_sigs, } = new_block; // If the block is empty, we dont have to ask for the block, as we already have the full block available // to us. if excess_sigs.is_empty() { let block = BlockBuilder::new(header.version) - .with_coinbase_utxo(coinbase_output, coinbase_kernel) + .add_outputs(coinbase_outputs) + .add_kernels(coinbase_kernels) .with_header(header) .build(); return Ok(block); @@ -645,7 +655,8 @@ where B: BlockchainBackend + 'static metrics::compact_block_tx_misses(header.height).set(missing_excess_sigs.len() as i64); let mut builder = BlockBuilder::new(header.version) - .with_coinbase_utxo(coinbase_output, coinbase_kernel) + .add_outputs(coinbase_outputs) + .add_kernels(coinbase_kernels) .with_transactions(known_transactions); if missing_excess_sigs.is_empty() { @@ -993,6 +1004,10 @@ where B: BlockchainBackend + 'static debug!(target: LOG_TARGET, "Target difficulty {} for PoW {}", target, pow_algo); Ok(target) } + + pub async fn get_last_seen_hash(&self) -> Result { + self.mempool.get_last_seen_hash().await.map_err(|e| e.into()) + } } impl Clone for InboundNodeCommsHandlers { diff --git a/base_layer/core/src/base_node/proto/request.rs b/base_layer/core/src/base_node/proto/request.rs index 189ce82ab8..1085810304 100644 --- a/base_layer/core/src/base_node/proto/request.rs +++ b/base_layer/core/src/base_node/proto/request.rs @@ -20,7 +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. -use std::convert::{From, TryFrom, TryInto}; +use std::convert::{TryFrom, TryInto}; use tari_common_types::types::PrivateKey; use tari_utilities::ByteArray; diff --git a/base_layer/core/src/base_node/proto/response.rs b/base_layer/core/src/base_node/proto/response.rs index 182c8c4653..87a4956541 100644 --- a/base_layer/core/src/base_node/proto/response.rs +++ b/base_layer/core/src/base_node/proto/response.rs @@ -22,7 +22,7 @@ use std::{ convert::{TryFrom, TryInto}, - iter::{FromIterator, Iterator}, + iter::FromIterator, sync::Arc, }; diff --git a/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs b/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs index e44673939a..19c88d1278 100644 --- a/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs +++ b/base_layer/core/src/base_node/state_machine_service/states/events_and_states.rs @@ -206,10 +206,10 @@ impl StateInfo { .unwrap_or_else(|| "".to_string()) ), HeaderSync(None) => "Starting header sync".to_string(), - HeaderSync(Some(info)) => format!("Syncing headers: {}", info.sync_progress_string()), + HeaderSync(Some(info)) => format!("Syncing headers: {}", info.sync_progress_string_headers()), HorizonSync(info) => info.to_progress_string(), - BlockSync(info) => format!("Syncing blocks: {}", info.sync_progress_string()), + BlockSync(info) => format!("Syncing blocks: {}", info.sync_progress_string_blocks()), Listening(_) => "Listening".to_string(), SyncFailed(details) => format!("Sync failed: {}", details), } @@ -299,7 +299,15 @@ impl BlockSyncInfo { } } - pub fn sync_progress_string(&self) -> String { + pub fn sync_progress_string_headers(&self) -> String { + self.sync_progress("hdrs") + } + + pub fn sync_progress_string_blocks(&self) -> String { + self.sync_progress("blks") + } + + fn sync_progress(&self, item: &str) -> String { format!( "({}) {}/{} ({:.0}%){}{}", self.sync_peer.node_id().short_str(), @@ -308,7 +316,7 @@ impl BlockSyncInfo { (self.local_height as f64 / self.tip_height as f64 * 100.0).floor(), self.sync_peer .items_per_second() - .map(|bps| format!(" {:.2?} blks/s", bps)) + .map(|bps| format!(" {:.2?} {}/s", bps, item)) .unwrap_or_default(), self.sync_peer .calc_avg_latency() @@ -320,6 +328,6 @@ impl BlockSyncInfo { impl Display for BlockSyncInfo { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { - writeln!(f, "Syncing {}", self.sync_progress_string()) + writeln!(f, "Syncing {}", self.sync_progress_string_blocks()) } } diff --git a/base_layer/core/src/base_node/sync/config.rs b/base_layer/core/src/base_node/sync/config.rs index cb9afb1528..187de3aca9 100644 --- a/base_layer/core/src/base_node/sync/config.rs +++ b/base_layer/core/src/base_node/sync/config.rs @@ -50,19 +50,20 @@ pub struct BlockchainSyncConfig { pub validation_concurrency: usize, /// The RPC deadline to set on sync clients. If this deadline is reached, a new sync peer will be selected for /// sync. + #[serde(with = "serializers::seconds")] pub rpc_deadline: Duration, } impl Default for BlockchainSyncConfig { fn default() -> Self { Self { - initial_max_sync_latency: Duration::from_secs(15), - max_latency_increase: Duration::from_secs(2), - ban_period: Duration::from_secs(60 * 60 * 2), // 2 hours - short_ban_period: Duration::from_secs(240), // 4 mins + initial_max_sync_latency: Duration::from_secs(240), // Syncing many full blocks over tor require this + max_latency_increase: Duration::from_secs(10), // Syncing many full blocks over tor require this + ban_period: Duration::from_secs(60 * 60 * 2), // 2 hours + short_ban_period: Duration::from_secs(240), // 4 mins forced_sync_peers: Default::default(), validation_concurrency: 6, - rpc_deadline: Duration::from_secs(15), + rpc_deadline: Duration::from_secs(240), // Syncing many full blocks over tor require this } } } diff --git a/base_layer/core/src/base_node/sync/header_sync/validator.rs b/base_layer/core/src/base_node/sync/header_sync/validator.rs index 3824d09fba..6a60cc81b7 100644 --- a/base_layer/core/src/base_node/sync/header_sync/validator.rs +++ b/base_layer/core/src/base_node/sync/header_sync/validator.rs @@ -240,10 +240,8 @@ mod test { use super::*; use crate::{ - blocks::{BlockHeader, BlockHeaderAccumulatedData}, - chain_storage::async_db::AsyncBlockchainDb, - consensus::ConsensusManager, - proof_of_work::{randomx_factory::RandomXFactory, PowAlgorithm}, + blocks::BlockHeader, + proof_of_work::PowAlgorithm, test_helpers::blockchain::{create_new_blockchain, TempDatabase}, }; @@ -316,7 +314,6 @@ mod test { mod validate { use super::*; - use crate::{blocks::BlockHeaderValidationError, validation::ValidationError}; #[tokio::test] async fn it_passes_if_headers_are_valid() { diff --git a/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs b/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs index 6e12642639..6c40bd7756 100644 --- a/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs +++ b/base_layer/core/src/base_node/sync/horizon_state_sync/synchronizer.rs @@ -251,7 +251,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> { let config = RpcClient::builder() .with_deadline(self.config.rpc_deadline) - .with_deadline_grace_period(Duration::from_secs(3)); + .with_deadline_grace_period(Duration::from_secs(5)); let mut client = conn .connect_rpc_using_builder::(config) diff --git a/base_layer/core/src/base_node/sync/sync_peer.rs b/base_layer/core/src/base_node/sync/sync_peer.rs index 4594f78aa5..f10891b316 100644 --- a/base_layer/core/src/base_node/sync/sync_peer.rs +++ b/base_layer/core/src/base_node/sync/sync_peer.rs @@ -140,7 +140,6 @@ mod test { use tari_crypto::keys::{PublicKey, SecretKey}; use super::*; - use crate::base_node::chain_metadata_service::PeerChainMetadata; // Helper function to generate a peer with a given latency fn generate_peer(latency: Option) -> SyncPeer { diff --git a/base_layer/core/src/blocks/block.rs b/base_layer/core/src/blocks/block.rs index ccb16c7c81..a3fb89bc25 100644 --- a/base_layer/core/src/blocks/block.rs +++ b/base_layer/core/src/blocks/block.rs @@ -247,34 +247,34 @@ pub struct NewBlock { /// The block header. pub header: BlockHeader, /// Coinbase kernel of the block - pub coinbase_kernel: TransactionKernel, + pub coinbase_kernels: Vec, /// Coinbase output of the block - pub coinbase_output: TransactionOutput, + pub coinbase_outputs: Vec, /// The scalar `s` component of the kernel excess signatures of the transactions contained in the block. pub kernel_excess_sigs: Vec, } impl From<&Block> for NewBlock { fn from(block: &Block) -> Self { - let coinbase_kernel = block + let coinbase_kernels = block .body .kernels() - .iter() - .find(|k| k.features.contains(KernelFeatures::COINBASE_KERNEL)) - .cloned() - .expect("Invalid block given to NewBlock::from, no coinbase kernel"); - let coinbase_output = block + .clone() + .into_iter() + .filter(|k| k.features.contains(KernelFeatures::COINBASE_KERNEL)) + .collect(); + let coinbase_outputs = block .body .outputs() - .iter() - .find(|o| o.features.output_type == OutputType::Coinbase) - .cloned() - .expect("Invalid block given to NewBlock::from, no coinbase output"); + .clone() + .into_iter() + .filter(|o| o.features.output_type == OutputType::Coinbase) + .collect(); Self { header: block.header.clone(), - coinbase_kernel, - coinbase_output, + coinbase_kernels, + coinbase_outputs, kernel_excess_sigs: block .body .kernels() diff --git a/base_layer/core/src/blocks/faucets/esmeralda_faucet.json b/base_layer/core/src/blocks/faucets/esmeralda_faucet.json index cf352f2c82..a726b40e0a 100644 --- a/base_layer/core/src/blocks/faucets/esmeralda_faucet.json +++ b/base_layer/core/src/blocks/faucets/esmeralda_faucet.json @@ -1,101 +1,21 @@ -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"18316784fc62aac449213bca20a33626700352f6b204b65143dc51d22198320c","proof":null,"script":"73","sender_offset_public_key":"6a993b4232a9b4643e25c1fcd941b9b859570c4b8b8e37adcd7dff8b2fb05d37","metadata_signature":{"ephemeral_commitment":"e0cc205c24142e589fdcbf073c736d200b521df7e4d89d4852ef41f38968ea11","ephemeral_pubkey":"52628f4860a74a5b3d819b665de42a5ffa5d039ad8a5142ffaa2d5ab3a12a624","u_a":"3a40f62235dcb983298f60a33c5796df9ab385ed53c10e70fa58e7bcc6679409","u_x":"0d7807dea476231fcc51ea6a07123964b874d94e825efe07500d81f1e116da05","u_y":"95ac4c595f3caace6ad65eaeba9d391dd413a87468e73753cfef8bdd8c050b08"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"9a48932646e8fe97a62097d380610260583e60cd8878eecb4bc1cbd04b373a39","proof":null,"script":"73","sender_offset_public_key":"0cd17ad67993b03c4ea5aebdcda0e1cea7e981dfcb0419dd749e199087b07f19","metadata_signature":{"ephemeral_commitment":"ca5f681df7a2696a981c1e7343f34eae180474f63fba32bc23a999cc0b0c8c08","ephemeral_pubkey":"a89cafb45e4055388e079bfab27e5170f5e54d26ea66dba4b92760ba146ac653","u_a":"7c2073347ceff0c18c889ac35372b5c18f56f9e412b463535790f1fa98c4750a","u_x":"2fe58bbbd21fc2d1d91496912475a499df4ec01d5899e003583445856ddc9e04","u_y":"8d794e84a786c12e3113b6087778ce94385d2f727452bcdf2d0564da499b0702"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b2ca2e2289f73c28cbec971ef239dceb276af4a1bebe1857161e5f87e7c8d567","proof":null,"script":"73","sender_offset_public_key":"3c2f3bba85ef6873d4ea2d6aee19d3a5eff8020eb6424d3b647efaf98334735c","metadata_signature":{"ephemeral_commitment":"ba8a82123e7bb3d6b83058573d10c79b350a6d78c46d31e5703d258fea961308","ephemeral_pubkey":"5e36a92ccffaf156415fbc965e984b58e49d734603656e1c33a3372f40bf6c0c","u_a":"c06ac93c3e47cd0a7bb1fa182ec8b0e0eda6a457cd4055f5cc362ce5dd27240c","u_x":"138f965814d6bbc941043c2828930d543e572723ea5027bd5dbe5ce2ba0a0b07","u_y":"b78d3bbe23376c10d1a68867120394f7f04ca6e796f62fa12cbe22c06a1fbc0d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"da091115aa919271fa8e1d6373b0acb572e28d28fd4bdb0c12b2b429d2f6d163","proof":null,"script":"73","sender_offset_public_key":"0877239f78c5ed814ddfb6220ed2757d69748e33abf771fff232e16c717cb87d","metadata_signature":{"ephemeral_commitment":"fc17a925b48b293b4aa18d319064de5dbd323288bf4885a4cb5fba2564c2a157","ephemeral_pubkey":"d26f9bba5a85657dd1ce05417b2a440aa404837d72eab5efbd1f56045671a942","u_a":"8fe15ce86b7cea68ff8ca3cec94b7f19ee985b95167f14b2df40b60552205c02","u_x":"17457c2cf4949e18719cfdcab48fa0ed86d1a35189d1ee76e219c611db405e05","u_y":"0635bc537448e9d5694f8fad49978f8def559ffbf6e0c58cabf0d6fa37c41a0b"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"701e5a084f4b0386de1ad79a2c94b3d302b23205d2adc18f8724e1193809c559","proof":null,"script":"73","sender_offset_public_key":"4490f5b78cbb03c65ecf216226dcecb6f99a428c69904044c2b530bd84afaa62","metadata_signature":{"ephemeral_commitment":"8097a7d1e49dfcecc5204b88f2da021d37aab0b09d0ff88288dba487b6c09161","ephemeral_pubkey":"1c7c2e4ccb5f4cc87a5474d05b09af0f3cbd9c736ab65dca565be5bae8215810","u_a":"29cec5e31e1f7d083c7d37f5d9d5378ae1b16d99b47cd4421d4f1a4afaea1e07","u_x":"def28d0b33589d6ba540218a5193164207d72058557bdb4d251eedcca2833706","u_y":"442567af9f81e33b955cfc935a34301f85503da868a623c43a67af524cc5b204"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"deb7eb3c860bdaf1c25a81a8a4d304b4ada65797eac171c37b10757675cf637f","proof":null,"script":"73","sender_offset_public_key":"e0b76730463833a2d1aa94ff011c7b6224e671d4fa5056c3089827fd8d13c32a","metadata_signature":{"ephemeral_commitment":"fe6df27ae7a7a840bfef275aa501f6693526a04f10a607421e935687558f0933","ephemeral_pubkey":"90bc4a528b22ab4deff8cc0bfce94a4eba55d8d41b8da76bc459c8fab4c9fe62","u_a":"860770dbd48874a194219f58b42bfc3fc524942436da753ca9405aa5ec75da00","u_x":"736c88184865a0f6d7c41af944da94bb16e30e60c4dbc19c673ced6050c3d60d","u_y":"cea209e7175df5e98774b0b889124eb7b923bba446d357d9220acfec30c9ee08"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"4a572e55a556e99e3dfde29cf11895c903d2d043dc69ef86c7c593bc996f706c","proof":null,"script":"73","sender_offset_public_key":"b4757b6e8e466cf9cac1e51d36e0c90b23fdfa23ee938729cd35395ed489200c","metadata_signature":{"ephemeral_commitment":"e0430a84b516b5151f76f870495e0ec1754d32d0b33edac7592c73871a62f20d","ephemeral_pubkey":"52e4a96db1892ea0d284cf74ea13919602e80f8c4753f2065fc609b38be88643","u_a":"84ce30eff399c148e61b622e55a4c145f269b8add7c30f4c0940b4434e71e101","u_x":"f3dec6defbfbbed453b86966bb082bab08f9b1a2ed6075b7a26d0b2b1045f60f","u_y":"f70608335b24f20e37c66f57b609657e476625e3b8e5a96961a8928942e23006"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"c006242699b91b994e10d59e69dda386d5b8bf46974b570f2f77fb2eea4caa38","proof":null,"script":"73","sender_offset_public_key":"54e44fb43ee9d7e1f930ebf51a57aa22a8de596794ad50dd38e9d6a2f80b5a6c","metadata_signature":{"ephemeral_commitment":"d8794e01603de8850a2852ea106fb07c3b132f5e597288ff4240ed819e411a24","ephemeral_pubkey":"c8b13805dae5f977db0b65c94676ee9136b029cb1693b6e93fb1043febdb3956","u_a":"93d2427177706fda0e7f914f7fe34158dd5c75224c35d2f9b6646501c5e3d306","u_x":"cebb0af70c8e11ad24ad3cd2d1a688fe7573d2ecd861f15ca3de77dcc2d17f01","u_y":"7832df7d0f5cf7424c6df21187f0fdd04a49b0a069359ea6e87f3c9a84428000"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"3416fe74985f96abdb366714c3049785da87eeaa187f2b98662b6937c8e67e47","proof":null,"script":"73","sender_offset_public_key":"ea3cb01b9ba848969efd6a4375ba7d1c83f358cb399665bcb4e48758596d2419","metadata_signature":{"ephemeral_commitment":"e49b61bb1cce4c01baff599b8eee9ddc106d3d1468b9bcc8544bf2e236b08b3a","ephemeral_pubkey":"1aa6c4f909bb63728cdcf680f5018f8a67944ed25da5d907ff92152e7cec355c","u_a":"8ca6d914f915bf792ef3e2fff1fa42079773ff5b52f9d235b782ef99d5924e01","u_x":"940cb59fb4ee2d1f35a0caa3e29da8bc3b7272d44591fda6c5513482b3c6e305","u_y":"f5f443af9ff39aca479c35857df2f3b6c862f083cb4f5352d9b9a8a9525e7400"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"7676dbcb7fdbd1e5563a56f208a0b44ec48768a1f567eb4282553460038aea78","proof":null,"script":"73","sender_offset_public_key":"ae199f5f16a2ca941c4a30a99bb600229d691be0b7cccbc9fd2c1d941c555979","metadata_signature":{"ephemeral_commitment":"da0bff47745b0ffb3764ffb5904d5ad3dcce6705157b47c89a547c413b402b17","ephemeral_pubkey":"e20c007991a8ec7c35c48191cb34d6c318b3dd6febcba3c6f9857f94602b7b4a","u_a":"2d7e9fc1fb4c1bcc19c0d7965c44f8b09ba21eaa5d22ddc3cbbbf9e2585f8d05","u_x":"0654340474adb09d8f510693bb83b5cfad3e52dfa040e2cd31978a4841101b02","u_y":"0c5d9afe5fdb96b82fa33fb3a46decbe1fe293ba28cbce82f71b3f842bc9e609"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"328761d82d01d86c1323f7a049dfbc1c0276e1352df812b36924450454019467","proof":null,"script":"73","sender_offset_public_key":"66a7e2ff67eca7471d24436240a4c8c952428698c40f72c91aacc09a210a0509","metadata_signature":{"ephemeral_commitment":"7a08114fc6a9946fee756cdb616b5188d17b68daef414467143fdfabf83a0631","ephemeral_pubkey":"18fc69ce71cc8253e4867754ff136429e19c9f8cf5821102ef1b58ddea2b6470","u_a":"7c5cbf898c38fdfed18ddc161d1d0bdf2176740cfbf0bce2f2a33517d8285002","u_x":"201328eee22435b3d8253aa0d503fb1b0d9b9023d0a631091efcbe191e72500b","u_y":"ecb92bfc37e48c252b9ed02dddf6f92178492022ca0c5f9834278c2f9747ea0d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"a824aa8eb45aac0768eb526d5bcd28c88f5e2ad1793e4a9fbe471e947ef36265","proof":null,"script":"73","sender_offset_public_key":"64d58beb87800537eff932678935d1a98b19fc67a18ebd0f640f617014106d65","metadata_signature":{"ephemeral_commitment":"38bb4f6fd809a1aa6055cc11ee40453c20f80f659c8301be55ba07c07e440b7b","ephemeral_pubkey":"869760c3712924ddaf319abd217b24436e1073f7044108e48cf20f03e1e7b147","u_a":"fa3368880914809e417abf0a5b6157b3a95fd3b4736c0099c44decae0bd2a30c","u_x":"c3b691b947e6e8b69f132ebb8328e43ddd2baa9d6d92bb4331cc33fa47baa80d","u_y":"e872d0bd461f2353a5fba91ee839a0e0d81e2f13da5e5ad31f0f4eecb2d2d10c"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"78e2c2facae0aa7853a2dfbccf06593856a64d9c6109e558a45003e424bbd663","proof":null,"script":"73","sender_offset_public_key":"3696c815a2912e38dc79e7c44b094ac347f6b82bfa8fbe64bccd39e25b32b860","metadata_signature":{"ephemeral_commitment":"42070e174c37af7a46ee4cc04907a1fbfdf612c44b6d31778bf6537a1895d221","ephemeral_pubkey":"3c0f10977084f55052e40a06a3596e6d3409cd6b5f7d613f3b1e74978c8f561e","u_a":"a4ce1dc8d70a5aa8d171582747d0058e1f7805c8a384b2d37a240eb65ac0f500","u_x":"83c25167a9a8f6756b859fb94e07d9191c35421d368236f409a0afc3a77eab0c","u_y":"a6470916a4c38df2edb41067bbff9e08b773ca41039a0421e212212afe8d9a06"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"f0e02c8e2a5653f4bd5059aa119c6d54ebe7032688d29e7c73ef9979275adf50","proof":null,"script":"73","sender_offset_public_key":"5e23e15dc472e8719330b2b4f2ada040b5259258a8c3941c32fbe70d963ea00d","metadata_signature":{"ephemeral_commitment":"ae60f7619fb0aecff61da0ffb8895590b9fde354f95c8c0b9aa8241a820b446a","ephemeral_pubkey":"76e40ce27fe58a3c811ca8733d4319a7caee1f5c39d1ff1d71b4d8ec8b93001a","u_a":"d65250dbaeee17bdf71f18aad93a6a5dc45053edb3de7b16963e2a4b43696402","u_x":"907b335114549cdfce3f0b729e19c805338f78371b41f3984c5f7e41ac154907","u_y":"d8120ce8d206166729f4507536c5f234a6ba15581cff06332f83404b74219404"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"fe5af90a40ecd9d941c7652a1b523b09029b619c2a6c38eed180612dd19f8873","proof":null,"script":"73","sender_offset_public_key":"d2edb1407aaf70a436f270d192b2dcf60f88ff75a1fed0b222baaf990f2ad954","metadata_signature":{"ephemeral_commitment":"e00e88cdabc5f1b94d4c3ec11b56351e3bba8866d90a0cc7941f8a883043b664","ephemeral_pubkey":"b8c153a63b12babf8c22618d12847eec9f84025e91f752871a1bf7003d1b7371","u_a":"95c9b7cd8a67b288175e6eccdfec163a3703d5a7b052fe885d112407cbcf6105","u_x":"8e48f930506141b62f828ce910b31f846801f83c9bbeb361ec860ebd0cc6b90c","u_y":"d8cd5c79cfebb9ed18d22ce2f5a1f680230d590be81b8c6cd061791a986df70c"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"f65dc778c90c975f76aec0cd30daa15f5330e680a54d76eb6b86828d6f979f08","proof":null,"script":"73","sender_offset_public_key":"0c5156b1d4a4f338a909d31eae337cfff3158f1e1b536ee642a65305ce64a44f","metadata_signature":{"ephemeral_commitment":"ca7e811627136693b2bb0e731cff9b4b110bc797c2e99bbeaf70cff0eeb6455b","ephemeral_pubkey":"e689450e6e66d3f5080322763cdeb89eb83d05fe2a26b73206b0c371fb20e94b","u_a":"72371d4dab299852d812a444b53e8c761f8108b85963f11789675116daf71607","u_x":"754c71fb04a8ecdececdc38d7abfb4664b584539854dbbeb71e39f23f1bace02","u_y":"372456dbe3f5327f82e39e654df1df74cf23a5b99b87040aa22175b2e30a1000"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"66e8ceff9b9277bfaee854d5c62c63e800a87bfdc51c6db7fe158a674f3c9b27","proof":null,"script":"73","sender_offset_public_key":"7e08f9e2c6c71033b13ab7ab4a899b1cd5978c53738552bbb96604bc3841526d","metadata_signature":{"ephemeral_commitment":"425ed55f845cb567581f0d7caaa496cd3259d50a2d045f85b1ff82f47ba21148","ephemeral_pubkey":"361c4d751ae46c42d60d8649be4f53d268411f3e34341d60bdc13c6ce4892d79","u_a":"53f81832e039190768761c523cd5ec2c697697d1ad24b72ddfcb146a149b9607","u_x":"fd8e665ea1d3d6258d170490114cb3ceb513ca2d5107c53a185755ecbed33a09","u_y":"9760ec67671fcfe5672158708e17952afd4b9f553395c1f54c601b1543c15f0c"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b6123cfb073438daed9f70e4656769f6c30d1d7621610db82d7b69ea57f42622","proof":null,"script":"73","sender_offset_public_key":"466dedd09c864db977e28824d67bb3a87418d2e1988c915c91a45d477ad74155","metadata_signature":{"ephemeral_commitment":"c24ceac1471ec3b77b4755b3a776e056181ff12933b529e9dfe5627577109219","ephemeral_pubkey":"26ad54f465a9959651dc7fe319547422f445f58d580922925e2de878d5aa7016","u_a":"505304453f5e704ae3f134c45afadaff08aef0e7e5bdf644c415fa7eb708c40a","u_x":"b1f71ad975204ae353ca03273b0a90c7073c2fca24b1dc9e019afa399d35f508","u_y":"52b248e3d1ccff206e2a8b8a0654e527f5cddf5869c35aa2bf90dcffa1ee6708"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"c0d1d731bc37493248fcc07243a630ce19702832e247c596806de5f30796040b","proof":null,"script":"73","sender_offset_public_key":"2a9210361414dc27f033da358b53dc680ac56db827fdb8fb9e982193109acd7b","metadata_signature":{"ephemeral_commitment":"b20919a672b65ebc90f581e3ea170fe77378e2b0489148407a199c8662adb86d","ephemeral_pubkey":"fc88a556822aa5162411783d2beaa667159f90ec299a3c8001d5d858f2e6c34a","u_a":"0f4761e03d8177267f14d1add9633e716a4882d36677084224a4a61fbb4b7f0e","u_x":"d7aebf3b965b0edeeb52e82f3a7a6cc774b5fb478a641b60e8c345010471cd01","u_y":"d294e7a3696e206a0ef8144bef5c3a15713ff4609b680d79da16cde995b9e00a"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"caf82d53173059b908abef746ef25845c57f7899704fa8fbdd37e63c73083c7c","proof":null,"script":"73","sender_offset_public_key":"f6bc97af50d9cbbfe3bbd294f5a41526f7274f45a3b2bd20b2ded5ad1144c210","metadata_signature":{"ephemeral_commitment":"36afba82bcaaf62f8b307792c5ade928b0337c2568905618b887ea88976a6d5e","ephemeral_pubkey":"bc0700aef440ad98d0463a46f5ee53cc8d54b163aa6fbeaa0154674dfbb9532d","u_a":"708abf91b28a89f9ead0c0d873284e4b7546f8c2519ed20c8a0d12edd2be2305","u_x":"43739503cb798daa9ba9321e7cf717dd6912a31dd3220b3389e02aa7167d3909","u_y":"2159ecd7de6e55ef590230d1f0b9e812c2472537adee08bdb5917be33b546002"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"e0104b531d410bd7ded90b5d1bd97cd5514a862996327a318761bf3e3143832b","proof":null,"script":"73","sender_offset_public_key":"be010fd79318f932160af9b2473664c20d63e3b00d29b15efabb8443322a8279","metadata_signature":{"ephemeral_commitment":"247d4f4d23aa881476ad76843e6c29c00ed46cf5eaead2f5a2517c1af0e6e50f","ephemeral_pubkey":"16be62121b92bd57187a93971b2ee82bbf88bd51f8cb78d6917c94f434913057","u_a":"519f3153e876a706fb85e367b45dae2173981a3dd557e54ffe6fdd21fb238008","u_x":"f82d36c63e226bf4460f5c18072a0e1786e7500de7d3005f439ddcf1bb82a609","u_y":"cfce57a645676c8c6cb431593eb3802039651a4bbb611980d6ec83f78042e007"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"0c4df21861976f097f4328ffca732a0aebbca65dfdc83ac35dd961f18a72ed2d","proof":null,"script":"73","sender_offset_public_key":"1c9bb4fcc078e080480a52e01e116bca36afbadd08d825bb8b83c932e2a37131","metadata_signature":{"ephemeral_commitment":"883e82eb15601462128645bf480ec579618bfb9e01289d0dca7bb9983a464e16","ephemeral_pubkey":"b220a39e41bbfeeb4e5645617dee8bcd1b075c4a216296d9a37da7a7038ebe45","u_a":"b6dda8194b1e4bf3536481f8a6119db3027f98e4098a73261a13ecd67329ee0e","u_x":"2c4f9e4fed9194048ab7e5b29a9712db7dd520f19fdeffe5943ca86d1431ed0a","u_y":"485633cfed580e9eaddbf9ef5b30ff7171b1c97842a8d47c12386db70e046901"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d2cdfddb637f5086c6f499572e7c7e9d8229106a8181dad9c0daf1498f0a8329","proof":null,"script":"73","sender_offset_public_key":"78a7dad3ad524e683dc217c2429dba4945ea49e6bb2aa20724f8c9a9c332c64c","metadata_signature":{"ephemeral_commitment":"f24d088376338bf7231de76a05a2ce8ef45ea1c9bd5622d75dde94f32339297a","ephemeral_pubkey":"887d5d0d249a1779cb48c43de954c35bf538c4873736cb588640e915e0fb1912","u_a":"2dfff7aca8c0471bdffd5d00adc2dc05da59e16aa583804da17421f0d94c0809","u_x":"83a72e77fa25079a78429939277274062837143a8a9101bd152a27f5e9442806","u_y":"0349717f08d1d1b7fb66247f801e6b1f9dafeb8080ae52fb7421fec0af741606"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d22afa0b3685f50dbd52608de6238329aea88d91289561623e1522af438e4803","proof":null,"script":"73","sender_offset_public_key":"400d90b6e11f9f66623907a81203b15d726c0e66f1f16146f1c9805fd7832442","metadata_signature":{"ephemeral_commitment":"8488a25f71c4e93b3e4eb187b613e2e46811af8264fd48422373877011e86129","ephemeral_pubkey":"e4797a949d6b654156b8530537f5e14d44dffc7c55ded0d75e76dec6d109857c","u_a":"bab77ffcd4113c27b0c3a34721e7637393afa44e98bbd12e205735ef04ddd904","u_x":"968f1ccb4c9d2df4afcb2ff777779d5fe40c66c6b5688cead174c5e49dfe550a","u_y":"c55e2c4975b18122faf4e96c3c6c407fd54b43129871e6f5472670af6cb01f00"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"4022e7ad4922930b9a0aa696c2114b201cdc42369dfebb66e74b45d3a248b114","proof":null,"script":"73","sender_offset_public_key":"7819345160569bf90cf35d6d7220591b25c8397e1dd5ab08acd7dce18fd4e179","metadata_signature":{"ephemeral_commitment":"4cc12c06c43ee6290e343b6aae6f385bb5d1331069802f91b10bfd997d5d3e64","ephemeral_pubkey":"228838436881f36d1ccbcd594ae1ba5cc712a2029fcf611bf3d04387965e3943","u_a":"1d5db33d9209e48603f67371bc0d079a594c9981a6110006986042da08d44103","u_x":"eddfb37a93965bae3eebfd4adb15b16b7d67429b4e03fdc02df23479d5d10304","u_y":"60f3a0fde38c286f7fbce84b8bca91a75e88468876f0e3091c9ddce07ecb210f"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d88b3f2655bab6bffd7bf05627883f57526f88cd941415a1318d16af0fa33514","proof":null,"script":"73","sender_offset_public_key":"06fb1b1e5f10574d2aa9902e05afc6b43b7f983fb244c4a81b4544715cc77420","metadata_signature":{"ephemeral_commitment":"505ee35ba5424096639aabdbe4db761af632e88a513eaf134d7c6c672b284729","ephemeral_pubkey":"964fbf0d561f4b8dec8c638dafc76067b376a42f0f04dd41a83f75ba3f767d68","u_a":"91b940cba0c9325102de0e155aa1de5f4986e8152996b72c649ca78b167c1009","u_x":"81dd6554311c7de2895fc284a33f4b2f3dfc3ee4ae849d6a535eba3bc9e0420b","u_y":"5aa8b301a0261cf5cc336cfb2ce69f62dcdfafd85d9abd064985e3c678121509"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"8021645ce8bdd6179d1991897091c03d8174e47faac2b6a3994d3e07e6953b7d","proof":null,"script":"73","sender_offset_public_key":"beb457ce4bf2147d5899a9c8d366ac95972ba367c0367a127465c7a591121440","metadata_signature":{"ephemeral_commitment":"dada15a0a8efec5bdf053afd5092ac95849b89f047baaf0ecfce13515bfd202b","ephemeral_pubkey":"72d7e36c54cba75dc932ce612e9e1b0ad266365b21201b46e0e3827fd6e0bc16","u_a":"155def5459fe777598357c53a2a737873fcacc2066c9b3e64b94e4edd6eeb102","u_x":"c84241828dc6bcefb16c64123ede203958e9e0090ab6007deb80163e62f3330a","u_y":"9d10da059dd5c0f6bcd304bd882f261e1a9006c6d0a6e07ea9ae6861ae209c0f"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b25a512d47658ab588d7cc1fd3ccfa84edd126ccc3c21d303efb8f3ab4ad3817","proof":null,"script":"73","sender_offset_public_key":"b4c32240fe1d6728dea9796865c51a7d47747491f6d689c0cf94ce6bf42dcf38","metadata_signature":{"ephemeral_commitment":"967a82a9e3d0a49390786b886fe0e92b3700744e084ea145a545a7ae7536e16b","ephemeral_pubkey":"32e7d3f6030c16b9ba00c588327edf01cf0e8f96b3821a857aeb5d270f3e5b77","u_a":"cdbde2b0522a49eea622dd1a92a28706503a49c894df1931574df9c4945d210e","u_x":"59436b6a415d502f1db874beac03d034fddaa277229e548e166ff165cdeb9a04","u_y":"6c401ff23d799345cf9fe77da1de7287c8d8f522f405fbbf5d2924b92b568808"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"eaa3af29ce32118e99ece54d5af2d3c1326a9aa7875fcfb9aec6519d1bc5e308","proof":null,"script":"73","sender_offset_public_key":"22a13a78bce3ee93ff58267769db77e43629e713d5c92106755ebf4437eaa528","metadata_signature":{"ephemeral_commitment":"c4cd410db9a8834cc7bb160f27be6c90ad07ab690beec4e9a45f12e5fbc2e80b","ephemeral_pubkey":"ee9363a93b8e1b0d98b9d4615ab7e779f6562a9711a9886a46df4b76f33d156d","u_a":"41caf77a67dc5cf3133d1584cae092bf9b3d3ba9b33f4bbdfa64430009508004","u_x":"defc7e10a477261cf6e11e401d0fb9aea3145771c97227f70eef2047ccebb800","u_y":"1d594d556a9d6ade13e03dcac4fc4a19b3342c69ba82f60fa0c7ee21cda15e07"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"1ab8984b68a867a1fa164dc7f51d8fb748084d7873c73c5ba4588a8932a5234f","proof":null,"script":"73","sender_offset_public_key":"50ec0290ca70bb28754a5278bcdb3abe62b5ae51ceb6b969d7443a50a9ce5734","metadata_signature":{"ephemeral_commitment":"0c30a66593d33ab689508db037cadd8d40513387dcdfbdea6a9a7ffc185d636d","ephemeral_pubkey":"267917dbc4045d021243918474d280e9323c8955a1060093e59a2c23b873211d","u_a":"89a3801f858a797d01c62f4aa7211d6e33e6f0dd3c3a4e0552f58d4886fd0d0f","u_x":"02bb2ed8843515d0a9a7852fd1d4a77e0c905639908bdc32bc9b5c323179d60d","u_y":"590e69fa814bb761509e4e10ef071da835ed7cdf10a313f559bdc4eefd1fe90e"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"14cd6c193396d51662887b0bb295c66d3126b52bb3146c484f774e03564a4176","proof":null,"script":"73","sender_offset_public_key":"6408579f9d0c1568196e714e9e23a59e36ab663e70bb3f079fca9412da2c6d6b","metadata_signature":{"ephemeral_commitment":"9eb0b796ccee7eea0b73dd00d8dd4f78452a9569b6fae95f1b6df564b88c6f57","ephemeral_pubkey":"445f86842341f9db3e927beec4a7cb21ed40081e6a162d1244a39e85b65bc35b","u_a":"c72848c768e3a5a4dd0fa6e66b137839c28323361877d767fd17061cab96810d","u_x":"e8f4fae3ebefff807148260155fbe0a882725d2ccf685187e7106e3d5aff4f0e","u_y":"c5a779c58dd4d21aa9e010bb42f7da547276e9003769ab8c0ed5fb01237a260d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"68ed34cf223384853048abc23ae64999fdca8f8f06f257bc9ddb1e79288aec04","proof":null,"script":"73","sender_offset_public_key":"aab5b5a76a50a167df1f33caaa2a6c741c913506560a0e42d0817770e028927a","metadata_signature":{"ephemeral_commitment":"1416cb194121f68783936e233e77132f82b13ef9c84c708328ea0a4fb6854200","ephemeral_pubkey":"82825ea8cad4717dd3b5fd78a5a7dcb1d6a39bd94014b0a963c2207c2fb6c354","u_a":"7633f9ecdadacb655f99781184cd73d225320c01358388ae57c6bc0c132eeb0d","u_x":"f7fc49643fef8bcc256fa29ebb9a5d2f44eee2c952ce4546672f4d97a345cd01","u_y":"e339e14483bba5acd291b97948fa88e742c00055897f9eb1b2a89ed09c0b590c"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"2c131eb89207b6a87693018ee9808ff1fe8792f78d814baffed723ef4bfdf94a","proof":null,"script":"73","sender_offset_public_key":"e623b17d3fbe14982d30d999eff6f87d8f3aa499c2bf39008ebc57617cf7dc74","metadata_signature":{"ephemeral_commitment":"cc620d191db2a4e525dfe6dbcc098bf6c2438883747ac6dda0c2e65162a4af23","ephemeral_pubkey":"64cc46258a6916943c3c94367847bb510368c2cd89f779b08eb2c2c864747e67","u_a":"e34df36ceeb36c24cc7d08ddbb6b354c67d6f2e65694842790386fed6f8e5206","u_x":"33fce28d0a506287b398ba27190e71430f86c389cbf21ddd36d15a4cf9836004","u_y":"8b3c4520602b455371d7689842a299417803f5a6b2bb01ca7e3854aa330af304"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"f467dfceee77657e75669a7ca62c74b7b4ef1aec4780f6131063fc6ef6bd3e47","proof":null,"script":"73","sender_offset_public_key":"540404e27d6293fa35561bb7dd08347403dfb608c8fbf77f02323e1f1246577c","metadata_signature":{"ephemeral_commitment":"9e46f34dd936d41c2b54af64d6ba0b2ab5accd318d2e0c7b9a0d885e56001164","ephemeral_pubkey":"048e8a200f6e5c6d6d02c0daefb3e5c5e7d43d85f8dcc2aed730618aadb23119","u_a":"c99844cc257b4d2a51134e7aa4de1832da96b4ff0aefea16c93c5fb200bd980a","u_x":"b1aecc2e4b2cf2076608628747da066c62b429d68fe20cb74afab044f1367e03","u_y":"48836c0665526db398eb5b56b713c5e7cd7370bb1c9941acd794065d08e8f405"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"1a523899ee9dcd2f8499a1595ca9f6a2a66751f0b3ee90dea1e9908272612202","proof":null,"script":"73","sender_offset_public_key":"aef69fb2dba61251b124a200b728c07acb5f07deef20ca67466f984d4f52e02d","metadata_signature":{"ephemeral_commitment":"72adf1ba8d5f9ac9356e11034caa182f50096299ada6977993a9aef5dfb6cc1c","ephemeral_pubkey":"76532910ec51047f1d4a32164e3fe62ad2797486ee36dc8e626fbeee0fa8fe37","u_a":"b10f2379e24624a4e0568376426bc2f1fa8182c326ad4f4792efe2c8c4634609","u_x":"31c6978ed9998a6363264b8921aade8cbee5870d1b0b892101bf0fb78e320307","u_y":"ae4732431d031e408dfbd28e8556bd4be1c54bf63cdfbc74f94819f2b9eeee0b"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"6ce8e9d43e6fd21968e26f9ad2b53312697a7ad323be49ef7e6bedf4d5068002","proof":null,"script":"73","sender_offset_public_key":"268d39d7ff7a10bfff439294e48ec85a2a966306e31bef414d716300e3f86218","metadata_signature":{"ephemeral_commitment":"267b2434262ce35d02a99c706c051379b789ce99a92b280e7d5e183d60279b6f","ephemeral_pubkey":"0a1572709b24622cb97154be4537cdcb25ee9000749d24d7d473eea882f4545e","u_a":"1f5c6cf865f8aa85c2dad20732d422bdf8eab3afb023eb4002e7c357242ca00d","u_x":"847cc60ffa1663cdfbb90c60adf9068fa1de117ba0d26d89b1f13e860c52390e","u_y":"b2ba4aed356088eab92058c2dba8154c52b38dacec8f8f7996b94c08e6b39a06"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"58187846055589c16c373dbfbc299a27940e44dba8f6bb09fc6700a5ff921518","proof":null,"script":"73","sender_offset_public_key":"141fc3700d8b0002af58a77fba1edc556d070fa9af9098819d821835ef94bd51","metadata_signature":{"ephemeral_commitment":"7cbb7293e179745bc83924d38594a45441e82d4d21c5ed804121ba153cd6640e","ephemeral_pubkey":"2c5563c2536e647f0f6a9be44fadc3b482343a0ed511124bb88fb26fc4bed863","u_a":"1fea9158937e58adea44da529c54bf813e8526bed50f9de859fb1685f449ee07","u_x":"f12d6bada081bb34390134f774c6f2e4ce72af82ef04aff15074fa1169c0c705","u_y":"2321e9b30b04f5d22da760d6b796d2d70b45e7f37560e5371273f0dc72364b09"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d4227b49fa8493d49348c8cb5997f3db1f887f3d4ee795a3b87211c1fd2f5e3e","proof":null,"script":"73","sender_offset_public_key":"0c9a09bd65f843c418df9191b5ef1c6a4ee0c3fa6fa45eb10d7007065c068d04","metadata_signature":{"ephemeral_commitment":"a05c6cd99713d87e2fa21aeb4532b04de9e5f75abed0a42a3da9211490756726","ephemeral_pubkey":"5e1226705955734cd040e0195bab6f02d01066f9e140371b99b67e37d0721d34","u_a":"cf93bcc663ea53ff37d0d6650151a0242d3a367008c1e6e20560d30a9cf4f201","u_x":"e0613b265a13bb5cc48f9888e416757614db3069d709c488f942ec863841420c","u_y":"9052211c55c4c8c6937de1ad91e0acbb5eb79e66b52c207e6cfe8b509be89603"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b810231e8e45d33af455e70182930c3c83d1b9510d777932e484332f6ba24e77","proof":null,"script":"73","sender_offset_public_key":"eafc8e61e05301a2d0afa58547b7a3490ca68a844b09eb09b5745bf9d2d0e54b","metadata_signature":{"ephemeral_commitment":"6405755bb6e5ec861c4278ec3b6e288fb924e823e12f2abbe47c1656ccbda916","ephemeral_pubkey":"4ca1164c6a231ad7b29c909a6e62048b37c96a1c56a056090a7785da16199b1b","u_a":"00760f20df35a3a5c5f8ff5a6967ffee99c04d339df79c58da1e16ca35592d0f","u_x":"320ebd0034346ab388effcd5e25fd183ebd500c3305735a56397e5a5d65c0f03","u_y":"dfa38b542f911c4fee983d69b5cd396de6104283983de50a4e610e64935d970a"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"1aec1e25304aabad2298cec8e17bea6f9e3e3152cbb2af259ce3a3db95bf2418","proof":null,"script":"73","sender_offset_public_key":"b60b030de0717b37a595f78cee8a802dcd6770b4a05e7a688690eb380509c361","metadata_signature":{"ephemeral_commitment":"046365b068f0c63666f14e79fb3806eec30c0a03d1d002c81ce1ef60f85c277e","ephemeral_pubkey":"765bb8f7ded46376f665f65f929606bbb99cd76505ff2d1497b4b073cddc6c6a","u_a":"241af8ed6a32a1c12b9fc9d0285fd041a0b3ca5f613d36d6e80f14a981dc7a00","u_x":"44fea1f9fc3cb20c72f013d1a5af88f25aeefba6c91ba6fed44da31905a5390b","u_y":"990e35816f263b445185b69c82dad7d8eff7337b1ab0a906a403312b6bc27606"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"2e567dd68b985f9fe6f56a6174665830aff892db72e7309a715400558cf7923d","proof":null,"script":"73","sender_offset_public_key":"6a4bc26fe8b1f3d65d251a4f27624dc0b55b5ab0eb0770498d87616b4b31972e","metadata_signature":{"ephemeral_commitment":"d49ffbf6bfed027865f800e70e7f3da7ee6fab09ef11974be4330eacbed81a12","ephemeral_pubkey":"1ca44843e2b558fdeff6008d404f05df91ad15a528e1c47ad8267430a329a040","u_a":"89e9446c5119cd71fbcb1d84bab942b1ed5b91e4cfb09aa74bc2f5d62d0ed608","u_x":"302123b1b4cf4bac4c1ab756de5711d9bef9550b4c4bd9fa67c8ed0c3ce38107","u_y":"90a07a2a4fe41ce61aad8e8d8f222dbbdab19cb126eb40963c512a187c3bd20e"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"86a25c6807864159a379f3fd03af2ef521d29055f80186e3506c644b4a9c0e67","proof":null,"script":"73","sender_offset_public_key":"8c09270385f94100153b1df1a438ca097e23803613ee2a568e6d4346615f7932","metadata_signature":{"ephemeral_commitment":"c2af6bf0f5713fdf95fb574b51ee40fad9904c98c754ffd0b3669f76a0e65a75","ephemeral_pubkey":"f2ba518658944e883fe63bfffd38ad90aa2c6ccc6e23d215f63c7ea18d91c178","u_a":"750dd5e4ae6a7daa100935cba677c8976cbe87370ee8a79538c47eaf672ed806","u_x":"fa3d6256a5434dd5bde1b274f402117d09396a8acb75407e8f25e541d3085a05","u_y":"7b4c098b16916ce43967997afad404e00bef6af51843deecc27ef553793df70e"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d6d4d5f4192e0eecc31a38f6d52dfa54b913201072dbebf7369b4b58122e3d6a","proof":null,"script":"73","sender_offset_public_key":"02d45c99a04dfb583f01903ff7d52d06a9d9ccc5dcf7385f0bb899b828f27f57","metadata_signature":{"ephemeral_commitment":"f4cc2e9e7c715054b1b7df46a362e81eda966e22d387837c2af1af7c1b83614b","ephemeral_pubkey":"283f3706f8b6d67916543fc51753c7b24dc1dc3379e337b666fc5d19e152ed43","u_a":"73e1651e0da1cba1b36d172fede4d00dba9afdc52c6bd1620259e3a3f98bd109","u_x":"169fbd92726be57a86cf18a7d24e8c73959c7ff1b59e88f5d06b409eb96d2100","u_y":"7d8e78d13a39ad5c2e277f98a3de1df311a7dde0427d835100976dec49ec290d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"4a84a6a3aefea68c0bd97f0780f3a51e77f26c519312f6cce9f06e12e280972a","proof":null,"script":"73","sender_offset_public_key":"5c25a1065453adb22a6ab5af587f9467c40ab2ac95159965ac4e89a80d192e75","metadata_signature":{"ephemeral_commitment":"0cdd53c9641ea1276fb9163b7a4436f6a34b0484229e5bda7f94bb8aea58f935","ephemeral_pubkey":"1616a8ce4f534f40eb1d45dae7af29b751411b71a38fe5ac3c1f4867877fde11","u_a":"26caeabc9d53929768cf9345d524aa225e16362856742a415f83bbcb006d6901","u_x":"14dc4b60aa6be78ce3317d00acd671ac8c04337c02a33845e9c55dfb9dfbce03","u_y":"11bbc76ce521bf86e610cc88e1828f434e96ff4b97b673c87f548436e7c5b20d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"807ec12b596e8b914a479015f4ef832174cabc89a8325ce083113473b079f84a","proof":null,"script":"73","sender_offset_public_key":"f6202e2e5e1a65152067482b319c85e676c6a01ab32ab3803344e32bcbe5030c","metadata_signature":{"ephemeral_commitment":"7ca1d0a768927853a68ed26ec354480da0373442ed0b0e32d54b4dd531035e40","ephemeral_pubkey":"faefa691ea88635324eed0e986e8dc88bc14d5427d06231fa83e70fcaba21c55","u_a":"247312c3fc37a4c1dd8257f5aa3a24b217d37f557dd682f4ca99b9ab92924e0d","u_x":"d2ead34656ae03d224f932c415f96b45d68937c980328633651fd3f6e2f4db04","u_y":"c556b480f4e8086e1e4e523c75d76885470c03863ea0a1660be3f644f1d3c906"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"e8ef859cf4d7bd437d37e6d7e6e321c39e44c105b4efb1025f5add3e6cb88517","proof":null,"script":"73","sender_offset_public_key":"e8fd0939afd79c8dd8c42419839dd82c2c38f29dd5ddca7844eca6acf3160a6c","metadata_signature":{"ephemeral_commitment":"d8cb46e4ebd3bd96592c5babcc12e2b730573dbd3619b6372b894ba3e58a0911","ephemeral_pubkey":"a6af3d2891f92bb34d35cd8568f29557a97c49f2ba8f6f45fdfa25e1c38b6b05","u_a":"dce99d18f86dde32e4ca853981066598907235f29c9831975e8e8693f4605f03","u_x":"cd0caa2a591969457abca5ea9c6672605b7ef3af8f8cf36dca4780993d03980f","u_y":"d15b0f07b8f2de3b84f240a4da3bfe29a03ef9fe605699d231a698774003c505"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"9230a072a737afc2dc919f02e6877e596c26b3377a015a9c19b31f02120b3554","proof":null,"script":"73","sender_offset_public_key":"203a080ac5cfedc7716fadb9ea9cef7952c754f003fbf7183b21b620cc1d610c","metadata_signature":{"ephemeral_commitment":"66b7bf4e185bb930d242db7a483c5714b0c2d485638f68e19a30f82ee75c986f","ephemeral_pubkey":"42a081dbaf4814e1d428e148ec89108b1bef1195eb1186b6a9234631f4a21065","u_a":"39bc693d61e2cdd2488372ef4c6061eb99e6191e964400661df9a39c6dc7bb07","u_x":"9358443115bfec9a835568953523fcd33f459c21095b5ccb76783f563e7da406","u_y":"cdbede0be366057cea081c2dff7144fef864f552dc456f594ea3f033fe17be09"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"042946fa8e43806b503f8ba8e7691e0635527f15be502509a5531399717b163d","proof":null,"script":"73","sender_offset_public_key":"80b04bfe7bfc465485ea4bc8e8cfe21e6f14d8fc41e5733362341ea02f0af73f","metadata_signature":{"ephemeral_commitment":"7c9ba5952e382679ccfe8c142a57580de0efe0fe5e0a85080a7cde3281a15658","ephemeral_pubkey":"0853412555d5aa3290a6945ddfaf2e6aff35da15ca3950c1bc92d25d40e1e108","u_a":"eec883018ae8b195b61d5a046549083b29d621c7635a17ed179cb6e9f8e1f604","u_x":"1b73ac3cf25a07161a2896f7181ef0d17516de0efe40323c259358a075f1250e","u_y":"fdce7455884b7a5a76167641f9e61d8937494250d773df7e681bf0986292d105"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d49074b24c2f557ba3d4e8807c77817e96a2ac6757dbeac6b02da06ab4e79a29","proof":null,"script":"73","sender_offset_public_key":"48c13c47b29c0f5946e1d3064366bdbd449c70b08c39c8315967175475a0673e","metadata_signature":{"ephemeral_commitment":"962a550439a4701f86acbb5004d5ee9c66bbc98e9240948ecc6679cc470b3e2e","ephemeral_pubkey":"cab937b0dc32312d4c47ec53deb0268d74b6534de5dcc4e2e52b3b575b8b5355","u_a":"5b129bd7cfe88aeee0ea2d69ea115a25d583ae3c3184c7217999c0dd7313990f","u_x":"606c0826b259bb7b3823e7310d883c08696daf8d9c9e5472ddd402d7805bf906","u_y":"1789ec29cfe407325087ca9afc3b64427d4812c25b2e76f605cc55e994407c0c"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"8ca6abc9ad40fa1d29b432395543b56aa7385429caf79404e5c647eaa14bb93d","proof":null,"script":"73","sender_offset_public_key":"603a8930b8e457c77a2910e22b9c0235a85c9f1f0a6f5be40036971a27a66258","metadata_signature":{"ephemeral_commitment":"7a86412e56336d6555b8c83dc7a3f98c547b76d593f9ebc3cf2a266931025052","ephemeral_pubkey":"de986af936b3597ac9d3f671bbc3867ce14988ac23e9a718a7d58ced93faed71","u_a":"4c655fa708059a9224104149cd893dff2f3e13e42ead01fa96753929892e8200","u_x":"139f3c0c598bc93df9a6b0c5649019886ff501570e71081dbe00c96635308d0c","u_y":"fc04b3736bd8bcd281a6b3b4849a922f62263917813b2d58f4ac03d5681d520b"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"46ec76a5ba95f7021aa401cf1984779cd896eba2796d4d9c5b38bfd71179a44f","proof":null,"script":"73","sender_offset_public_key":"86637f4634df7a997bddbd10750b34ed62e9b761d22832ff5279d96b34262835","metadata_signature":{"ephemeral_commitment":"b6516b93b7c8383d6df885324a584d8762827b813a7c85e8044d14ae3102af7d","ephemeral_pubkey":"e21b831e92e68661d433c10304a5470c0c47d42f09f659c1415213998cba2852","u_a":"8cdc77480df3db54afba6f7510bd8f77278a946e8a9c6c610cdaf3455901d80b","u_x":"581984dd85277bf62ed05d308ac29e8e28eb332deb6e415a6afd4f6a3659a408","u_y":"b831348ca1c334db2adbfa86b23de34d6a6aeb44c9d04d1084cc8e29f488310e"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"7095f04da427a0a6e8d0040f32e622651cadbf33c03f745b4bf15ca518af5c38","proof":null,"script":"73","sender_offset_public_key":"58b292b431fb9a16b674222ab575885441a4ae253bff0b5afacdf4026b89aa76","metadata_signature":{"ephemeral_commitment":"7a6111b24ea9b1dfd439d3b4eac7f6cef7f49fa1aecda7dc114e057e2e7ee877","ephemeral_pubkey":"5ccf83a3ab876d83ad562c87204195d753f9d47d094805847dd00abba9d10e0a","u_a":"b502ad89932d956f73e532e9b58e04b18e9e20bb595bcd1eb9c98ea803b98b09","u_x":"469ea840f5b78e7719c6466684a0ab90384af1e133d8a9262804175fd3e19b0d","u_y":"8420024fb574ee49ac6130488c9e80f1121594b945650dc85781201b67b0e70f"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"764f89a2a668256a534210536628bc324dcc5f9fe39fb78b3580cea5234f3e11","proof":null,"script":"73","sender_offset_public_key":"b4076063b025194c881b3cfc72723f5cb199cad7ea4611a2cc8c8e1f6cb4141f","metadata_signature":{"ephemeral_commitment":"7e011c647fb908a030e8fc9844e5ba0d159ecd67bbec2afe9c42067027929657","ephemeral_pubkey":"64e137466ca25041eddf7376f190b5420262bbc7b6af945aa907482b2473214d","u_a":"1431d331208766f3c77bd8d067f3262de4ac39e19898bf3a46ce10548b792206","u_x":"32e9dd5c454dfb09cd3fdeeb2628c65ccc9329865c57aebc2cf833b5ece2a70d","u_y":"5c0febbed16b7cb02b12f8e10913a03784e3b994a2eaa4cb908ea2e537d85100"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"ca6551bbd3a6b99634685f99c7a20b14c810ba67d7db95029b9649448ea41123","proof":null,"script":"73","sender_offset_public_key":"569d2b38b635bf7ef9f030a0786b8e80b3c53e427ac5bd150f13b7faf7e5fe5b","metadata_signature":{"ephemeral_commitment":"5a4c85f85f18de1e60fd793b0013538367a9667f682bba1a57ee63cdff30241c","ephemeral_pubkey":"f6b8ae3919bd9d7b6156ca1fd75011f451440d53c774308d7d0a9d19c84ea55c","u_a":"da27bde6323c77dfa1db9018799edc607fe2a901e4208e2475e1bb84fab89601","u_x":"9fc0141499630dfc001844cbd120b170ae86447cca142d1ccb6565bcc9b1df0c","u_y":"2b83467546a160e9f368c062f7ea8d3ad5c4f32059b97f93002390950e243a07"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"7890adb7c4fd4bb2207b92f85b0bb3eb0c0736727a06b457e75c684934cd6c00","proof":null,"script":"73","sender_offset_public_key":"14e3ae51e2412e653472f6b010aae7636454d962f310162da0aaa2ddf5f9bc60","metadata_signature":{"ephemeral_commitment":"38959703f1026d19851eee5974121f8bd05fd854ffa0aa97626e9c0901e1850d","ephemeral_pubkey":"186b3f1f6e157b60681f4ad7f6b7b8587ae5be95e0a059e353f5461d1061bd7e","u_a":"748814b769fdf1bd295e4c6b6b96c7263c62cdfab7c60dc95e7d31039c972704","u_x":"863ad8d37bfb6f113ab4d81641290787b32c373b3031b7ab26943a3a293f8a0e","u_y":"d5919bd119eb26ff40928ad0965f948dbed62b5e5a3d7219a0fc3ab2f50a4309"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"9c5a085846a4e4392ee6852abbe6ab6c0b8b21d9d1b930cdbcbed437b734e438","proof":null,"script":"73","sender_offset_public_key":"26dfedf7d4e277d84d5a46c554505328f2e530a2a1153d758d590eb00423e537","metadata_signature":{"ephemeral_commitment":"b82b482e9c0fbdfb9769518c87a3cb582d700908bd84d5a65e8a5489ad797805","ephemeral_pubkey":"86fa972f2d8a347126dabfad6a64722cdeb7c151f2aca8fe99c121065dcf985f","u_a":"b2b9195d241e81da66c8c650ae62b5dfa35d71f4039109da5234f6ec8713350e","u_x":"2c8b48e19f26d3e59077d08d2be81a1cfe078848833083dd9a0f2d6b8be6fa0e","u_y":"bf74ed3f817a715d8fa221dd6e909684e293c5c93cdcc437efdefd56cf8d7f0d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"2c47637f1fa09d2a262eef2158817b809402db567f725132418aeb5801a8542d","proof":null,"script":"73","sender_offset_public_key":"868153b6e49000c8ed8445be65835d33de09f82b2a29bc86e76a8855406a3e19","metadata_signature":{"ephemeral_commitment":"fe2f637344ab32ed1fc723e93847108968206d554a7193e661668f06ec6c9c40","ephemeral_pubkey":"86b14fab34e5c54937869ecd84f141b323a0770b239112ec2bc1503751fb4c69","u_a":"29d8b1bdbceda9b9858fcdd39296de99b61302658708c0755952daa3f6fa2f08","u_x":"2df0f37fc9642b46ae88f15943800b56fa9171c23a3567cfdf1da924ff634607","u_y":"5f749a40edefff2d8187a3134474bd2f886774fc29267f425e745daf5075ec03"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"3accbfc1348083cdd504e8480487cfb7b55ea1b86197286ddc99955d9bc44009","proof":null,"script":"73","sender_offset_public_key":"30bef5092128dadd919a5b372f56ea0439c35c85853dc347afe5524ff6f4237c","metadata_signature":{"ephemeral_commitment":"aace000ac5091c972f71513e28894a3f95e60dc352af680673a1dc05ea0d761a","ephemeral_pubkey":"5aa586fd631050eb0e0cbba29e9b7a4ebab267238368aa702f331a6324079915","u_a":"830ab6c5e71f558f077e743427ae8097babd8c6aec7afda826933670a21fdd07","u_x":"3014ce983f8a1beeb952fb26583e2446aefe9811ead05d57138b983f5e8dae02","u_y":"1fdee59024499c2e1009b5812fe0d051b41dfc4753bbb1a6159111b9deaa1406"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"2e9091256760f3d585d79650cb0aae5a3c160e24e3e82e60f33188b2e548e60c","proof":null,"script":"73","sender_offset_public_key":"48fde31bb7922e71b75aa55ed16435883fa7381c1d91d4f5e13f5bc858cea51e","metadata_signature":{"ephemeral_commitment":"be2660a3837934da641e6bcee219476091dcf6f48e1c933049854b0756d29f22","ephemeral_pubkey":"eee64f168e206b7954369ab3a153c53ebc266599fbbd6db847963278c7a3b400","u_a":"a4cd6a88ca94f483d7b70b11234f9fce2cd8c8610ff21f26a9eab2ed4d1c4500","u_x":"ab9f92bab83210e0ef4839a7d2dd579ac45e923cd81a26bce854886fc9330a0e","u_y":"5ce55e15d599e546d44608f41d7f42a3b9b9ff92ab43154cf291e25f8f889e0d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"6ed6a77fb088707c5b4bb6c8220c96370bf4c19e23f98d050595bc1586361506","proof":null,"script":"73","sender_offset_public_key":"c4aa90c28f1d90ad71441de4288fa73a05c36d83b8815777889868fbdda5c570","metadata_signature":{"ephemeral_commitment":"76bb7219a24e9282880676f2e24408458a2e0b83a42585e1996cad5658e2b631","ephemeral_pubkey":"7ce38c8a07f21f67ea9eb078c192fa85d064063ac2f126bf0f1a136046bf9d6b","u_a":"9ebbff97fa5c88736a54e74b3977b5bbadda50c307c689af6706adba5b74830d","u_x":"d98a0ac9e17876016edcfd098c0e14704feb303994f403e3083225220d87690a","u_y":"333d41fd881c9b4f34b109dcd6b1346a0cef78b2e49b654e85ddfd78f475700e"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"4ac0c26e84a638bed9dca54494d8463f7796f8e33cdc9a6cc67d6442b2a78e7c","proof":null,"script":"73","sender_offset_public_key":"62d2e4d8bbe8d88c35ba78a524c95a9cbf88063f92d421163b43b083bfb54b73","metadata_signature":{"ephemeral_commitment":"ba22004ce48941f5fdb8e337af077911a3f288ae0318027679f678869996a04e","ephemeral_pubkey":"4e7ec3b4010fc7c992c563c9e2244035ca1b0ceb57cf611d24b0259cb98c7f0a","u_a":"22de415d28cf6aabfc54455c688898886f4a8d5b85afaba82adbc65a82ed3a0d","u_x":"c031897011362f4b66f0f27230b3eedfefe6ff8dc73897df9e42b19fc031a702","u_y":"d72b162d1ae342a2f72569a1ebcf51c4adb5723001cf6334d5d2f62bc393e600"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"5ee36f47ae4bffa5e4989a51c218dc5f27f44d42a3fe736bf6dea41fea259845","proof":null,"script":"73","sender_offset_public_key":"04a7bc18faf5c34adec96aef8defbfd98cff6f59ce7205b1879a6cbc32c7190f","metadata_signature":{"ephemeral_commitment":"7af0bbf5d5080676437456ff5c0d6ca11fb9d415065163d0d3ebe2a34208c14e","ephemeral_pubkey":"f82d69ac1f0d806eaabd32a5637475c4a0d518fbf97e14fbc33e0e8a3ce0144a","u_a":"9cf0c2f34834743c12cdab443fcfebd09feac4dbd6c04c35b12c02ff0c070a03","u_x":"8878697c8ebc6c63a4eb54804779f7f17401b4eef7e931f45b6e1bd35cd8230e","u_y":"17d7d180edad15d1a2317cf3e35eb9ecc358ac52c8faef439cf057cf88e3f707"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"744637f99d4d06b6e33bd52dbc6e40e1431fa11c0f6a504724be4407df9ac370","proof":null,"script":"73","sender_offset_public_key":"3e21e56c62cdf9ceaf2b9a919da0aa0f67091af87d897a5ff5c474646c6eb74c","metadata_signature":{"ephemeral_commitment":"f4dc0be02e026b79acc3cbb524b7cd4444899aceee12e8c8c5f1b140e536cc59","ephemeral_pubkey":"061c657a87638fb3048fe7c970cdba8a49598e1e35f7a663e68ca5a9914c202c","u_a":"f69f8fb5cde5dd2b04bbed0c9797f972d457a7ccb5bd35ef7230ac1ad8da7401","u_x":"c06b943bd43d862e604bc83de89f3d956b9e012778bfdfe77b47d65c22873a03","u_y":"dd5f9ed752a76e042c3b2d89a5d4c8fbc5ef388e9241a24ee01fe9b2faa4b200"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"dee9100ddbb7b2b134e36684eef11b0410375e0b207f728c57e58f467cb7740f","proof":null,"script":"73","sender_offset_public_key":"6003512999ad3b38d8fb289b77b40a70ff32bfe5d55ef5073f2bd4dffb2bf327","metadata_signature":{"ephemeral_commitment":"eec5986d55137041e6778facb2edc37301ccf1e140e47ff2f4e5329aa7eb1303","ephemeral_pubkey":"765308ffc34e70ad4f3f47a56cc1b26dcb191596d62e8ed805e4853caca2f911","u_a":"1406775e310199e8c3a854d723fb4a2ad76a0fa3b5696a1353a0e40ab972f702","u_x":"66fd73b89637189411e5089bb73ea10a476664a5786471a5d1cbccc5af070d07","u_y":"4e2827e08fd690210999104f9f0d5aff44b969ccf934ecff2f7b662bca50710b"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"8c47db7dca6822a6ce69d6127843f6f7a8a97b28704ca7de4e2f33d30145b41b","proof":null,"script":"73","sender_offset_public_key":"d40aa0ec423e0c2ec4da3c600cecf7a938beac220b2d017099d46f3b783a2e37","metadata_signature":{"ephemeral_commitment":"58f26d836257971721c472dad3024a03c45b09f77023b831b1d153c07a66b01f","ephemeral_pubkey":"bc1efc1ce52ea3df7b66776144545c1b93704fc748250684ef443933d78c5b7c","u_a":"b30719c85db1f524c3dbf1aae943059176c09c13d912f30f91417d79ddf36e0e","u_x":"16473271c89c42d101223fc68960fed1286422e3347d381cfad29b6f65b4e806","u_y":"5276e3f78c2ea73f965f474d5c408bd681997de38ccc5f7fe5501c02310ea608"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"cc928849981a2b7e0370f0d9f92eced439a33f7b28c9ee7133a1b41a88fceb7d","proof":null,"script":"73","sender_offset_public_key":"de9ee9561689fffcf736fc18c452b570aae5eb76bc18b597dd14e4c29bec2e14","metadata_signature":{"ephemeral_commitment":"1a8f0146aeeb7cab789a798f6dc9690a98b9a1cf97b9f6f74a9aadfb48329c7c","ephemeral_pubkey":"3cee84df9b7da4518e3bdc7c67aaadc6a61b04b4c78150a84613c8accfca0c09","u_a":"0d5f1d828def652e3599f429c8a478b973016e7f6640698264c3db8559bde504","u_x":"65cdcdc73ee474734b8a72ab01da49b62865fda127f6af4fb283d92e4c5de105","u_y":"f086977f8c635311dbd732b82e20d02a4a0ec3a9bc39bf128ace6530a5e00c0a"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"f8029d9b90cfeeb719874855cd45a6527ae8e0f17e729974b182c6dbb7609e30","proof":null,"script":"73","sender_offset_public_key":"d0d2274ca727673c5384588ad64a890d0599d4730ff63689ffca15c7298fd053","metadata_signature":{"ephemeral_commitment":"84c663ba1044cf628f35018e74e8dfb98fe36c5a162b063bcfa423aa69c78f4c","ephemeral_pubkey":"96862b0f6e52889d52766c2e675e4ab129df39efdcd20c4bcd6026db057f6b50","u_a":"a5bf9e05a633bc29ee15f16cf9e89cb891dbe00e09af210848f413b26d60ff03","u_x":"09028918bc8adbde811afb08dec3cf57b519c98e4b64fb011df7f263ec5b9806","u_y":"5294cfe7f44a583edc1d56a142e4ac03e015227de19cab801295161243e9f00b"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"120852b7ea38f4318dcfc3d96caed60da01abdfcc8ee7dbc2c9eb6a903184f74","proof":null,"script":"73","sender_offset_public_key":"d84df363e2c52fbcc1fdecad859dc999bf0aac2161db8b26df814160c011af02","metadata_signature":{"ephemeral_commitment":"fa11b4ba883e18c145ddee2465cbdc652756919410f9745933e9cfb19a7f766e","ephemeral_pubkey":"c20c4cac45f9e337149c806239c7924e2a95237cd487d2b3a01a05ccfcee9769","u_a":"36050d3bd94f4133257608dd29ae2bc156f4b407213e4b87aea60863bdf8330e","u_x":"e81058775b3337ae8bb40829d69bbc5206c51e7f522d8d215c0e054449d12500","u_y":"aed9f1aa39710b91ba8b64f474ed662ab53313d333d8c447ad934d1271e3dd04"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"e64c92e13fe30f6d679de6232bef1149a592ed33448a5cf91959f80de9912656","proof":null,"script":"73","sender_offset_public_key":"08f7523c717c9cfa5b64f946cca8579b55ad757441b33d44ab75cdedd4ea7a07","metadata_signature":{"ephemeral_commitment":"7c32d639b0748b4662947285a50302021acf66d8d95b3a2dbfd8503340612e14","ephemeral_pubkey":"92ec523f208c2f4fa0ef70d7cd0210236cd817d1dbf7278e20947075eb1dde22","u_a":"9c91c7a809eaa01c679232866b426fafc5661592c2f0e43ef7d7561e80f8cc0b","u_x":"7591d0025cfd852a9eaea311dfe3e294b609fc7a4b1256558ff95951558fde07","u_y":"30df1f0e9a1910eb2306ef61a91ee5f77f39dbd5862183c4281c6c29f5ffed04"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"426a6dfbf4ef573e68634aef5bbb73c4f3d23d5b09a3008064b0ccf53e2bfb65","proof":null,"script":"73","sender_offset_public_key":"38977efb9201ba5b02d1e1a9d6847c8eedc3a9798d702711885cc2109957c11a","metadata_signature":{"ephemeral_commitment":"72604bb4dbff2ba0381febbe13671b7abdfc9c8757735e04c961650e211f9c1a","ephemeral_pubkey":"ca280d085b4830ff17874611d227825c16e17405cf247ee28191b5b5e3ac6244","u_a":"8bd20c7f44c68cb7db348b5c57bdd50a31443e0fc88da8869b13712919631307","u_x":"4996110c68af7433e611985a16cfa1e3d407d5e85e63174872fc61d38598480b","u_y":"1a88d51732d5a1d70dc21e837693a0a5578f79b19fc8a3fea88ee4b7e9743a04"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"a84143ada6e293e414f8261e28af9c137e72994506bb3c50dc90eb92b03d6370","proof":null,"script":"73","sender_offset_public_key":"2eb7b7170b0654efe5cc0f8aad4e9b9dccaac376fdd0e5d46a82d9ae45a38a78","metadata_signature":{"ephemeral_commitment":"12b00e84fcab78a1a0bc5bd34c822aa20cf0cee13dc1eedee1973a0558eae463","ephemeral_pubkey":"ae36897c252129520e98621d690b79bd9af13237fdc8cd78c16b5282040d8f5e","u_a":"56b7766b1a263ae307a9e126f37980597a7896e42d449b429ac05ca63cf11508","u_x":"977b62b8f698872533f62aab9f05af006816ffe3b85770d13dfaa7e14a7f4607","u_y":"bb7c66f30d8e3d2bed303263476807409e7728420a46245ad5c3cb435d86570a"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"7416fed83833c4e88e2d5521a21887f1ffdc19a8dba8fe2186b42b71a70b682a","proof":null,"script":"73","sender_offset_public_key":"e82426bf0b8cbb99622e81aeb6e423555753ca2c10d2371f67d183ad991e4600","metadata_signature":{"ephemeral_commitment":"ea71db62799449f2cad19e93374deb1c6d844f63977edcc2b4b5aef261be0e05","ephemeral_pubkey":"e8721020a543ab036e0c688c0885f5794a6d2593c922db573d18f0a7fe01de12","u_a":"cf1729306463689e57d140a50d1cf40751efc407323ef7d812ed896cd4c38702","u_x":"b6428503948131967d89ebfaa6c725e90252343b5a4c6ac4a308ee2f96ee1109","u_y":"41c07f66d4439a83bef4dd2745b755668cf22c54b429ba75d32e105baeca900c"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"2489268a1da6a3fdac38cb2b02821b1b404778bd70536a9b30ae64e6ce243c51","proof":null,"script":"73","sender_offset_public_key":"f6324b7952b8b76fa2b3a67c3c7e328a9de904b99312e76242401bf6b7a76535","metadata_signature":{"ephemeral_commitment":"c8078b304c409b8799eaaf5b394c70b8cb7b0e150d80159974c9bb43623d8860","ephemeral_pubkey":"9837344b378563b286f5a4c89aa8f668ff13d7115ebcf768285027aa4b666a46","u_a":"9e13790638792d3382608b9de2fb471077bd007eb92544d7804eae70422c5307","u_x":"bb1ad927abe571cc9f6774f000b87a243bccec06d38d9fedffee6b424beda107","u_y":"873e51868d3455ed4a2c44e7db38c3fe195c4d17b81d73761bf263d3d061840a"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"98f50d666dda4f9633a98d9a879ada68c3e33ae0a985d9bdc92ea5a609ad3976","proof":null,"script":"73","sender_offset_public_key":"1ebc0ef40ea3f2643880a0f244afa483fb7bd56f3d0b7d867525bdbd4c719962","metadata_signature":{"ephemeral_commitment":"4e90da54c6ec39f480d0b28569a2b5b8a847a62a48fdd3d7582ca6de5ae5ef07","ephemeral_pubkey":"b2ce5b40878c28402c9bfca53dba462b1b9d6e961d701f3f331db795bb4e8502","u_a":"17167791e6ee997a1d364810b94b40b1f60a8a859ac82d907ab48953fe53950a","u_x":"868e6e63821ea47f17faf085255d9a7be8450c2bda2629083a507cfdb18da40a","u_y":"404554eafc391af6c9aa8984408e6fe58fc6f05b2b897b731656956c9c67a304"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"9ad2b8bf547f3ce880540a41f32a0ea1f8d91206c181f2c33089b432c614041c","proof":null,"script":"73","sender_offset_public_key":"ea6e4232e2b9af4079ae5a72df0c85edc7fc96801a2c435dc3839f7a5501c26f","metadata_signature":{"ephemeral_commitment":"e6ab6895cb58194bb9f1668964a1fe7dbdd1172e6ee54270909daa8d0af90b76","ephemeral_pubkey":"1e514ec37e8f63edafd92ca3822a3ff6fa01afcc6d622c1b856ee4c94ad5ee37","u_a":"bbb7acff373656de8676f44645d137b9d5a3e6d2574349b837795fee6f8ea607","u_x":"5675391e1e373635917c677199679e406ee92040929fa789757de7d1df68f60b","u_y":"9f95b56344c9f7c6ef5fcc416ab01758591309e0fc47020158c2c1cbad2cb104"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"28f5fa6aa0a4fa02589292a6e0fa895acd7db48d6cef3aa660272ade69dc1714","proof":null,"script":"73","sender_offset_public_key":"5a7fb7f87b3368ee2dfea5e51437a029c04adb24e2a661c15a0b573353bbd938","metadata_signature":{"ephemeral_commitment":"20ddb70e89b138bb6da99b15f07615f8b46ee79bfc308b2091b3be773d2b682d","ephemeral_pubkey":"48d74dd46a911e28b057b24afcd32847b21dfecc20d71a5943b1cada68c89048","u_a":"00ca0dd5cace8d2e45d9a24f0052646559be85407449fa0441aa1ad4fc991109","u_x":"009a216b858a7b468467d684e418e9ad99fae0871283176d605897f8a589070d","u_y":"48016f02951c8e127071c66e267e2ae805c1b0cc8bf209b5d1c100e8e7135a02"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b65bc624866ce3bbb38f64e37d9c059f73e1c80b84ff385d4c08691f83ec341e","proof":null,"script":"73","sender_offset_public_key":"f2c6c537737ae6d11e45faf8ed701905db4bbc7c35bbc85f9105b49d0ea27a00","metadata_signature":{"ephemeral_commitment":"3e5822fa002b3f6be9d5e7a6a582367e7cdb9f172e9747fc237df9b0bc115c57","ephemeral_pubkey":"240dad13169a173eab2cf60518ab5d7f3c99b379b9799867aa4809f5ca6d2677","u_a":"5dc2b96188323045eafcfb455bac3f1ca0a3d49f66e1156cbf8241a7b75d4809","u_x":"c27c704c5d144ef0c1833feca32b4229a528bc2c904a2c60e48e98e06187060e","u_y":"063c7040bf001297fc8552b8122e7c109844112bf658c434766cf40472b99506"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"4a4d3b6a8bb491a6fe075db602c6dd1d23458397674a4bbe1d838d349d01890c","proof":null,"script":"73","sender_offset_public_key":"5c902706846562cdab8364097a036f1078bc8ce55877e867cd570d00df0ca11a","metadata_signature":{"ephemeral_commitment":"5ad7ae2dcee3166b2540770d7f4ce89c7c35f187e8edde24e1e5118ce0acfd4b","ephemeral_pubkey":"cef2551c86fa4811ac7bcd355d293fddb759b79e1e5c0e19ce246719ec4b3019","u_a":"9bb4c250d84205f71dd0dbb847e85ae425d1fc6185a5caeac9cb0afd8bc28909","u_x":"62cc82da86debfd15e45d18670be4b76a32ce3d8d26adf39c5d3cf9849e9f70b","u_y":"e73998058adbc1e8dbd1f03ce0da2e73eb3ccd86a2d0ed3acba560d2f5278a05"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"10044b0a4a71034cab19a23e11a093f4dd43c763864c024d101b2b950cf07675","proof":null,"script":"73","sender_offset_public_key":"7096cc4b9ab59e877ddd1c4c36613f4103c9e6c6b536601880827f509c010e6d","metadata_signature":{"ephemeral_commitment":"e4b62c653fef25d7683a9c4c03e63e699566148eab89a3a50c4dc1e2d38ab375","ephemeral_pubkey":"76c50ab2acf4da1321640cf9f932c8817dc9b3f2b1d623167ab13132b80f8a3d","u_a":"e65e7b2b9af16a21f2ea6fdb31e703e6a8725843abd06980b3a0966833c69403","u_x":"48acf25efa312b5869e959fc53be6986b9930acc62a95d4056798d1980000e07","u_y":"125988bcde47ea3145854bc993ffd01600afff3104a5806c759cf76259b67604"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"f865d37671e2ae7f590d558e0577ffd326d9fc2c12fcfef15820804499932d08","proof":null,"script":"73","sender_offset_public_key":"c466151209328422d4bef9f4ad6dee1e3292baa6ba96bc1380635faf19d9df45","metadata_signature":{"ephemeral_commitment":"761b39e610141b42774b37293dfdf60982e00083aa725f1ece5f3a29949f9e16","ephemeral_pubkey":"a05c2e33fb8a5411c1cb697e371ab2a53753e44d5531b873c7285a8c449f4235","u_a":"d05ff5b4d5a07efe95d14ac198eab9f5af38574f4b4a07dca680f48881fe820b","u_x":"8b7dd883f03a4e7b6ee050226942e5726b8a8c01f878e9ac474e78caa3a02a0c","u_y":"7ffee5f3797557581eaa26d155e3f6a8108c86afb10c46c2123a71ff58d64302"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"143de0f84e10f9295570999fabd5656ccf789a3c2548b0d877febb5b241a0929","proof":null,"script":"73","sender_offset_public_key":"569375469d792a8512edaf4c38cc82acf15610060b57261a627c900bf75b4c30","metadata_signature":{"ephemeral_commitment":"a2a1b46e9e4df2861990dd34d055370a4670cbd2d020b7a16d2ba35a9d2f246e","ephemeral_pubkey":"5c0f2117e1a4b89c37163e4e5fc6d75340b8dd37b9064f08a7ad65a687fca02f","u_a":"654d3f352c1561cae579b68ce294bc15595d3de5123e02e2d42224dfe94ea80f","u_x":"73832267064d226b93582c865008354b356da53e3b002cb6691cee7342742e08","u_y":"7d5811c02b7af6c8952d9333db818d2b38c23d281b7e897e9a8115d308820606"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d6fb5c8008aa02252e1630fff9f17e3767992078edb1320b91cc0ad1e0542f63","proof":null,"script":"73","sender_offset_public_key":"48357f2f082a65e5571565cf868a70bb4a61f123977dd04660493be72b3a8f6d","metadata_signature":{"ephemeral_commitment":"0e55b010b02ddf61a3868ab15c1cd6de2b27ca72679ea330aae208f211a5893d","ephemeral_pubkey":"94a49527d995527dfba63bbe2e3d6def7dc0a660163e101ea4628b33a66a5546","u_a":"7036570a61cde3e722485c8bd882c12eda698b8895e9065be92ab426a066ce00","u_x":"f9fe7ff03aad14849399b0a45d4971761c8c7c8dc95afe5920f5f0646951f405","u_y":"f61da010dd806b2e5817127d62fbb162f0c823b8aaaa66226675891d962e6406"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"3e56f7acf5289b74de81375aafb8ed99aa32e8b12eb073ac6328e21f5d9ddf39","proof":null,"script":"73","sender_offset_public_key":"f2a0b0fd68421d15d1437821a8cd06f3ed79f96952ac65b44d3fa389b661ce35","metadata_signature":{"ephemeral_commitment":"805b91b0a25c9db9326562fa8c751e1150f9ff4a7d23f5311c9b8fab85ef045f","ephemeral_pubkey":"c0592f8c3b4856938c0fdbde227bbea18362002fdf60364ed6bc3aa3ab93d724","u_a":"3bb6eecdb53d039fadb022adb1986f8dbe4be086d97590900cd3765ea8e1270b","u_x":"a64ca9b5e6f09dae4ce6becab83fb4f82cf8d2dba1987c1df3bddbec4821570a","u_y":"7502184f3d342326e4bff381a610eb734fb81c5fb1d26707eba29b82c843d709"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"7ae6737a7d354dfa7a2a477403167997c98bb0d37d427d466d990005a5a4b01e","proof":null,"script":"73","sender_offset_public_key":"6403924ac53ae9e051e715b00380044e4ae8b0ab65047fca9700e6618b4ee474","metadata_signature":{"ephemeral_commitment":"3a9e5a863127546ea12289dd01dd63e1a5dd874d20269701215f5ec176f6dc18","ephemeral_pubkey":"365de1c0215ac48a8e4720849bff66374022edc59836a11dc757be841302fb5a","u_a":"3263335c11aa85ae0a05ae5dadd19ceb8075eb17dbbb1e98943261510713840d","u_x":"14c6942c9302793c89ad5e4be8edd25962b25dcfbf9f6dc3d52bceb5d52ea307","u_y":"40f6f707860ae95bbee1de2eb7a0778d073e78f57d39b2d873e42816e40ee001"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"146bb692c32a7531fcae698b40c4fd976e5cffcb56dd1d749bb761575213bb08","proof":null,"script":"73","sender_offset_public_key":"289c234e6a3040213e32c1a2bb042000d781b7eccc465039e3c7b65cadfab37c","metadata_signature":{"ephemeral_commitment":"78f8a474252d408731cfbbd50aba0ebcea6efa441243549aed193aefec54e73a","ephemeral_pubkey":"1643b62945e4c7836ba6ac7798e89ed77322eea3eedc76fe9caab6eaa0f70672","u_a":"f5628ee087dea89e8797673b1d4e7968998eb3f8f04aab389bed8204ce640e0b","u_x":"48255019f5222a35483503f8448b3bb0118c8b4906cbbdcbed7ed400053acd01","u_y":"9034d296d8fc9393f0c1d4c8560683207f16ba5b3a72fbdd1bde93eaeb249200"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"82eba2d715442e1c5fdb64ce5795c9efe916e62a70df88cf1eba07cfe9a9a41c","proof":null,"script":"73","sender_offset_public_key":"54142bd9ee08bd2adc11b94706d3c192e48fd09a92aa9f379e7f33922138a474","metadata_signature":{"ephemeral_commitment":"22e0a359109b11907faed4c28c5e68782fbc59736a562f9c103f9f3f54658231","ephemeral_pubkey":"a244c653eaeb1e5841b29dace9d702ea75d10a82a4f3fd064071f0d523964e04","u_a":"38023de97d1049375e8932fe3c8a795cf672c0e9c6fe65c6951f7f622628d001","u_x":"9bf9f5777fe772a3378aaa818e5ff7f205b4d4021d4ee9ecfe98c14ee171dd0f","u_y":"313f675dedc1142d14b6dadedd55a8a12aedbf5225e0281786aaff108592260a"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"22e3fa91493fd51c165af4186696ad520275fe5b0c0de8704f4e369210f89949","proof":null,"script":"73","sender_offset_public_key":"02506edb73f17a8f3f9a6805b5acf3ac7bbe6546607b571e39b2cfd1f3768f41","metadata_signature":{"ephemeral_commitment":"48454bd5648eb0139563868ae84cbddef24a8e59cfad282f73c433580840635f","ephemeral_pubkey":"3cc9f5f9ccedb8391dc00c82304573908298b136ff03f910a723ad98ee68bf21","u_a":"474a4c1429df5139d85e86b019989be707c3bdd4a32e78dbffbc9433e670b603","u_x":"2dfc72fd3581e4bc4cd097c18760b59c947400ce47916dfc601b9d5848a99802","u_y":"9ecb5881134afcdcc64f0329307c4d8d329fe95ff6e502289a085c228e2f2f05"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"985a4abfd7f4f74f10dab5b514478e2a292aafe031eafa9ae219dbb1c2dc1e2a","proof":null,"script":"73","sender_offset_public_key":"d4cbfde47c6cf5d2960f46c1547c84bab1a23bccebd771cef0da51683f36ef37","metadata_signature":{"ephemeral_commitment":"fccc7b0fa2b8628e8ac82a502cfeea32a7b5fd4b67db9da7b0cb957f996f632a","ephemeral_pubkey":"9c0a84a04c0fddef831c8d951071b826fab69107b28fbd76f0abeb09a20e3944","u_a":"3cf2832b479072ee2f3b2235ff88d12dfc329ed009e64111a7633db2bf3e8306","u_x":"792d37c4790bcd7de3c75785650fa8c3535cbc3e6e555b2a7c12969c01b2ad00","u_y":"766d8f65787f8ef88198d82ea2d369a61eed22bde927ad332ceb4b63cfc67801"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"1aab9580daa49c51bad394ca4331a87a8eb45d25eb6186d20fcdfccd406e1924","proof":null,"script":"73","sender_offset_public_key":"d25b20284847ca8d2287c9ceb96bfc440d30f2121fcfae8aa2cac9b9bd14b56a","metadata_signature":{"ephemeral_commitment":"826e12a4aba6fd8a375c1f8480ecf093a6cbea2882a176391325b31c0e2ad224","ephemeral_pubkey":"0c570725fba4a177f8603ec4f1fc3f3eeb5b23bda012688fcd2efe5b3aed4318","u_a":"45efcc326aad6f5794cbeff59ca1113bebd0e2ccb9f8adc1b3f7d4e7f65a9d04","u_x":"a7e8fdddbbb23913795acb084c4e7a7b99e2e27cbb7a6bdb8f9d2313ed052e0d","u_y":"9d1ca6546f9d24caca56883eccbdc694169ad4a0a822d831ffa81193b9693409"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"80a083395bee20284cc1b7e2a6b268fc4d97394373c19038a37888a17da36644","proof":null,"script":"73","sender_offset_public_key":"782cb4a2671166c6abe056ce035101481c44ebf0f8238e5a3a1b4530d8e8f14b","metadata_signature":{"ephemeral_commitment":"b8fc65fc274ab195e51e0c9ff301e17091977f1b22b849585449be0a23966b4e","ephemeral_pubkey":"30287671ca971cb1e40d1987406297929fab458393a3eecb3b45875a13a84361","u_a":"288eab2b25b7e185f9f08e6f034c5024020d79733120c0a96aaf9caa49b6cb0f","u_x":"af0c82e9eade2cf700cbde7aeffb8dfe380bb4c2bd669e5f4a254ea5d007f102","u_y":"31c7bc29c3170a98570211731a6dd71af823518814c9e70cdeeac55073babb06"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"60eaf70f2abd09715d161be06312153edcfee28d4eb5dda71e328ee988222c20","proof":null,"script":"73","sender_offset_public_key":"a8b8b5a0b973e42dfc0814a84e840fda0c87b730f6ea7ac673c70d9e099e9c22","metadata_signature":{"ephemeral_commitment":"b00407b8b04812f501356ece9f8afea87abb2cbc48336594054adc2639ba855f","ephemeral_pubkey":"3a20968891e99928e127a6c7be8eb3d71b516725cae4470ecfca54c51f435d62","u_a":"93071bf695983854de71064de02771be477261f89121d247ae621d739d917902","u_x":"c4c377b985b443f67ce06e27a2720cf3e59e209abc591f3c9433a32e7433f80c","u_y":"04e4b154b2cd2095d111e0d252968010977d1c1ffd06daa2a20e4ea55114620e"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"ae1b7ef051bab3daf01dc80cf3e2e89a697fbb81bf97e10724f32f9dd032b510","proof":null,"script":"73","sender_offset_public_key":"34063a92ca9c2fc99e98dbcecacc47a92b46f1ccd1071c120d32fe1b44acc92c","metadata_signature":{"ephemeral_commitment":"14bde520e450cbf985034de2a87076d0375258a735ca9eb861f02513b6d4f911","ephemeral_pubkey":"dce51048907b6e18662ed910ae1b2aae9f252c7b2c49f19e8d73094a23347d2c","u_a":"f7feb0334a19a7a1f10c431b84ab2bab95f9c2374a9eb67fed918dcbf8ade709","u_x":"e720d933d84860b7fe9f35d593302eda7c23df3fb3e74cfba3d02dcb82b67b07","u_y":"30ef0d12e274b04347713de6fb5b8196abc9ecf49c5b062d398474014ba3840c"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"128bee29ebcd84db484fed4a36dfa828cf37237bc4da5debb590b238a1bdaa35","proof":null,"script":"73","sender_offset_public_key":"d8bc32e07b23960609c144a53ac8956461d33405214f09a3d9f4eb9512cbac48","metadata_signature":{"ephemeral_commitment":"26b7451dce662badfe5074d1a4f85271c10aece59ebfa362ae4708209275a25a","ephemeral_pubkey":"7e46b077af3c10e395b68c10f400044cf2e5019672ec0061688d0c23e0e1f45a","u_a":"9e4b5a70d37137e8f0e5d9cf10382830e6102b68229e26a6104ef0424b27e909","u_x":"a08edbfa3189bbef739906c67a447cbd8a444446064abdb604d7f7228bd72c05","u_y":"b296485154bd64d13e82501b395d9b4644b39ccf04dbb51ad731c9e1edb20c04"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"9e22740ba770067165f9b4a14fe66d5823febe3da08e4b7154acdf41c242fe44","proof":null,"script":"73","sender_offset_public_key":"30ffd85d9c95c0c349f234295ae04ee648a464bdd0883340e61c1a4bf78bd57b","metadata_signature":{"ephemeral_commitment":"947cdebe6ae00d853e20f8eb57ae9ca72f7892851581da92d6c83c87e0120803","ephemeral_pubkey":"d4ed85f7cc718cad89d8d47b7ffb2dda4a65b5105e66e783b4186e67cabb8e50","u_a":"f7a83ac7985e0ddf67507ae18be5419b4d51358a74320be9e2e7be8602cab70c","u_x":"7bddfd5a7f61af57a80e5c4d21966ae8a61ffe368af2122df13dc8fce1934e06","u_y":"14b747b255e36d84d25d5ab4f5c7512e002f85d9fe2c9942f0fc772cd74ef108"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d0d6196254c4072c80964e1bc1b4da40e4489c2439ba1bdef4f30627fe200808","proof":null,"script":"73","sender_offset_public_key":"02ba2d054c6578304b849375bab15a4684ad0dd49319f21151ef3adfabc04a59","metadata_signature":{"ephemeral_commitment":"4e0ff41f5e64ca19dcf29a4f999dd05db01cbaf8308ca3a40a6bc229d37b4119","ephemeral_pubkey":"6452120da319bc3babb8385c2641a251a4815f6d99451b868c310184a47e6522","u_a":"2e0883a2e8f99ca9cd494c7a87d9c28461816e3854e045d67919a7c9db749f0b","u_x":"7eddb0f53344ec113ddcbb70ea01833982b72d88d81d285176d8f4e14b9e2208","u_y":"8d300cc8a4ae7e37452c69a5b8b114da4ae2c75edb5d314e93f7839bb3c29501"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"54177cca628a8550d51eeaa80693a1a7e10a5f9a0055813c1b5849291127ac0b","proof":null,"script":"73","sender_offset_public_key":"661f9aa392d52b48c9677c674bcbc3439a7e868ebd58180daa3a71f69101312b","metadata_signature":{"ephemeral_commitment":"b6c58fe984cad2152aeffc8e03f51b448b2d5737ec1cbf3725f455da27efb500","ephemeral_pubkey":"f04897d5b36e5c079bf4ac26500b0116392d9194034358fae34a4337f12c5473","u_a":"769349c284da7aa807e9d7f0ea8882181697591a14feb696ff963f00de8bc106","u_x":"87aa18d4accbcc482658df068a4a5b16a1bf04977edabb43055a557b5fa71209","u_y":"7e79d47934cfcfd0f220c8e6fde2d33fe1f25899352f266bf910691e1c496a0b"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"360940d26c84d511281228b82ec89a6b61d8ed4dce03a1756e3d619703e5bf78","proof":null,"script":"73","sender_offset_public_key":"22e2c239ffd1d1432033fd0c53a1572e14942931e8d26e0b06f04fcd2d134077","metadata_signature":{"ephemeral_commitment":"366e0bdea4896ee6a55d40ee27b5e0ccfd3bdcf2d3e89b4e465073ddedd69364","ephemeral_pubkey":"c2b328857bea9008bf96dd8f4ed09a7557e7363a7d86ad2c19d177d70ac6a171","u_a":"f08915210c14e58ef1fd92c8e7b271ac6f29af4aa9b89fe27b1c4d98fed26c01","u_x":"36a12253aaa12c674038fdeccd93d35f4d092240aabfa30e5ac2ffb11f246d0e","u_y":"b9c639f41601c402068b2e488a775d653a9265a911a6a1d48bd2a6ee2c9f690f"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"e256a8e28da45f566ba0aa78b18b8fef99159a2f902ae6e013e3ea03ffd0b304","proof":null,"script":"73","sender_offset_public_key":"6027b789db891afa8847f70e01a09171b6cddd4861334274d264d3857ae93157","metadata_signature":{"ephemeral_commitment":"da23b441660dbd68015f7b13676092f3847a1910947b4de863c8ee74983a8657","ephemeral_pubkey":"a6757fddd6f71e17b80eaa581ec8e30aeae64aec4e5d0953ce286a7d16f2bd0c","u_a":"11a07c26bf7463dab13cbe66c89aef611c8b8972f8617ec9cb78a3a34f4df804","u_x":"c4422d23cc0bdf88ef15a53d33fb65c289c6ac7f29a2093ebe14cba68fa7890b","u_y":"eaa4c5ad45a2aacb7a384520121118d4fd3e23ddc3928c22bdd64b64e59fbd0e"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"982ad6bcc6d2b2849b9c76eea7e71b12be25e2dd5a17480abe09a125926bcc2e","proof":null,"script":"73","sender_offset_public_key":"3665c4037326581ecd6772268081e2d8b845d0278ada0d161928ddff84a75034","metadata_signature":{"ephemeral_commitment":"e8b2c47301c4147d161ca9009ee134d387fee02bf46ef3f95829c55a0c979905","ephemeral_pubkey":"9c43bc2765190f210b3e1af10610de5c8900d444523a7f34e4e1066631c5d672","u_a":"cc4eb06d19229069c35905d876d96a0fee62a821ffcef76568fb845f92001502","u_x":"3feba3c48362f6a13cb9b62bbd4ce960c30470acd21b959bce1f854bd84efb05","u_y":"01afdeb2d5be53005f124217193e7a8a085518fe512e6bc6a0c7affa20886a0d"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771810} -{"version":"V0","features":{"version":"V0","output_type":0,"maturity":0,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b801d30fa59b2e7bb954fa025f0a3cd30386e77198340aa41fc80b6e8f3e970e","proof":null,"script":"73","sender_offset_public_key":"6ce99f34c04f481e8a3203c2101164c83469bbd1fa03530a4e51a14c29d25579","metadata_signature":{"ephemeral_commitment":"0a051b107d688c6619b70de7926ffb92c5a8ea642383b69fb5565e8b9e1e9774","ephemeral_pubkey":"741dda95869f8b5d72b266983781f9318584bbcc1821a547036c3f6715b0cc37","u_a":"af1f4514658d28673d98123dfac4ae20b5b28aeee17ac1b793d48265b6e38508","u_x":"2bfbfe4cb9f019fa70d2bde2430f1838a5978507ec31aa5bd0db799b98264d0f","u_y":"e2339f5a7679459b42fe38a62ceb1af8f30b61ab39cf418d3ece4c59be2f040f"},"covenant":"","encrypted_data":{"data":"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"minimum_value_promise":60301577771822} -{"version":"V0","features":0,"fee":0,"lock_height":0,"excess":"7e84437ef34e04e40b09946de825ebfbd2d680f334dabe86773ad3f76f920030","excess_sig":{"public_nonce":"be756baf41bf9df644113c8bce5f771152f47640982ec503224e23a14c345d38","signature":"3ca076d162f7e78d0386329ad5ee0d5e85ef56671bb0b14430e32ba362fa840f"},"burn_commitment":null} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"18b18950f9e56b13f1ec0f0bf52f62f1d9742be87c9185891056f5d01a2b1d0f","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430288ac5bcb3fb05a35bfc59df22e40384a07a0f7110a29c06b81377194434ea36","sender_offset_public_key":"34c45a273d44ffa78778ebaa304773ea0bb1411c8500c17d9b003668c7836c60","metadata_signature":{"ephemeral_commitment":"40f3ced4525d70a70ebfaae1ab9aabc99a009589b5e6fa2f69c771e0ae0ba548","ephemeral_pubkey":"305ee7f6744d4dc5f02719a3b1c39d15e0ec7d0de2580b39eb89008850bf2f04","u_a":"c018d54c0be7f1aef0a37ec19b295e313926587ab2e5448572685eb71aca2e06","u_x":"90bf5c52c85adc75958ff41a1a903c9e20a7755e032ce8f8013656076276d50d","u_y":"036bf285d519e0d9cc5c35da7c558ac8c48e4eb20b5c2c7fbb94877548e2b201"},"covenant":"","encrypted_data":{"data":"768538593b93cfc87ea8bbd23bbce1f3154a15cc0be6b64fe65ea1a8d90cdedb37d553a8da25fc28ff9fbaf6e4a21da48a2bce802d22884e6c19d3afefc4e1f8ed23ed3046fe0ba0c36f07c6d970aca1"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"84be3c60d7ce3c0de43d24565f8c6a61b3b4a28d22e1b0f584fb7d7b2a35f95f","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430a26286fd66c1e4613464c55fca80d95c87d6415e97ccfb0bf1cbfdfd191d0201","sender_offset_public_key":"ee2dbd23396369f82a4bb0ff45f31638716a00d52e74f3208fe2e2f869318b57","metadata_signature":{"ephemeral_commitment":"6ccc4b3431f98756056c453b6a3e2eb75507cd836fa211296a8c3a5aa51f2d68","ephemeral_pubkey":"8a41af90b030c495ac34dc6df2ffab1da8b4db3cc7f7d90488b45e38350ffc74","u_a":"a97a706c313547ef53e1a87e3de62ec3d673372635bd30ad00c947016da56802","u_x":"cbd75a079d9f3c44bc71bfd53645feb44c050d9c487a6321282d707da4436205","u_y":"02c95746d0db0dc00e59c061e6ad105d06db73dd8167c378e63ba6998e188c08"},"covenant":"","encrypted_data":{"data":"ee051b414f5ef6e6f376b05453a883c1fd525f2cd35bd2e33214a246f570f6e1eda79eadcda02e7d7511272ab8e501913297371e77460ced9ffed0b5198925092bc0ae9f472e0fbd6b048e02e363cf7a"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"482206555eed1e9df5e8346dad01411d78601669f8045eee0ad5556c8a403c5a","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430bfbd61f8b2d651f3583dd7b132d934baca28e34033fc3ecaf7a2ef53d26f3a6d","sender_offset_public_key":"f2f4503a7bf1673b903cd84fff066b5a6e26b06f1292f1c533631f24a0b1be0d","metadata_signature":{"ephemeral_commitment":"a46dfad5f71c83b1c1e9786184c9e667ec1d7967efc9375ae632248209fb3327","ephemeral_pubkey":"167fe83f55fa5a8b9ebcb97e6e140db911b59ac986af2b64e1ba08dae2ebd71b","u_a":"c1b2d64adb5cef8903f66b49e60096cbe0f5b11e8b2bb464219068fd0592340e","u_x":"86cc47bc2c350a98dd162d8497483b0ac3926c0f6934aa15e9a9db7c275c5400","u_y":"095664fa68f761ad020d2c90c0dd7d4e38a97e0762a8088d7b1e6540b2772508"},"covenant":"","encrypted_data":{"data":"b1f2b99e4fc72b999b17ea282e88b853ff56572214bf7358187149ba0d66d85aa1ab0ea644f4343a4313ccfbacb212cd5447082dbbf40710993a43c532d32c993bfee0eb8009bdea4c36772192f3d6f7"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b400ba561312dda062b671dc7921b64efdd45db37935c60f91eba5bcd3ef8053","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43066e0ad381a15d730bf058a844133f965335e9f125bdd82feb2bcad0f62897394","sender_offset_public_key":"a28be8cc8efcb7e6891cf1b75448fdfcba2a4464f1899c47085f7fe3cfe43044","metadata_signature":{"ephemeral_commitment":"301463ebccc46d0656b6a91e861c1ad361ecee673fce59319a7978ca6e314f69","ephemeral_pubkey":"9eeb4bee46f69ce374925900056018a6b22008f0f6b5cd5405b818d594e4da74","u_a":"c8ee27af4a50e243b169b9012ad6fba45342f0abb1f3872b974940bba781b403","u_x":"4f6f5b07cd7e4e566a86971747a130615e3c268ef2bc47671e8d7d3ab755800e","u_y":"a1866b01494be020936862be2b4e30aa05c4d813ed70c13926762631f23f8903"},"covenant":"","encrypted_data":{"data":"aae5852d744ad73acbfa11c1438f88a43ac6d9abb27bb991b132bc343596c254116cbf1636465ef1bcca78e5a59d0474a0e536fa2274e9ef297af8b5574087a28952bed3e390d1ae6ede5f36b7810ae7"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"a4596e8dd4334030d3f6b868f146469b388aa53c1ced526ef6e80cced15f4c59","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43086078d0657b473666c51c9c9734db48b84b2c50fa487449ee41ca65e31be4c42","sender_offset_public_key":"fc270e4f0e5cae37690d58f18bc1b7038811d10192b1245aaadd939554ee656e","metadata_signature":{"ephemeral_commitment":"20d3f2c3339f0a7150f1a5023a26cdbf37a5973902583ecd7728188b30982e4c","ephemeral_pubkey":"8674af6560ac6b9e1659b7a5e69c57ce27412f8f1befbdfa68140fcc54ed7319","u_a":"3f531859c310bca21ea12d19e8ec9d62078560117d23ca6c9f0e59bf500d330d","u_x":"55cded294834cba34b81a895d7e96691fd7289e9f2beff14653f44e93d9a5b0c","u_y":"433e29cc52b72b60cb941ecd6fa219446300cd8e368237b135bf4d996d33e40f"},"covenant":"","encrypted_data":{"data":"609126d0989efd7594bdbbf0c2c3d26416b5f2ca3f6d9cefc09b2c9699e32d6ee8ca0b38fc667a505f343f49b6cb1fc8fb028861cbc348e2b70dff08879d1c08a62293215e075419154084435618b76f"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"de8d4c24aa8439bc0492cbab128235a3db2c52eef03bb5e40aaec9a587612c69","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43020270b85611ded4ba4aa230fa1240ace6cf9aa18bd9d387a5036c7d357a13350","sender_offset_public_key":"8e4363a7614a504b78afb1d07290b07b2c1938a1b0f9648901081a67e1b62d2e","metadata_signature":{"ephemeral_commitment":"0690d044d068de3c22aea54689d9fb7387fd70aecfa62218363dae9f373edc2e","ephemeral_pubkey":"b82e6e84b3f19d806b6cbc6bf649a527f4ffef290cd6300bc80108be163f5710","u_a":"d8c613230c7646e4d1811ddc381f084cbe8ada9678407bea3c87421223822b0c","u_x":"2368aca0d99c49f8d6177f5a0d0eb3e9c8504f541e34778e3b2ee697c700e001","u_y":"e87cd9e612216879040c67be02beb3ea9a008ae971f3de75d9c7029431dc2203"},"covenant":"","encrypted_data":{"data":"ff8ed315ae5b5b30edb3c08880411564db648a59b1f481a9a821116ce32ec45770f824680b2d9c325608d2c118e2bb467b7e64e17d3000d9d80ecbfd2757e63ec30a8ee457a9030cb06802db77f8e8e9"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"72638b8ca85da01f984b036ca654314af3a1fe3dacb4eabc357746921bd5f15e","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43068ce3bb9527fe4c5618f633a7841063dab60a090ca5aae8868a3e4234a9eef35","sender_offset_public_key":"f85c0236783765c65a812ed55a06e868f7e699f977c2a55283337a20b32b410b","metadata_signature":{"ephemeral_commitment":"1e4fb5aba4d242abd5eaca50d1d885eb3e9f73a3d98e270bdf17555a4257017a","ephemeral_pubkey":"ccc25852fc3c6556b8cbbf21fbfbc3048f71ed1006efad22c21b5efb0c40e64e","u_a":"3bbb3994079f61abf7d85781eacd238d499ae1539df8725d358439d12c224403","u_x":"c651c2729c69a85b7b56d7420f3db712314531ac28abb02dd02efe9bca398f03","u_y":"27508321d6b79d1e6a4f936e96db7b3cb8a1d771d814596bf6c37b18d61fb60c"},"covenant":"","encrypted_data":{"data":"cea7889b34841b440009c1c7e1d0bbd894be2a1ba8263521e5c7d46bf77cc5ba180daa5b1f720a23eed679e90f6f5a2754d518f2ec1221cccae66fe97d62239c06f9e7da34129353c8b4313f7b924208"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"20e54413baba1477e2736f92965e754b0f3e60aa0e012b5d6d8d2f6cd368327e","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430dff7d3d79782249e4cf9923e3407f128be5c1d7234c7a3282e7a272a78173279","sender_offset_public_key":"7e76171df513e09b8640f45e2d086ddc5f26046796f0bb7ca29c6a174eebcc1a","metadata_signature":{"ephemeral_commitment":"682b6c00ef842cbee07fffc93fcc6a9975ca91cf33c181ec3e39682ee410c860","ephemeral_pubkey":"26f92aeeba8573622e7230ba808ef6387829a7f5d6b4f69ab8540a8a60dee80d","u_a":"dc51e24410437988a058939e8f53789c2042b4e06b01141b844ea19df6aebf0e","u_x":"2bb951e0d0c236e9b8dfd4857acf6e03be48928d680f7a6e12d7ffb2f0b7720c","u_y":"f2c54800decc33d89b3338b38e0be048a5a39d051f2656d455c56f92264f2004"},"covenant":"","encrypted_data":{"data":"c6994bb727ed5d7381dee45367eae8f9ad6c47c7822f92190f9fdd7bb39d197b9b7ba478892b2344d1648146b04389470dff6056ec9bf0e42171d64de1fd57869944c61ab50d00af851fbfdf4a9cebdc"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"e681e59b7745d21cd327ebd08d98f698adcde14ca324cfbfaa435ac7c42c4f4f","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430ba2a544760da5b5321a752aa54b0bbafba03b7dd3de152c81dbe0cbd4b12706a","sender_offset_public_key":"888a1f4c056cb089d87483fb86dc606d7f7691dd56d66b58d20dce0d2d51bd30","metadata_signature":{"ephemeral_commitment":"0e29da8caf6461e09dfa2bca89f773a7b083b80b3c3c8af38c31a43f8907911e","ephemeral_pubkey":"da4cf0b877dff56d098bb417c14d8da51351164cc04461bef9332240d6ffbc5e","u_a":"257f9f3dd1278425ad6117b406d95555350e763e82382786c828cabb85b7280b","u_x":"b2e91de697a0a28421f9a045ed4e10230fc0a902ee65eec1cadffab8bb6dca0b","u_y":"0798d2ac9d5b0038b4f7cb36bef8ad0e9d5a6f986bb1b182ad0b940048232406"},"covenant":"","encrypted_data":{"data":"7774882f09158ef733f81d33fb547007d67517c8ab32f774b42dadfa5ea29cac309fc4c46988078002591ae0b27e4ed3ab0680051a1c102f66c781a30ee5985ee4d859b0690ce5ca29e0515501aec9ea"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d4bf703e0ccc1f82eb410c77e916cadf6c2acc9d23d97fa7549a0e25da81d549","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43075db6aafdc79d3dfdbe2943ca31f019bfddb61e3052ba4f2afcce3881515b797","sender_offset_public_key":"52a84b133377c51eed4cd3436a3033f8da69f7508db55b74328609288e061964","metadata_signature":{"ephemeral_commitment":"b438c8d9b511015c23f36679e1a13ea572bd18e5e7901d2a40c5e47a84122c23","ephemeral_pubkey":"dad9848c4b2f0220c2c4611fcca919cd74ed52180f186b86396f30bb6f1b6c67","u_a":"61ce14740f8199a9b8fce91e8297c3ca871c9c49dfae71fd1195e1e68d257b00","u_x":"822ac9c2fcc6683cf7340f208d158e55ba8a0fbd4e7de72a2fe6605ac067d801","u_y":"2a9a7452be5d90910b6f871178d15d8d8de3222e63db57f03989c46776679701"},"covenant":"","encrypted_data":{"data":"d9f531d6dd25c6dac97d10d0f918989b402436d50362500143aafec94b42c471e36ad972e647b63413ea2287aa33d530bfc2634b1a2ebbf2ab7c00b8bd4966d808d150332fbf5189bbd5a0bc40f14486"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"7eb9ca6fc4640fed95e0470583fd103c8da71218999cb77d4714682f86d69900","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43044125e85cce75b82aaba3b128dec8747a5fa9a5011453165cdaf6f46f3ae0dde","sender_offset_public_key":"7c12cf1aa67b78ca60e2b593e5807e1c361afad54e8fda39f9c80f6e029a7b00","metadata_signature":{"ephemeral_commitment":"7618f599087db8ad66f0258c73e77782afaecda8a58136b078df4f44fd21dd4a","ephemeral_pubkey":"ae4879e7e836d8bf92eed6ec0dbf801d45beffd1a920b7ccdb4c15937bf15933","u_a":"5a7ab0f1ede1feb693b9f04e2f070df39481c5f38166814aeb6549f65d62760d","u_x":"698003812d03f9a53c7ee64a5c7009c96379cb345478c9271022220e7918d203","u_y":"c4827bb0ea023b244dfa406bf407ecad7bc262cdbd1f13aa339fadfdd4a2d907"},"covenant":"","encrypted_data":{"data":"84fd2ad1a7b7d55dc6ef7cf48e1239c8639415af0847210d2bda58d3c9da4392ed5e4a69ed45015e8730df94a3be0ed427e8a30adb60f37d027fc968a1b9ec4eb39af7d0ff3537db9ca076b8b9edf9c3"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"b2c9cbc1711da742fb8e282204b50fc632207bc91a0fb6450116e6a490e34b2d","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43074ad971d01f48344ffeb9d517b72a2a87f891552c33bde24904a9d3ee44c9e7e","sender_offset_public_key":"dad66caf2870d9b8ffb647ca39b063924717e9ab5259ed4426e15639d7122a26","metadata_signature":{"ephemeral_commitment":"183834f07b6dd7e5b9996577042f544afecf77371bdae78d22708eae14bd0f6e","ephemeral_pubkey":"9879eb536667cb48701bc35caf6a5d80d73e2eae66471a9e6a07a009ae58f413","u_a":"6d290394905a7a1d080f8ee476496fd82907b49c8e6a94ca3d60ff6704f30a01","u_x":"5fc8077126dade504cfaf22d6379b7388569df60db404aeb718e0f64ab6f1b06","u_y":"21e6b91726ce9a15b509b55bca44acf75a2809aa9b02b2fe6b662a6cda9acf07"},"covenant":"","encrypted_data":{"data":"a02c5581a3d839b30a749e92ce7b443472ee2db2c26f1c043ff16edee061a6484146b464d6060da44df6468b9c879598f293fabdd163307929d3dff97079bf27d58a677c1d4b8181a2e870c285bff957"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"345b82e53950a0221ca5bde52b8bb308276a0942a5d0b7e1c19d5878d7ea3f13","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430298090ded5ee0149ad476bccbd8a2f3499face72ee0c7bc60495f3f3395d3f26","sender_offset_public_key":"7a1a8338c5bbfd11b097370e0bd5f57f4b911d815e9e0a5ab56746e0b496fc45","metadata_signature":{"ephemeral_commitment":"76b81d81eee516cbefcc5fc4d817a02f87c52255c95d539ee04160a3e23d3576","ephemeral_pubkey":"c6ad40b415e3294568e391045350c8161b0910d3686160d834aae5f372741911","u_a":"e12012cace215f93c486a66c038d2b99e227d5310db2e12805a2a86fc398e00b","u_x":"44e8d12b071bb7f72eb4a5b151e2c48508b8cd2d8af73996a2eaa399906d550a","u_y":"6f60f3f4322aee00a69e4af636c780634f15890bef66d176520f182548a3af09"},"covenant":"","encrypted_data":{"data":"1b92941f7b3c3a7d8a7ca6563895859f0fbfd1e88cd49f24f16cfeb55f6a3490129688a096deaf2221e7784589920d96de4fc9c10e0ac99bd06de49ba969e2c6294df3a884f277c0d8eb72b122498257"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"c6c29604070303a0fc32e8592e2e2938233101e8131768a095ad53405553b512","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430ec61102b9209d08e83d0c14e5b7f889ea1f10c28b82a0bbf8ba95120b59dfe5a","sender_offset_public_key":"2a6420524be939075dc8fdd6c7294a9320e3633175965926d608f05f38e45e6c","metadata_signature":{"ephemeral_commitment":"7801344dc8234c313671a9615b5a85bed36bfc65d1644428b9a28b8a37b3933c","ephemeral_pubkey":"0c7184ea450128feefc5c2923a71590676591468e0f5f69b2027f2a2e5daf21e","u_a":"d5cae91fa20472613841fc5a82531ccf0e569fb88bddd9f5ba9c1302e2d1ff03","u_x":"a942c26c70d9f4418f8f2da1039700b1195d97e34dae1bacbbf33b85699fdf0b","u_y":"dd931cfd68f531aed93da6f5a07104633a36ce5c9b7dd5302d8429be185b7e0c"},"covenant":"","encrypted_data":{"data":"fe4f6619f378e1f4fb76ee03d82cd6828c9a6dc888b014886d8592d51c4c81ec3fcf0c79afc8c96370fa465f26a1c4c58e25fe712641a380243f42aa981aeac14ef2138e8b6752c8067083e52cd2760a"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"643f1cd34c3d3b8a21ffc98a7786bef0989a4c1b4f2e4598d72208133524ab68","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430a00d2060ced960ad3abbac895b041d99222e088e5826834dbae729a2ee27a7c3","sender_offset_public_key":"fcddfcde4bf82351d0d3aa3665e3ce867afeae8fa703137b543de483e1e59237","metadata_signature":{"ephemeral_commitment":"2a7f0f8093abe7ccab6d4d674098a4d99d63cf101337afbc994cba6c94518257","ephemeral_pubkey":"18e6bf0f36c2643f450ccd7df818da72d6707d8ec3bf50f34f264b31b6a14600","u_a":"333770a1333006a5688ea76b683194f117e99424364c23a7eceb925393290f04","u_x":"a7ae5610d33930e7ca96ce0b4d9c9134a3c122e17c8b533b5ecdb0325e70a008","u_y":"105d225984999d9391ab1e9728ce0b64df72e7fd265a065e44e3da42f228b50c"},"covenant":"","encrypted_data":{"data":"8b64bc0824a0f57f162cb99c90e4e2060a46663f62cadcbac84cb12e1e5ff46ad8f0f88cb4892641d452d0b77be527c6b31661962d12319f4d086fe59e7044559a93cd1e01962d30a84a0573318aca9d"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"d26147dd7461aa4457384f16479df3916668f0d3f5b90e0a4ffd9a455bce0624","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43081bda62eb863a83c971166ad34c3d53ff16443a42c18b2e1cccdc470ba8102f5","sender_offset_public_key":"dc1dba35eb080cef9d92edddbc757b0557c7b821cb07b749d25aa27726e21e71","metadata_signature":{"ephemeral_commitment":"468def3eb9d9b7fd97cd7a77489ca85f06bbdd22efd8d5e2c031cd762aa9085a","ephemeral_pubkey":"be58d35589b4db016d6aea639c7a2cb0671dd3ecde301265efd65385a7cb7c54","u_a":"2e3133d83841220f241908ae0437478fd1b2132b2caccc689aa14b597da11304","u_x":"47f3a88ac15fd3b0b9a83569ab8d432ad252dad7f88005cb00a03e0a13f33902","u_y":"d6c18bc73458449f6f6f5dc5592fab58cf225d3d58b9875f6a8ce89f456ed808"},"covenant":"","encrypted_data":{"data":"77cd83aeb328c9a50952a9e788c362523b81bc6ff52a9cd6e62c946d0df4fd032340b1a351018b3d2773817ec4cca945f20d795db354490305e6704d900e5f6d0bc402035c8cfc5c22aedc8bfef05d74"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"f207880adb9229d53d0d44bb4d14380f219f49c4531a07dac69911826e284a50","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c430a6055550f1dbc61a15adffdb622fec45c8980960c5fd541351b0bd942a74bb3b","sender_offset_public_key":"885470ee34d1b5ea71957819f2b663e04e173e9402099afc4b508f430f47ee6c","metadata_signature":{"ephemeral_commitment":"4272fc1313aed3def7388af7a77817d1569a416c920ec4576c2216531d1a6e1b","ephemeral_pubkey":"543440833e8129aaed8d852534a81c1aac204bca997f8cc1d7bf9a1faca2f035","u_a":"72cc36037cbe9d26a509abdea29e53b5550c6d903acb5a34d420e6210164fc0a","u_x":"2a40990a5b7780fd29928fbb5c55e6470fef0621e8cdf67c7667f46964b1d709","u_y":"35fe5d46ceda868c7bc748f678a36f3445128c3ea7da9ad38550b9862fbe1508"},"covenant":"","encrypted_data":{"data":"d0710ea6f5e67a37667d7fcda3cdb4f62ab363e5202c76d0bd448115c66fcfa0c1aa2524e587af951625b1abca5f2400ad5a42759ba182b72f2371059baa14c7c01711f9cb55b15a73f7236e65467827"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"a07700a82cabb04c5cd014182068f4ffb8891be959f5ac0aeba8ba81943a047a","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43059a1f98172a253f96be4f3a237378d59c973745d479c0f66edcc99344d20c11a","sender_offset_public_key":"7e1dd2274ba9d2c3765817270f1b63f1a75461df708b8061d4b334b2910ec349","metadata_signature":{"ephemeral_commitment":"b8a62595ccc2a8660b3ae328794175848009c52bf9a45d484e3bcfdec3b13b06","ephemeral_pubkey":"9c8099012772713161839e616b196924338b182dc4c5afc5549ac059b94c042c","u_a":"d31fac476f8ddcb58830b2c647779026c9b23b1d5c2e589c4aba152f9ec8d204","u_x":"4193199036af17b6b09564b5600fe5a9b83b3f6eada19891d214fb99cfd08e0a","u_y":"4d39ed13295eb7104558519c68f54f0cae4133e64c4efb621b93b9f07666540c"},"covenant":"","encrypted_data":{"data":"ca29040bacdc8f712cf8f3f01770c5e791b76d9a7e633279d5bc74f0cca1b415d3097f6f0ef4234e3dbadc46ba3efeda40a9393c2f7bcb1661c998dcad0b6db4695adeefed7ca78b666c7182e213edcc"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"3e5525ab4104c36b95c41377fe229159fd6f4c7ca79a132e57ca04e4da626c73","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c43081f280abb88e7e2984b7e3b681b343ddcf6210a8f421dda7aa29dfb0ef971775","sender_offset_public_key":"0cffaa2f543eefe8cd6c0afa3a6a5a7738f2d2e3d0b8166c5ac7da285952c106","metadata_signature":{"ephemeral_commitment":"1c6f6d57e25a811c1790937cc0899af711b8f0b94058a89c50f0c9c24637ee16","ephemeral_pubkey":"56f33b949c2509c2401fec2f3821029f34ce35c2ef413a2ba81387c05818117d","u_a":"b47fd014849beebee8a5c5e3598b179eb0595423ee31df6d5be2185e0a3c0b0c","u_x":"e2850c499e46e266402c8a17b1ff95340c1ce1bc420881232881f2321218e400","u_y":"65934210ced41c6ca2cd3b12cd2ec8fd8a31c0d68059e1fbea14f7feb352060f"},"covenant":"","encrypted_data":{"data":"8eef746716b6e65fde7af8fd9d17fb0e81d0cdc3ab5ea2ace2b7241a7c5e312f1baf5896b9e6a46636597ad32f18eb16eb3258d8b98c8d1b0faaed1317a754dd717ab643fed15ea23bc11c70251f62b9"},"minimum_value_promise":2000000000} +{"version":"V0","features":{"version":"V0","output_type":0,"maturity":5,"coinbase_extra":[],"sidechain_feature":null,"range_proof_type":"revealed_value"},"commitment":"fce85a69db1dbbf47dee09233b9fdc988c1d3ce341c73fec24acee9d8a076937","proof":null,"script":"b4020314655e679fc083bc209e83c59c0c768f2b38cf8a8cd12c6c6d1e657d240819378eb94d03240d2865d940e634d20b4e3d861c73f2fc82622d1026f440d5ec7b7b3c5074169c2a33344ef9e8657b7693bf9a0955680215330e1e30daf6c446c4308dcfc033c6543661550117a29f10e54fd1ac57d53242a9c279f9e1a77cd68b94","sender_offset_public_key":"fcf463bc66893a3c84f8ecea51f35c5f40e7f6d36e644d2c98b9be568707bf02","metadata_signature":{"ephemeral_commitment":"78e30dc8047d482a598f986b5d98c18bb637a1e35eae5846c099a8c294441d6d","ephemeral_pubkey":"564d325dfa372abf8aa951f673d4d99b995f1a2eea7cb34ac3ace7455ffdef3d","u_a":"dfa85e56967dc712e4adcc3cb26920e8608eb1403bb5383bbc5b392f2767e901","u_x":"63a758ec8586da957543d2c17b482c978728c3284d15f4002fba3e8da7d0b00c","u_y":"e6e7e420dda33ee2e34e0bd798aa76b0d507e181bb3ea2f2df0f8bb98e169a0e"},"covenant":"","encrypted_data":{"data":"3bf080786ec59f3b5df7299c0064f6c74e5bacd49999425d6812aeefb239e0ef8d67b0546de325b6520876155c47662509a70e87f145ca4e80cd5a83f566fa4bdf7fcaa808eaea4edf8ec07142aefd3d"},"minimum_value_promise":2000000000} +{"version":"V0","features":0,"fee":0,"lock_height":0,"excess":"2853b04a1bfff1e10e4d0a139913d323472c0490e068bb2f785198d74b1d0042","excess_sig":{"public_nonce":"10994a8c691f5621be52eed4e71f30d4b6e89dc8c755f9ae7798530c9a420f7e","signature":"df1bbeaf96f3020b1a7acb54f109ac47e1e9632e5a164c46f063466120dea600"},"burn_commitment":null} diff --git a/base_layer/core/src/blocks/faucets/mod.rs b/base_layer/core/src/blocks/faucets/mod.rs new file mode 100644 index 0000000000..103a56e52d --- /dev/null +++ b/base_layer/core/src/blocks/faucets/mod.rs @@ -0,0 +1,181 @@ +// Copyright 2024. 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. +#[cfg(test)] +mod test { + + use std::{convert::TryFrom, fs::File, io::Write}; + + use rand::rngs::OsRng; + use tari_common_types::{ + tari_address::TariAddress, + types::{Commitment, PrivateKey, PublicKey, Signature}, + }; + use tari_crypto::keys::{PublicKey as PkTrait, SecretKey as SkTrait}; + use tari_key_manager::key_manager_service::KeyManagerInterface; + use tari_script::{ExecutionStack, Opcode::CheckMultiSigVerifyAggregatePubKey, TariScript}; + use tari_utilities::ByteArray; + + use crate::{ + one_sided::public_key_to_output_encryption_key, + transactions::{ + key_manager::{ + create_memory_db_key_manager, + SecretTransactionKeyManagerInterface, + TransactionKeyManagerBranch, + TransactionKeyManagerInterface, + }, + tari_amount::MicroMinotari, + transaction_components::{ + encrypted_data::PaymentId, + KernelFeatures, + OutputFeatures, + OutputFeaturesVersion, + OutputType, + RangeProofType, + TransactionKernel, + TransactionKernelVersion, + TransactionOutput, + TransactionOutputVersion, + WalletOutputBuilder, + }, + transaction_protocol::TransactionMetadata, + }, + }; + + pub async fn create_faucets( + amount: MicroMinotari, + num_faucets: usize, + signature_threshold: u8, + lock_height: u64, + addresses: Vec, + ) -> (Vec, TransactionKernel) { + let mut list_of_spend_keys = Vec::new(); + let mut total_script_key = PublicKey::default(); + let key_manager = create_memory_db_key_manager().unwrap(); + for address in &addresses { + list_of_spend_keys.push(address.public_spend_key().clone()); + total_script_key = total_script_key + address.public_spend_key(); + } + let view_key = public_key_to_output_encryption_key(&total_script_key).unwrap(); + let view_key_id = key_manager.import_key(view_key.clone()).await.unwrap(); + let address_len = u8::try_from(addresses.len()).unwrap(); + let mut outputs = Vec::new(); + let mut total_private_key = PrivateKey::default(); + + for _ in 0..num_faucets { + let (commitment_mask, script_key) = key_manager.get_next_commitment_mask_and_script_key().await.unwrap(); + total_private_key = + total_private_key + &key_manager.get_private_key(&commitment_mask.key_id).await.unwrap(); + let commitment = key_manager + .get_commitment(&commitment_mask.key_id, &amount.into()) + .await + .unwrap(); + let mut commitment_bytes = [0u8; 32]; + commitment_bytes.clone_from_slice(commitment.as_bytes()); + + let sender_offset = key_manager + .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) + .await + .unwrap(); + let script = TariScript::new(vec![CheckMultiSigVerifyAggregatePubKey( + signature_threshold, + address_len, + list_of_spend_keys.clone(), + Box::new(commitment_bytes), + )]); + let output = WalletOutputBuilder::new(amount, commitment_mask.key_id) + .with_features(OutputFeatures::new( + OutputFeaturesVersion::get_current_version(), + OutputType::Standard, + lock_height, + Vec::new(), + None, + RangeProofType::RevealedValue, + )) + .with_script(script) + .encrypt_data_for_recovery(&key_manager, Some(&view_key_id), PaymentId::Empty) + .await + .unwrap() + .with_input_data(ExecutionStack::default()) + .with_version(TransactionOutputVersion::get_current_version()) + .with_sender_offset_public_key(sender_offset.pub_key) + .with_script_key(script_key.key_id) + .with_minimum_value_promise(amount) + .sign_as_sender_and_receiver(&key_manager, &sender_offset.key_id) + .await + .unwrap() + .try_build(&key_manager) + .await + .unwrap(); + outputs.push(output.to_transaction_output(&key_manager).await.unwrap()); + } + // lets create a single kernel for all the outputs + let r = PrivateKey::random(&mut OsRng); + let tx_meta = TransactionMetadata::new_with_features(0.into(), 0, KernelFeatures::empty()); + let total_public_key = PublicKey::from_secret_key(&total_private_key); + let e = TransactionKernel::build_kernel_challenge_from_tx_meta( + &TransactionKernelVersion::get_current_version(), + &PublicKey::from_secret_key(&r), + &total_public_key, + &tx_meta, + ); + let signature = Signature::sign_raw_uniform(&total_private_key, r, &e).unwrap(); + let excess = Commitment::from_public_key(&total_public_key); + let kernel = + TransactionKernel::new_current_version(KernelFeatures::empty(), 0.into(), 0, excess, signature, None); + (outputs, kernel) + } + + // Only run this when you want to create a new utxo file + #[ignore] + #[tokio::test] + async fn print_faucet() { + let addresses = vec![ + TariAddress::from_base58( + "f4bYsv3sEMroDGKMMjhgm7cp1jDShdRWQzmV8wZiD6sJPpAEuezkiHtVhn7akK3YqswH5t3sUASW7rbvPSqMBDSCSp", + ) + .unwrap(), + TariAddress::from_base58( + "f44jftbpTid23oDsEjTodayvMmudSr3g66R6scTJkB5911ZfJRq32FUJDD4CiQSkAPq574i8pMjqzm5RtzdH3Kuknwz", + ) + .unwrap(), + TariAddress::from_base58( + "f4GYN3QVRboH6uwG9oFj3LjmUd4XVd1VDYiT6rNd4gCpZF6pY7iuoCpoajfDfuPynS7kspXU5hKRMWLTP9CRjoe1hZU", + ) + .unwrap(), + ]; + for address in &addresses { + println!("{}", address.public_spend_key()); + } + // lets create a faucet with 10 outputs of 1000T each + let (outputs, kernel) = create_faucets(MicroMinotari::from(2_000_000_000), 20, 2, 5, addresses).await; + let mut utxo_file = File::create("utxos.json").expect("Could not create utxos.json"); + + for output in outputs { + let utxo_s = serde_json::to_string(&output).unwrap(); + utxo_file.write_all(format!("{}\n", utxo_s).as_bytes()).unwrap(); + } + + let kernel = serde_json::to_string(&kernel).unwrap(); + let _result = utxo_file.write_all(format!("{}\n", kernel).as_bytes()); + } +} diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 57a3289d8e..983ed0a908 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -106,7 +106,7 @@ pub fn get_stagenet_genesis_block() -> ChainBlock { let mut block = get_stagenet_genesis_block_raw(); // Add faucet utxos - enable/disable as required - let add_faucet_utxos = true; + let add_faucet_utxos = false; if add_faucet_utxos { // NB! Update 'consensus_constants.rs/pub fn igor()/ConsensusConstants {faucet_value: ?}' with total value // NB: `stagenet_genesis_sanity_check` must pass @@ -157,7 +157,7 @@ pub fn get_nextnet_genesis_block() -> ChainBlock { let mut block = get_nextnet_genesis_block_raw(); // Add faucet utxos - enable/disable as required - let add_faucet_utxos = true; + let add_faucet_utxos = false; if add_faucet_utxos { // NB! Update 'consensus_constants.rs/pub fn igor()/ConsensusConstants {faucet_value: ?}' with total value // NB: `nextnet_genesis_sanity_check` must pass @@ -279,10 +279,11 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { // Hardcode the Merkle roots once they've been computed above block.header.kernel_mr = - FixedHash::from_hex("3f4011ec1e8ddfbd66fb7331c5623b38f529a66e81233d924df85f2070b2aacb").unwrap(); + FixedHash::from_hex("b97afb0f165fc41e47d5a6bea4e651a16ffab2ecc6259814b42084aeac8fb959").unwrap(); block.header.output_mr = - FixedHash::from_hex("3e40efda288a57d3319c63388dd47ffe4b682baaf6a3b58622ec94d77ad712a2").unwrap(); - block.header.validator_node_mr = FixedHash::zero(); + FixedHash::from_hex("a2bbf7770db43bb1ad57c20d7737870f290618376f8b156019414abb494c23a8").unwrap(); + block.header.validator_node_mr = + FixedHash::from_hex("277da65c40b2cf99db86baedb903a3f0a38540f3a94d40c826eecac7e27d5dfc").unwrap(); } let accumulated_data = BlockHeaderAccumulatedData { @@ -299,7 +300,7 @@ pub fn get_esmeralda_genesis_block() -> ChainBlock { fn get_esmeralda_genesis_block_raw() -> Block { // Set genesis timestamp - let genesis_timestamp = DateTime::parse_from_rfc2822("11 Mar 2024 08:00:00 +0200").expect("parse may not fail"); + let genesis_timestamp = DateTime::parse_from_rfc2822("12 Jul 2024 08:00:00 +0200").expect("parse may not fail"); // Let us add a "not before" proof to the genesis block let not_before_proof = b"as I sip my drink, thoughts of esmeralda consume my mind, like a refreshing nourishing draught \ @@ -395,8 +396,6 @@ mod test { use std::convert::TryFrom; use tari_common_types::{epoch::VnEpoch, types::Commitment}; - use tari_utilities::ByteArray; - use Network; use super::*; use crate::{ @@ -417,7 +416,7 @@ mod test { // Note: Generate new data for `pub fn get_esmeralda_genesis_block()` and `fn get_esmeralda_genesis_block_raw()` // if consensus values change, e.g. new faucet or other let block = get_esmeralda_genesis_block(); - check_block(Network::Esmeralda, &block, 100, 1); + check_block(Network::Esmeralda, &block, 20, 1); } #[test] @@ -426,7 +425,7 @@ mod test { // Note: Generate new data for `pub fn get_nextnet_genesis_block()` and `fn get_stagenet_genesis_block_raw()` // if consensus values change, e.g. new faucet or other let block = get_nextnet_genesis_block(); - check_block(Network::NextNet, &block, 100, 1); + check_block(Network::NextNet, &block, 0, 0); } #[test] @@ -436,7 +435,7 @@ mod test { // Note: Generate new data for `pub fn get_stagenet_genesis_block()` and `fn get_stagenet_genesis_block_raw()` // if consensus values change, e.g. new faucet or other let block = get_stagenet_genesis_block(); - check_block(Network::StageNet, &block, 100, 1); + check_block(Network::StageNet, &block, 0, 0); } #[test] diff --git a/base_layer/core/src/blocks/mod.rs b/base_layer/core/src/blocks/mod.rs index e3f06abb9f..776495672d 100644 --- a/base_layer/core/src/blocks/mod.rs +++ b/base_layer/core/src/blocks/mod.rs @@ -47,6 +47,9 @@ pub use block_header::{BlockHeader, BlockHeaderValidationError}; #[cfg(feature = "base_node")] pub mod genesis_block; +#[cfg(feature = "base_node")] +mod faucets; + #[cfg(feature = "base_node")] mod historical_block; #[cfg(feature = "base_node")] @@ -59,6 +62,7 @@ pub use new_block_template::NewBlockTemplate; #[cfg(feature = "base_node")] mod new_blockheader_template; + #[cfg(feature = "base_node")] pub use new_blockheader_template::NewBlockHeaderTemplate; diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index 4b987c1604..2a66d9a15d 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -275,7 +275,9 @@ where B: BlockchainBackend } else { // lets load the smt into memory let mut smt = blockchain_db.smt_write_access()?; + warn!(target: LOG_TARGET, "Loading SMT into memory from stored db"); *smt = blockchain_db.db_write_access()?.calculate_tip_smt()?; + warn!(target: LOG_TARGET, "Finished loading SMT into memory from stored db"); } if config.cleanup_orphans_at_startup { match blockchain_db.cleanup_all_orphans() { @@ -2450,7 +2452,7 @@ fn get_previous_timestamps( Ok(timestamps) } -/// Gets all blocks ordered from the the block that connects (via prev_hash) to the main chain, to the orphan tip. +/// Gets all blocks ordered from the block that connects (via prev_hash) to the main chain, to the orphan tip. #[allow(clippy::ptr_arg)] fn get_orphan_link_main_chain( db: &T, @@ -2651,7 +2653,6 @@ mod test { chain_strength_comparer::strongest_chain, consensus_constants::PowAlgorithmConstants, ConsensusConstantsBuilder, - ConsensusManager, }, proof_of_work::Difficulty, test_helpers::{ @@ -2955,7 +2956,6 @@ mod test { mod handle_possible_reorg { use super::*; - use crate::test_helpers::blockchain::update_block_and_smt; #[ignore] #[tokio::test] 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 3e9d2a893b..c56a8acad4 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 @@ -1461,7 +1461,7 @@ impl LMDBDatabase { seed: &[u8], height: u64, ) -> Result<(), ChainStorageError> { - let current_height = lmdb_get(write_txn, &self.monero_seed_height_db, seed)?.unwrap_or(std::u64::MAX); + let current_height = lmdb_get(write_txn, &self.monero_seed_height_db, seed)?.unwrap_or(u64::MAX); if height < current_height { lmdb_replace(write_txn, &self.monero_seed_height_db, seed, &height, None)?; }; @@ -1663,12 +1663,6 @@ impl LMDBDatabase { output_hash: &[u8], ) -> Result, ChainStorageError> { if let Some(key) = lmdb_get::<_, Vec>(txn, &self.txos_hash_to_index_db, output_hash)? { - debug!( - target: LOG_TARGET, - "Fetch output: {} Found ({})", - to_hex(output_hash), - key.to_hex() - ); match lmdb_get::<_, TransactionOutputRowData>(txn, &self.utxos_db, &key)? { Some(TransactionOutputRowData { output: o, @@ -1686,11 +1680,6 @@ impl LMDBDatabase { _ => Ok(None), } } else { - debug!( - target: LOG_TARGET, - "Fetch output: {} NOT found in index", - to_hex(output_hash) - ); Ok(None) } } @@ -1701,12 +1690,6 @@ impl LMDBDatabase { output_hash: &[u8], ) -> Result, ChainStorageError> { if let Some(key) = lmdb_get::<_, Vec>(txn, &self.deleted_txo_hash_to_header_index, output_hash)? { - debug!( - target: LOG_TARGET, - "Fetch input: {} Found ({})", - to_hex(output_hash), - key.to_hex() - ); match lmdb_get::<_, TransactionInputRowData>(txn, &self.inputs_db, &key)? { Some(TransactionInputRowData { input: i, @@ -1724,11 +1707,6 @@ impl LMDBDatabase { _ => Ok(None), } } else { - debug!( - target: LOG_TARGET, - "Fetch input: {} NOT found in index", - to_hex(output_hash) - ); Ok(None) } } @@ -2101,13 +2079,11 @@ 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()) } fn fetch_input(&self, output_hash: &HashOutput) -> Result, ChainStorageError> { - debug!(target: LOG_TARGET, "Fetch input: {}", output_hash.to_hex()); let txn = self.read_transaction()?; self.fetch_input_in_txn(&txn, output_hash.as_slice()) } @@ -2586,9 +2562,9 @@ impl BlockchainBackend for LMDBDatabase { } trace!( target: LOG_TARGET, - "Finished calculating new smt (size: {}), took: #{}s", + "Finished calculating new smt (size: {}), took: {:.2?}", smt.size(), - start.elapsed().as_millis() + start.elapsed() ); Ok(smt) } 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 a7fa5e4c01..4d1f05eae2 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -127,7 +127,7 @@ mod fetch_blocks { #[tokio::test] async fn it_returns_all() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(4, &db, &key_manager).await; let blocks = db.fetch_blocks(.., true).unwrap(); assert_eq!(blocks.len(), 5); @@ -139,7 +139,7 @@ mod fetch_blocks { #[tokio::test] async fn it_returns_one() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (new_blocks, _) = add_many_chained_blocks(1, &db, &key_manager).await; let blocks = db.fetch_blocks(1..=1, true).unwrap(); assert_eq!(blocks.len(), 1); @@ -149,7 +149,7 @@ mod fetch_blocks { #[tokio::test] async fn it_returns_nothing_if_asking_for_blocks_out_of_range() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(1, &db, &key_manager).await; let blocks = db.fetch_blocks(2.., true).unwrap(); assert!(blocks.is_empty()); @@ -158,7 +158,7 @@ mod fetch_blocks { #[tokio::test] async fn it_returns_blocks_between_bounds_exclusive() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let blocks = db.fetch_blocks(3..5, true).unwrap(); assert_eq!(blocks.len(), 2); @@ -169,7 +169,7 @@ mod fetch_blocks { #[tokio::test] async fn it_returns_blocks_between_bounds_inclusive() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let blocks = db.fetch_blocks(3..=5, true).unwrap(); assert_eq!(blocks.len(), 3); @@ -181,7 +181,7 @@ mod fetch_blocks { #[tokio::test] async fn it_returns_blocks_to_the_tip() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let blocks = db.fetch_blocks(3.., true).unwrap(); assert_eq!(blocks.len(), 3); @@ -193,7 +193,7 @@ mod fetch_blocks { #[tokio::test] async fn it_returns_blocks_from_genesis() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let blocks = db.fetch_blocks(..=3, true).unwrap(); assert_eq!(blocks.len(), 4); @@ -224,7 +224,7 @@ mod fetch_headers { #[tokio::test] async fn it_returns_all() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(4, &db, &key_manager).await; let headers = db.fetch_headers(..).unwrap(); assert_eq!(headers.len(), 5); @@ -236,7 +236,7 @@ mod fetch_headers { #[tokio::test] async fn it_returns_nothing_if_asking_for_blocks_out_of_range() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(1, &db, &key_manager).await; let headers = db.fetch_headers(2..).unwrap(); assert!(headers.is_empty()); @@ -245,7 +245,7 @@ mod fetch_headers { #[tokio::test] async fn it_returns_blocks_between_bounds_exclusive() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let headers = db.fetch_headers(3..5).unwrap(); assert_eq!(headers.len(), 2); @@ -256,7 +256,7 @@ mod fetch_headers { #[tokio::test] async fn it_returns_blocks_between_bounds_inclusive() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let headers = db.fetch_headers(3..=5).unwrap(); assert_eq!(headers.len(), 3); @@ -267,7 +267,7 @@ mod fetch_headers { #[tokio::test] async fn it_returns_blocks_to_the_tip() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let headers = db.fetch_headers(3..).unwrap(); assert_eq!(headers.len(), 3); @@ -279,7 +279,7 @@ mod fetch_headers { #[tokio::test] async fn it_returns_blocks_from_genesis() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let headers = db.fetch_headers(..=3).unwrap(); assert_eq!(headers.len(), 4); @@ -307,7 +307,7 @@ mod find_headers_after_hash { async fn it_returns_from_genesis() { let db = setup(); let genesis_hash = db.fetch_block(0, true).unwrap().block().hash(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(1, &db, &key_manager).await; let hashes = vec![genesis_hash]; let (index, headers) = db.find_headers_after_hash(hashes, 1).unwrap().unwrap(); @@ -318,7 +318,7 @@ mod find_headers_after_hash { #[tokio::test] async fn it_returns_the_first_headers_found() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let hashes = (1..=3) .rev() @@ -334,7 +334,7 @@ mod find_headers_after_hash { async fn fnit_ignores_unknown_hashes() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let hashes = (2..=4) .map(|i| db.fetch_block(i, true).unwrap().block().hash()) @@ -362,7 +362,7 @@ mod fetch_block_hashes_from_header_tip { #[tokio::test] async fn it_returns_empty_set_for_big_offset() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); add_many_chained_blocks(5, &db, &key_manager).await; let hashes = db.fetch_block_hashes_from_header_tip(3, 6).unwrap(); assert!(hashes.is_empty()); @@ -371,7 +371,7 @@ mod fetch_block_hashes_from_header_tip { #[tokio::test] async fn it_returns_n_hashes_from_tip() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (blocks, _) = add_many_chained_blocks(5, &db, &key_manager).await; let hashes = db.fetch_block_hashes_from_header_tip(3, 1).unwrap(); assert_eq!(hashes.len(), 3); @@ -383,7 +383,7 @@ mod fetch_block_hashes_from_header_tip { #[tokio::test] async fn it_returns_hashes_without_overlapping() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (blocks, _) = add_many_chained_blocks(3, &db, &key_manager).await; let hashes = db.fetch_block_hashes_from_header_tip(2, 0).unwrap(); assert_eq!(hashes[0], blocks[2].hash()); @@ -396,7 +396,7 @@ mod fetch_block_hashes_from_header_tip { async fn it_returns_all_hashes_from_tip() { let db = setup(); let genesis = db.fetch_tip_header().unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (blocks, _) = add_many_chained_blocks(5, &db, &key_manager).await; let hashes = db.fetch_block_hashes_from_header_tip(10, 0).unwrap(); assert_eq!(hashes.len(), 6); @@ -424,7 +424,7 @@ mod fetch_total_size_stats { async fn it_measures_the_number_of_entries() { let db = setup(); let genesis_output_count = db.fetch_header(0).unwrap().unwrap().output_smt_size; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let _block_and_outputs = add_many_chained_blocks(2, &db, &key_manager).await; let stats = db.fetch_total_size_stats().unwrap(); assert_eq!( @@ -481,7 +481,7 @@ mod fetch_header_containing_kernel_mmr { async fn it_returns_corresponding_header() { let db = setup(); let genesis = db.fetch_block(0, true).unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (blocks, outputs) = add_many_chained_blocks(1, &db, &key_manager).await; let num_genesis_kernels = genesis.block().body.kernels().len() as u64; @@ -531,7 +531,7 @@ mod clear_all_pending_headers { async fn it_clears_no_headers() { let db = setup(); assert_eq!(db.clear_all_pending_headers().unwrap(), 0); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let _block_and_outputs = add_many_chained_blocks(2, &db, &key_manager).await; db.clear_all_pending_headers().unwrap(); let last_header = db.fetch_last_header().unwrap(); @@ -541,7 +541,7 @@ mod clear_all_pending_headers { #[tokio::test] async fn it_clears_headers_after_tip() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let _blocks_and_outputs = add_many_chained_blocks(2, &db, &key_manager).await; let prev_block = db.fetch_block(2, true).unwrap(); let mut prev_accum = prev_block.accumulated_data().clone(); @@ -603,7 +603,7 @@ mod validator_node_merkle_root { #[tokio::test] async fn it_has_the_correct_genesis_merkle_root() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut vn_mmr = SparseMerkleTree::::new(); let db = setup(); let (blocks, _outputs) = add_many_chained_blocks(1, &db, &key_manager).await; @@ -613,7 +613,7 @@ mod validator_node_merkle_root { #[tokio::test] async fn it_has_the_correct_merkle_root_for_current_vn_set() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (blocks, outputs) = add_many_chained_blocks(1, &db, &key_manager).await; let (sk, public_key) = RistrettoPublicKey::random_keypair(&mut OsRng); @@ -668,7 +668,7 @@ mod validator_node_merkle_root { #[tokio::test] async fn it_has_the_correct_merkle_root_for_current_vn_set_with_sidechain() { let db = setup(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (blocks, outputs) = add_many_chained_blocks(1, &db, &key_manager).await; let (sk, public_key) = RistrettoPublicKey::random_keypair(&mut OsRng); diff --git a/base_layer/core/src/common/mod.rs b/base_layer/core/src/common/mod.rs index 0f447aecd3..52dd52d263 100644 --- a/base_layer/core/src/common/mod.rs +++ b/base_layer/core/src/common/mod.rs @@ -28,7 +28,6 @@ use crate::consensus::DomainSeparatedConsensusHasher; pub mod borsh; pub mod byte_counter; -pub mod limited_reader; pub mod one_sided; #[cfg(feature = "base_node")] diff --git a/base_layer/core/src/common/one_sided.rs b/base_layer/core/src/common/one_sided.rs index 3d5740ee08..8bd8488a6c 100644 --- a/base_layer/core/src/common/one_sided.rs +++ b/base_layer/core/src/common/one_sided.rs @@ -20,8 +20,6 @@ // 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 core::result::Result; - use blake2::Blake2b; use digest::consts::U64; use tari_common_types::types::{PrivateKey, PublicKey, WalletHasher}; @@ -32,7 +30,7 @@ use tari_crypto::{ keys::{PublicKey as PKtrait, SecretKey as SKtrait}, }; use tari_hashing::WalletOutputEncryptionKeysDomain; -use tari_utilities::byte_array::ByteArrayError; +use tari_utilities::{byte_array::ByteArrayError, ByteArray}; hash_domain!( WalletOutputRewindKeysDomain, @@ -59,6 +57,26 @@ pub fn shared_secret_to_output_encryption_key(shared_secret: &CommsDHKE) -> Resu ) } +/// Generate an output encryption key from a secret key +pub fn secret_key_to_output_encryption_key(secret_key: &PrivateKey) -> Result { + PrivateKey::from_uniform_bytes( + WalletOutputEncryptionKeysDomainHasher::new() + .chain(secret_key.as_bytes()) + .finalize() + .as_ref(), + ) +} + +/// Generate an output encryption key from a public key +pub fn public_key_to_output_encryption_key(public_key: &PublicKey) -> Result { + PrivateKey::from_uniform_bytes( + WalletOutputEncryptionKeysDomainHasher::new() + .chain(public_key.as_bytes()) + .finalize() + .as_ref(), + ) +} + /// Generate an output spending key from a Diffie-Hellman shared secret pub fn shared_secret_to_output_spending_key(shared_secret: &CommsDHKE) -> Result { PrivateKey::from_uniform_bytes( @@ -70,22 +88,19 @@ pub fn shared_secret_to_output_spending_key(shared_secret: &CommsDHKE) -> Result } /// Stealth address domain separated hasher using Diffie-Hellman shared secret -pub fn diffie_hellman_stealth_domain_hasher( - private_key: &PrivateKey, - public_key: &PublicKey, -) -> DomainSeparatedHash> { +pub fn diffie_hellman_stealth_domain_hasher(diffie_hellman: CommsDHKE) -> DomainSeparatedHash> { WalletHasher::new_with_label("stealth_address") - .chain(CommsDHKE::new(private_key, public_key).as_bytes()) + .chain(diffie_hellman.as_bytes()) .finalize() } /// Stealth payment script spending key pub fn stealth_address_script_spending_key( dh_domain_hasher: &DomainSeparatedHash>, - destination_public_key: &PublicKey, + spend_key: &PublicKey, ) -> PublicKey { PublicKey::from_secret_key( &PrivateKey::from_uniform_bytes(dh_domain_hasher.as_ref()) .expect("'DomainSeparatedHash>' has correct size"), - ) + destination_public_key + ) + spend_key } diff --git a/base_layer/core/src/consensus/consensus_constants.rs b/base_layer/core/src/consensus/consensus_constants.rs index 530c9aa9f9..a561c1ea46 100644 --- a/base_layer/core/src/consensus/consensus_constants.rs +++ b/base_layer/core/src/consensus/consensus_constants.rs @@ -97,6 +97,8 @@ pub struct ConsensusConstants { transaction_weight: TransactionWeight, /// Maximum byte size of TariScript max_script_byte_size: usize, + /// Maximum byte size of encrypted data + max_extra_encrypted_data_byte_size: usize, /// Range of valid transaction input versions input_version_range: RangeInclusive, /// Range of valid transaction output (and features) versions @@ -155,9 +157,7 @@ pub struct PowAlgorithmConstants { pub target_time: u64, } -const FAUCET_VALUE: u64 = 6_030_157_777_181_012; -const ESMERALDA_FAUCET_VALUE: u64 = FAUCET_VALUE; -// const IGOR_FAUCET_VALUE: u64 = 1_897_859_637_874_722; +const FAUCET_VALUE: u64 = 0; // 6_030_157_777_181_012; const INITIAL_EMISSION: MicroMinotari = MicroMinotari(13_952_877_857); const ESMERALDA_INITIAL_EMISSION: MicroMinotari = INITIAL_EMISSION; @@ -274,6 +274,11 @@ impl ConsensusConstants { self.max_script_byte_size } + /// The maximum serialized byte size of TariScript + pub fn max_extra_encrypted_data_byte_size(&self) -> usize { + self.max_extra_encrypted_data_byte_size + } + /// This is the min initial difficulty that can be requested for the pow pub fn min_pow_difficulty(&self, pow_algo: PowAlgorithm) -> Difficulty { match self.proof_of_work.get(&pow_algo) { @@ -396,7 +401,8 @@ impl ConsensusConstants { proof_of_work: algos, faucet_value: 0.into(), transaction_weight: TransactionWeight::latest(), - max_script_byte_size: 2048, + max_script_byte_size: 512, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -460,7 +466,8 @@ impl ConsensusConstants { proof_of_work: algos, faucet_value: 0.into(), // IGOR_FAUCET_VALUE.into(), transaction_weight: TransactionWeight::v1(), - max_script_byte_size: 2048, + max_script_byte_size: 512, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -515,9 +522,10 @@ impl ConsensusConstants { max_randomx_seed_height: 3000, max_extra_field_size: 200, proof_of_work: algos, - faucet_value: ESMERALDA_FAUCET_VALUE.into(), + faucet_value: 40_000_000_000.into(), transaction_weight: TransactionWeight::v1(), - max_script_byte_size: 2048, + max_script_byte_size: 512, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -573,7 +581,8 @@ impl ConsensusConstants { proof_of_work: algos, faucet_value: FAUCET_VALUE.into(), transaction_weight: TransactionWeight::v1(), - max_script_byte_size: 2048, + max_script_byte_size: 512, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -623,7 +632,8 @@ impl ConsensusConstants { proof_of_work: algos, faucet_value: FAUCET_VALUE.into(), transaction_weight: TransactionWeight::v1(), - max_script_byte_size: 2048, + max_script_byte_size: 512, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -675,7 +685,8 @@ impl ConsensusConstants { proof_of_work: algos, faucet_value: MicroMinotari::from(0), transaction_weight: TransactionWeight::v1(), - max_script_byte_size: 2048, + max_script_byte_size: 512, + max_extra_encrypted_data_byte_size: 256, input_version_range, output_version_range, kernel_version_range, @@ -893,7 +904,7 @@ mod test { ConsensusConstants, }, transactions::{ - tari_amount::{uT, MicroMinotari, T}, + tari_amount::{uT, MicroMinotari}, transaction_components::{OutputType, RangeProofType}, }, }; @@ -908,49 +919,6 @@ mod test { ConsensusConstants::mainnet(); } - // Comment out the feature flag to run this test - #[test] - #[cfg(feature = "schedule_get_constants")] - fn esmeralda_schedule_get_constants() { - let mut esmeralda = ConsensusConstants::esmeralda(); - loop { - let schedule = EmissionSchedule::new( - esmeralda[0].emission_initial, - esmeralda[0].emission_decay, - esmeralda[0].emission_tail, - ); - // No genesis block coinbase - assert_eq!(schedule.block_reward(0), MicroMinotari(0)); - // Coinbases starts at block 1 - let coinbase_offset = 1; - let first_reward = schedule.block_reward(coinbase_offset); - assert_eq!(first_reward, esmeralda[0].emission_initial * uT); - assert_eq!(schedule.supply_at_block(coinbase_offset), first_reward); - // Tail emission starts after block 3,255,552 + coinbase_offset - let mut rewards = schedule - .iter() - .skip(3_255_552 + usize::try_from(coinbase_offset).unwrap()); - let supply = loop { - let (block_num, reward, supply) = rewards.next().unwrap(); - let total_supply = supply + esmeralda[0].faucet_value; - println!( - "Initial: {}, Block: {}, Reward: {}, Supply: {}, Total supply: {}", - esmeralda[0].emission_initial, block_num, reward, supply, total_supply - ); - if reward == esmeralda[0].emission_tail { - break supply; - } - }; - let total_supply_up_to_tail_emission = supply + esmeralda[0].faucet_value; - if total_supply_up_to_tail_emission >= 21_000_000_800_000_000 * uT { - println!("Total supply up to tail emission: {}", total_supply_up_to_tail_emission); - break; - } - esmeralda[0].emission_initial = esmeralda[0].emission_initial + MicroMinotari(1); - } - panic!("\n\nThis test may not pass in CI\n\n"); - } - #[test] fn esmeralda_schedule() { let esmeralda = ConsensusConstants::esmeralda(); @@ -985,15 +953,15 @@ mod test { let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3255553 + coinbase_offset); assert_eq!(reward, 800_000_415 * uT); - assert_eq!(supply, 20_999_999_999_819_869 * uT); + assert_eq!(supply, 14_969_882_222_638_857 * uT); let (_, reward, _) = rewards.next().unwrap(); assert_eq!(reward, 799_999_715 * uT); // Inflating tail emission let mut rewards = schedule.iter().skip(3259845); let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3259846); - assert_eq!(reward, 797 * T); - assert_eq!(supply, 21_003_427_156_818_122 * uT); + assert_eq!(reward, 796_998_899.into()); + assert_eq!(supply, 14_973_309_379_635_607 * uT); } #[test] @@ -1027,8 +995,8 @@ mod test { let mut rewards = schedule.iter().skip(3259845); let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3259846); - assert_eq!(reward, 797 * T); - assert_eq!(supply, 21_003_427_156_818_122 * uT); + assert_eq!(reward, 796_998_899.into()); + assert_eq!(supply, 14_973_269_379_635_607 * uT); } #[test] @@ -1062,8 +1030,8 @@ mod test { let mut rewards = schedule.iter().skip(3259845); let (block_num, reward, supply) = rewards.next().unwrap(); assert_eq!(block_num, 3259846); - assert_eq!(reward, 797 * T); - assert_eq!(supply, 21_003_427_156_818_122 * uT); + assert_eq!(reward, 796_998_899.into()); + assert_eq!(supply, 14_973_269_379_635_607 * uT); } #[test] diff --git a/base_layer/core/src/consensus/consensus_encoding/hashing.rs b/base_layer/core/src/consensus/consensus_encoding/hashing.rs index 1eb42a460a..b3c4eacb91 100644 --- a/base_layer/core/src/consensus/consensus_encoding/hashing.rs +++ b/base_layer/core/src/consensus/consensus_encoding/hashing.rs @@ -71,7 +71,6 @@ impl Default for DomainSeparatedConsen mod tests { use blake2::Blake2b; use digest::consts::U32; - use tari_common::configuration::Network; use tari_crypto::hash_domain; use tari_script::script; diff --git a/base_layer/core/src/covenants/covenant.rs b/base_layer/core/src/covenants/covenant.rs index b9fc7ebf12..e010780f9d 100644 --- a/base_layer/core/src/covenants/covenant.rs +++ b/base_layer/core/src/covenants/covenant.rs @@ -194,7 +194,7 @@ mod test { #[tokio::test] async fn it_succeeds_when_empty() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let outputs = create_outputs(10, UtxoTestParams::default(), &key_manager).await; let input = create_input(&key_manager).await; let covenant = covenant!(); @@ -204,7 +204,7 @@ mod test { #[tokio::test] async fn it_executes_the_covenant() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut outputs = create_outputs(10, UtxoTestParams::default(), &key_manager).await; outputs[4].features.maturity = 42; outputs[5].features.maturity = 42; @@ -221,7 +221,7 @@ mod test { #[tokio::test] async fn test_borsh_de_serialization() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut outputs = create_outputs(10, UtxoTestParams::default(), &key_manager).await; outputs[4].features.maturity = 42; outputs[5].features.maturity = 42; diff --git a/base_layer/core/src/covenants/fields.rs b/base_layer/core/src/covenants/fields.rs index 480dd0a6b4..84ec22028a 100644 --- a/base_layer/core/src/covenants/fields.rs +++ b/base_layer/core/src/covenants/fields.rs @@ -401,7 +401,7 @@ mod test { #[tokio::test] async fn it_returns_true_if_eq() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let side_chain_features = make_sample_sidechain_feature(); let output = create_outputs( 1, @@ -444,7 +444,7 @@ mod test { #[tokio::test] async fn it_returns_false_if_not_eq() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let side_chain_features = make_sample_sidechain_feature(); let output = create_outputs( 1, @@ -495,7 +495,7 @@ mod test { #[tokio::test] async fn it_returns_true_if_eq_input() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let output = create_outputs( 1, UtxoTestParams { @@ -565,16 +565,14 @@ mod test { use crate::transactions::key_manager::create_memory_db_key_manager; mod construct_challenge_from { - use blake2::Digest; use digest::Update; - use tari_crypto::hashing::DomainSeparation; use super::*; use crate::transactions::{tari_amount::MicroMinotari, transaction_components::RangeProofType}; #[tokio::test] async fn it_constructs_challenge_using_consensus_encoding() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let features = OutputFeatures { maturity: 42, output_type: OutputType::Coinbase, @@ -618,15 +616,11 @@ mod test { mod get_field_value_ref { use super::*; - use crate::transactions::{ - key_manager::create_memory_db_key_manager, - tari_amount::MicroMinotari, - transaction_components::RangeProofType, - }; + use crate::transactions::{tari_amount::MicroMinotari, transaction_components::RangeProofType}; #[tokio::test] async fn it_retrieves_the_value_as_ref() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let features = OutputFeatures { maturity: 42, range_proof_type: RangeProofType::RevealedValue, diff --git a/base_layer/core/src/covenants/filters/absolute_height.rs b/base_layer/core/src/covenants/filters/absolute_height.rs index a01cfd3955..14a2b6be04 100644 --- a/base_layer/core/src/covenants/filters/absolute_height.rs +++ b/base_layer/core/src/covenants/filters/absolute_height.rs @@ -72,7 +72,7 @@ mod test { #[tokio::test] async fn it_filters_all_out_if_height_not_reached() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(absolute_height(@uint(100))); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 42, |_| {}, &key_manager).await; @@ -85,7 +85,7 @@ mod test { #[tokio::test] async fn it_filters_all_in_if_height_reached() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(absolute_height(@uint(100))); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 100, |_| {}, &key_manager).await; @@ -98,7 +98,7 @@ mod test { #[tokio::test] async fn it_filters_all_in_if_height_exceeded() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(absolute_height(@uint(42))); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 100, |_| {}, &key_manager).await; diff --git a/base_layer/core/src/covenants/filters/and.rs b/base_layer/core/src/covenants/filters/and.rs index 857ae2a5b0..846cc8f56d 100644 --- a/base_layer/core/src/covenants/filters/and.rs +++ b/base_layer/core/src/covenants/filters/and.rs @@ -53,7 +53,7 @@ mod test { #[tokio::test] async fn it_filters_outputset_using_intersection() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(101)); let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))); let input = create_input(&key_manager).await; diff --git a/base_layer/core/src/covenants/filters/field_eq.rs b/base_layer/core/src/covenants/filters/field_eq.rs index 51e512e177..7824d4bc31 100644 --- a/base_layer/core/src/covenants/filters/field_eq.rs +++ b/base_layer/core/src/covenants/filters/field_eq.rs @@ -90,7 +90,7 @@ mod test { #[tokio::test] async fn it_filters_uint() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(field_eq(@field::features_maturity, @uint(42))); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); @@ -108,7 +108,7 @@ mod test { #[tokio::test] async fn it_filters_sender_offset_public_key() { let pk = PublicKey::from_hex("5615a327e1d19da34e5aa8bbd2ecc97addf29b158844b885bfc4efa0dab17052").unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(field_eq( @field::sender_offset_public_key, @public_key(pk.clone()) @@ -128,7 +128,7 @@ mod test { #[tokio::test] async fn it_filters_commitment() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let commitment = Commitment::from_hex("7ca31ba517d8b563609ed6707fedde5a2be64ac1d67b254cb5348bc2f680557f").unwrap(); let covenant = covenant!(field_eq( @@ -151,7 +151,7 @@ mod test { #[tokio::test] async fn it_filters_tari_script() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(100)); let covenant = covenant!(field_eq( @field::script, @@ -173,7 +173,7 @@ mod test { #[tokio::test] async fn it_filters_covenant() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let next_cov = covenant!(and(identity(), or(field_eq(@field::features_maturity, @uint(42))))); let covenant = covenant!(field_eq(@field::covenant, @covenant(next_cov.clone()))); let input = create_input(&key_manager).await; @@ -192,7 +192,7 @@ mod test { #[tokio::test] async fn it_filters_output_type() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(field_eq(@field::features_output_type, @output_type(Coinbase))); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); @@ -210,7 +210,7 @@ mod test { #[tokio::test] async fn it_errors_if_field_has_an_incorrect_type() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(field_eq(@field::features, @uint(42))); let input = create_input(&key_manager).await; let mut context = create_context(&covenant, &input, 0); 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 c9c1fe40d7..d7502ac141 100644 --- a/base_layer/core/src/covenants/filters/fields_hashed_eq.rs +++ b/base_layer/core/src/covenants/filters/fields_hashed_eq.rs @@ -62,7 +62,7 @@ mod test { #[tokio::test] async fn it_filters_outputs_with_fields_that_hash_to_given_hash() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let features = OutputFeatures { maturity: 42, sidechain_feature: Some(make_sample_sidechain_feature()), diff --git a/base_layer/core/src/covenants/filters/fields_preserved.rs b/base_layer/core/src/covenants/filters/fields_preserved.rs index 06caf4881f..2d7dc6c92b 100644 --- a/base_layer/core/src/covenants/filters/fields_preserved.rs +++ b/base_layer/core/src/covenants/filters/fields_preserved.rs @@ -50,7 +50,7 @@ mod test { #[tokio::test] async fn it_filters_outputs_that_match_input_fields() { let covenant = covenant!(fields_preserved(@fields(@field::features_maturity, @field::features_output_type))); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut input = create_input(&key_manager).await; input.set_maturity(42).unwrap(); input.features_mut().unwrap().output_type = OutputType::ValidatorNodeRegistration; diff --git a/base_layer/core/src/covenants/filters/identity.rs b/base_layer/core/src/covenants/filters/identity.rs index 88d1e5c03d..13f85394ee 100644 --- a/base_layer/core/src/covenants/filters/identity.rs +++ b/base_layer/core/src/covenants/filters/identity.rs @@ -44,7 +44,7 @@ mod tests { #[tokio::test] async fn it_returns_the_outputset_unchanged() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let covenant = covenant!(identity()); let input = create_input(&key_manager).await; let (mut context, outputs) = setup_filter_test(&covenant, &input, 0, |_| {}, &key_manager).await; diff --git a/base_layer/core/src/covenants/filters/not.rs b/base_layer/core/src/covenants/filters/not.rs index 811b18854e..9f1d5017d5 100644 --- a/base_layer/core/src/covenants/filters/not.rs +++ b/base_layer/core/src/covenants/filters/not.rs @@ -50,7 +50,7 @@ mod test { #[tokio::test] async fn it_filters_compliment_of_filter() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(100)); let covenant = covenant!(not(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone()))))); let input = create_input(&key_manager).await; diff --git a/base_layer/core/src/covenants/filters/or.rs b/base_layer/core/src/covenants/filters/or.rs index 6401ce4bc3..6849b2c0b0 100644 --- a/base_layer/core/src/covenants/filters/or.rs +++ b/base_layer/core/src/covenants/filters/or.rs @@ -56,7 +56,7 @@ mod test { #[tokio::test] async fn it_filters_outputset_using_union() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(100)); let covenant = covenant!(or(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))); let input = create_input(&key_manager).await; diff --git a/base_layer/core/src/covenants/filters/output_hash_eq.rs b/base_layer/core/src/covenants/filters/output_hash_eq.rs index 82c02117b3..0a4f5542f9 100644 --- a/base_layer/core/src/covenants/filters/output_hash_eq.rs +++ b/base_layer/core/src/covenants/filters/output_hash_eq.rs @@ -52,7 +52,7 @@ mod test { #[tokio::test] async fn it_filters_output_with_specific_hash() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let output = create_outputs(1, Default::default(), &key_manager).await.remove(0); let output_hash = output.hash(); let mut hash = [0u8; 32]; diff --git a/base_layer/core/src/covenants/filters/xor.rs b/base_layer/core/src/covenants/filters/xor.rs index 5ffa78c50f..75d6ffc467 100644 --- a/base_layer/core/src/covenants/filters/xor.rs +++ b/base_layer/core/src/covenants/filters/xor.rs @@ -58,7 +58,7 @@ mod test { #[tokio::test] async fn it_filters_outputset_using_symmetric_difference() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(CheckHeight(100)); let covenant = covenant!(and(field_eq(@field::features_maturity, @uint(42),), field_eq(@field::script, @script(script.clone())))); let input = create_input(&key_manager).await; diff --git a/base_layer/core/src/iterators/chunk.rs b/base_layer/core/src/iterators/chunk.rs index 92d57ec402..8d771bba5d 100644 --- a/base_layer/core/src/iterators/chunk.rs +++ b/base_layer/core/src/iterators/chunk.rs @@ -20,7 +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. -use std::{cmp, fmt::Display}; +use std::{cmp, convert::TryFrom, fmt::Display}; /// Iterator that produces non-overlapping integer vectors. /// This is similar to `Vec::chunks` except it does not require a complete vector of integers to produce chunks @@ -98,7 +98,10 @@ macro_rules! non_overlapping_iter_impl { if self.current == self.end { return None; } - let size = self.size as $ty; + let size = match <$ty>::try_from(self.size) { + Ok(size) => size, + Err(_) => <$ty>::MAX, + }; match self.current.checked_add(size) { Some(next) => { let next = cmp::min(next, self.end); @@ -128,7 +131,10 @@ macro_rules! non_overlapping_iter_impl { return None; } - let size = self.size as $ty; + let size = match <$ty>::try_from(self.size) { + Ok(size) => size, + Err(_) => <$ty>::MAX, + }; // Is this the first iteration? if self.end == self.current_end { let rem = (self.end - self.current) % size; diff --git a/base_layer/core/src/mempool/mempool.rs b/base_layer/core/src/mempool/mempool.rs index 07f3d5c903..6bebd4b6f4 100644 --- a/base_layer/core/src/mempool/mempool.rs +++ b/base_layer/core/src/mempool/mempool.rs @@ -23,7 +23,7 @@ use std::sync::{Arc, RwLock}; use log::debug; -use tari_common_types::types::{PrivateKey, Signature}; +use tari_common_types::types::{FixedHash, PrivateKey, Signature}; use tokio::task; use crate::{ @@ -213,4 +213,8 @@ impl Mempool { }) .await? } + + pub async fn get_last_seen_hash(&self) -> Result { + self.with_read_access(|storage| Ok(storage.last_seen_hash)).await + } } diff --git a/base_layer/core/src/mempool/mempool_storage.rs b/base_layer/core/src/mempool/mempool_storage.rs index 554af9e2d6..e44c8189e1 100644 --- a/base_layer/core/src/mempool/mempool_storage.rs +++ b/base_layer/core/src/mempool/mempool_storage.rs @@ -23,7 +23,7 @@ use std::{sync::Arc, time::Instant}; use log::*; -use tari_common_types::types::{PrivateKey, Signature}; +use tari_common_types::types::{FixedHash, PrivateKey, Signature}; use tari_utilities::hex::Hex; use crate::{ @@ -57,6 +57,7 @@ pub struct MempoolStorage { validator: Box, rules: ConsensusManager, last_seen_height: u64, + pub(crate) last_seen_hash: FixedHash, } impl MempoolStorage { @@ -68,6 +69,7 @@ impl MempoolStorage { validator, rules, last_seen_height: 0, + last_seen_hash: Default::default(), } } @@ -220,6 +222,7 @@ impl MempoolStorage { self.reorg_pool.compact(); self.last_seen_height = published_block.header.height; + self.last_seen_hash = published_block.header.hash(); debug!(target: LOG_TARGET, "Compaction took {:.2?}", timer.elapsed()); match self.stats() { Ok(stats) => debug!(target: LOG_TARGET, "{}", stats), @@ -269,12 +272,13 @@ impl MempoolStorage { .remove_reorged_txs_and_discard_double_spends(removed_blocks, new_blocks); self.insert_txs(removed_txs) .map_err(|e| MempoolError::InternalError(e.to_string()))?; - if let Some(height) = new_blocks + if let Some((height, hash)) = new_blocks .last() .or_else(|| removed_blocks.first()) - .map(|block| block.header.height) + .map(|block| (block.header.height, block.header.hash())) { self.last_seen_height = height; + self.last_seen_hash = hash; } Ok(()) } diff --git a/base_layer/core/src/mempool/priority/prioritized_transaction.rs b/base_layer/core/src/mempool/priority/prioritized_transaction.rs index 0c78db88b7..f32501b004 100644 --- a/base_layer/core/src/mempool/priority/prioritized_transaction.rs +++ b/base_layer/core/src/mempool/priority/prioritized_transaction.rs @@ -35,7 +35,7 @@ use crate::transactions::{ }; /// Create a unique unspent transaction priority based on the transaction fee, maturity of the oldest input UTXO and the -/// excess_sig. The excess_sig is included to ensure the the priority key unique so it can be used with a BTreeMap. +/// excess_sig. The excess_sig is included to ensure the priority key unique so it can be used with a BTreeMap. /// Normally, duplicate keys will be overwritten in a BTreeMap. #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] pub struct FeePriority(Vec); @@ -144,7 +144,7 @@ mod tests { #[tokio::test] async fn fee_increases_priority() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let weighting = TransactionWeight::latest(); let epoch = u64::MAX / 2; let tx = create_tx_with_fee(2 * uT, &key_manager).await; @@ -158,7 +158,7 @@ mod tests { #[tokio::test] async fn age_increases_priority() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let weighting = TransactionWeight::latest(); let epoch = u64::MAX / 2; let tx = create_tx_with_fee(2 * uT, &key_manager).await; diff --git a/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs b/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs index b3b11e94c6..eab697378c 100644 --- a/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs +++ b/base_layer/core/src/mempool/reorg_pool/reorg_pool.rs @@ -349,7 +349,7 @@ mod test { #[tokio::test] async fn test_insert_expire_by_height() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let tx1 = Arc::new( tx!(MicroMinotari(100_000), fee: MicroMinotari(100), lock: 4000, inputs: 2, outputs: 1, &key_manager) .expect("Failed to get tx") @@ -409,7 +409,7 @@ mod test { #[tokio::test] async fn test_remove_all() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let tx1 = Arc::new( tx!(MicroMinotari(100_000), fee: MicroMinotari(100), lock: 4000, inputs: 2, outputs: 1, &key_manager) .expect("Failed to get tx") @@ -446,7 +446,7 @@ mod test { #[tokio::test] async fn remove_scan_for_and_remove_reorged_txs() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let network = Network::LocalNet; let consensus = ConsensusManagerBuilder::new(network).build().unwrap(); let tx1 = Arc::new( diff --git a/base_layer/core/src/mempool/sync_protocol/test.rs b/base_layer/core/src/mempool/sync_protocol/test.rs index 47b4719a86..b075649e36 100644 --- a/base_layer/core/src/mempool/sync_protocol/test.rs +++ b/base_layer/core/src/mempool/sync_protocol/test.rs @@ -61,7 +61,7 @@ use crate::{ }; pub async fn create_transactions(n: usize) -> Vec { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut transactions = Vec::new(); for _i in 0..n { let (transaction, _, _) = create_tx(5000 * uT, 3 * uT, 1, 2, 1, 3, Default::default(), &key_manager) 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 bb7edfce04..ee1ccf9d2f 100644 --- a/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs +++ b/base_layer/core/src/mempool/unconfirmed_pool/unconfirmed_pool.rs @@ -882,7 +882,7 @@ mod test { #[tokio::test] async fn test_find_duplicate_input() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let tx1 = Arc::new( tx!(MicroMinotari(5000), fee: MicroMinotari(50), inputs: 2, outputs: 1, &key_manager) .expect("Failed to get tx") @@ -911,7 +911,7 @@ mod test { #[tokio::test] async fn test_insert_and_retrieve_highest_priority_txs() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let tx1 = Arc::new( tx!(MicroMinotari(5_000), fee: MicroMinotari(5), inputs: 2, outputs: 1, &key_manager) .expect("Failed to get tx") @@ -974,7 +974,7 @@ mod test { #[tokio::test] async fn test_double_spend_inputs() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (tx1, _, _) = tx!(MicroMinotari(5_000), fee: MicroMinotari(10), inputs: 1, outputs: 1, &key_manager) .expect("Failed to get tx"); const INPUT_AMOUNT: MicroMinotari = MicroMinotari(5_000); @@ -991,7 +991,7 @@ mod test { TariScript::default(), ExecutionStack::default(), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ); @@ -1061,7 +1061,7 @@ mod test { #[tokio::test] async fn test_remove_reorg_txs() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let network = Network::LocalNet; let consensus = ConsensusManagerBuilder::new(network).build().unwrap(); let tx1 = Arc::new( @@ -1133,7 +1133,7 @@ mod test { #[tokio::test] async fn test_discard_double_spend_txs() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus = create_consensus_rules(); let tx1 = Arc::new( tx!(MicroMinotari(5_000), fee: MicroMinotari(5), inputs:2, outputs:1, &key_manager) @@ -1208,7 +1208,7 @@ mod test { #[tokio::test] async fn test_multiple_transactions_with_same_outputs_in_mempool() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (tx1, _, _) = tx!(MicroMinotari(150_000), fee: MicroMinotari(50), inputs:5, outputs:5, &key_manager) .expect("Failed to get tx"); let (tx2, _, _) = tx!(MicroMinotari(250_000), fee: MicroMinotari(50), inputs:5, outputs:5, &key_manager) @@ -1311,7 +1311,7 @@ mod test { #[tokio::test] async fn it_compiles_correct_stats_for_single_block() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (tx1, _, _) = tx!(MicroMinotari(150_000), fee: MicroMinotari(5), inputs:5, outputs:1, &key_manager) .expect("Failed to get tx"); let (tx2, _, _) = tx!(MicroMinotari(250_000), fee: MicroMinotari(5), inputs:5, outputs:5, &key_manager) @@ -1341,7 +1341,7 @@ mod test { #[tokio::test] async fn it_compiles_correct_stats_for_multiple_blocks() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let expected_stats = [ FeePerGramStat { order: 0, diff --git a/base_layer/core/src/proof_of_work/difficulty.rs b/base_layer/core/src/proof_of_work/difficulty.rs index dc9ec85baf..db9ef47fda 100644 --- a/base_layer/core/src/proof_of_work/difficulty.rs +++ b/base_layer/core/src/proof_of_work/difficulty.rs @@ -132,32 +132,6 @@ impl CheckedAdd for Difficulty { } } -/// This trait is used to add a type to `CheckedSub`, which greatly simplifies usage in the code. -/// It is implemented for `Difficulty` and `u64`. -pub trait CheckedSub { - fn checked_sub(&self, other: T) -> Option - where Self: Sized; -} - -impl CheckedSub for Difficulty { - fn checked_sub(&self, other: Difficulty) -> Option { - if let Some(val) = self.0.checked_sub(other.0) { - if val < MIN_DIFFICULTY { - return None; - } - Some(Difficulty(val)) - } else { - None - } - } -} - -impl CheckedSub for Difficulty { - fn checked_sub(&self, other: u64) -> Option { - self.checked_sub(Difficulty(other)) - } -} - impl From for u64 { fn from(value: Difficulty) -> Self { value.0 @@ -191,7 +165,7 @@ mod test { use primitive_types::U256; use crate::proof_of_work::{ - difficulty::{CheckedAdd, CheckedSub, MIN_DIFFICULTY}, + difficulty::{CheckedAdd, MIN_DIFFICULTY}, Difficulty, }; #[test] @@ -237,14 +211,6 @@ mod test { assert!(d2.checked_add(1).is_none()); } - #[test] - fn subtraction_does_not_underflow() { - let d1 = Difficulty::from_u64(100).unwrap(); - assert!(d1.checked_sub(1).is_some()); - let d2 = Difficulty::max(); - assert!(d1.checked_sub(d2).is_none()); - } - #[test] fn be_high_target() { let target: &[u8] = &[ diff --git a/base_layer/core/src/proof_of_work/monero_rx/error.rs b/base_layer/core/src/proof_of_work/monero_rx/error.rs index 1517ff9f0f..18e8fd242e 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/error.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/error.rs @@ -24,7 +24,11 @@ use tari_utilities::hex::HexError; use crate::{ common::{BanPeriod, BanReason}, - proof_of_work::{randomx_factory::RandomXVMFactoryError, DifficultyError}, + proof_of_work::{ + monero_rx::merkle_tree_parameters::MerkleTreeParametersError, + randomx_factory::RandomXVMFactoryError, + DifficultyError, + }, }; /// Errors that can occur when merging Monero PoW data with Tari PoW data @@ -50,6 +54,8 @@ pub enum MergeMineError { DifficultyError(#[from] DifficultyError), #[error("Cannot mine with 0 aux chains")] ZeroAuxChains, + #[error("Merkle Tree Parameters error: {0}")] + MerkleTreeParamsError(#[from] MerkleTreeParametersError), } impl MergeMineError { @@ -66,7 +72,9 @@ impl MergeMineError { reason: err.to_string(), ban_duration: BanPeriod::Long, }), - MergeMineError::RandomXVMFactoryError(_) | MergeMineError::ZeroAuxChains => None, + MergeMineError::RandomXVMFactoryError(_) | + MergeMineError::ZeroAuxChains | + MergeMineError::MerkleTreeParamsError(_) => None, } } } 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 dc4aea7399..a64471b847 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 @@ -117,7 +117,7 @@ pub fn verify_header( let mut is_found = false; let mut already_seen_mmfield = false; for item in extra_field.0 { - if let SubField::MergeMining(Some(depth), merge_mining_hash) = item { + if let SubField::MergeMining(depth, merge_mining_hash) = item { if already_seen_mmfield { return Err(MergeMineError::ValidationError( "More than one merge mining tag found in coinbase".to_string(), @@ -162,19 +162,21 @@ fn check_aux_chains( } } let merkle_tree_params = MerkleTreeParameters::from_varint(merge_mining_params); - if merkle_tree_params.number_of_chains == 0 { + if merkle_tree_params.number_of_chains() == 0 { return false; } let hash_position = U256::from_little_endian( &Sha256::new() .chain_update(tari_genesis_block_hash) - .chain_update(merkle_tree_params.aux_nonce.to_le_bytes()) + .chain_update(merkle_tree_params.aux_nonce().to_le_bytes()) .chain_update((109_u8).to_le_bytes()) .finalize(), ) .low_u32() % - u32::from(merkle_tree_params.number_of_chains); - let (merkle_root, pos) = monero_data.aux_chain_merkle_proof.calculate_root_with_pos(&t_hash); + u32::from(merkle_tree_params.number_of_chains()); + let (merkle_root, pos) = monero_data + .aux_chain_merkle_proof + .calculate_root_with_pos(&t_hash, merkle_tree_params.number_of_chains()); if hash_position != pos { return false; } @@ -327,7 +329,7 @@ pub fn insert_aux_chain_mr_and_info_into_block>( // Adding more than one merge mining tag is not allowed for item in &extra_field.0 { - if let SubField::MergeMining(Some(_), _) = item { + if let SubField::MergeMining(_, _) = item { return Err(MergeMineError::ValidationError( "More than one merge mining tag in coinbase not allowed".to_string(), )); @@ -342,13 +344,10 @@ pub fn insert_aux_chain_mr_and_info_into_block>( let encoded = if aux_chain_count == 1 { VarInt(0) } else { - let mt_params = MerkleTreeParameters { - number_of_chains: aux_chain_count, - aux_nonce, - }; + let mt_params = MerkleTreeParameters::new(aux_chain_count, aux_nonce)?; mt_params.to_varint() }; - extra_field.0.insert(0, SubField::MergeMining(Some(encoded), hash)); + extra_field.0.insert(0, SubField::MergeMining(encoded, hash)); debug!(target: LOG_TARGET, "Inserted extra field: {:?}", extra_field); block.miner_tx.prefix.extra = extra_field.into(); @@ -385,9 +384,8 @@ mod test { use borsh::BorshSerialize; use monero::{ - blockdata::transaction::{ExtraField, TxOutTarget}, + blockdata::transaction::TxOutTarget, consensus::deserialize, - cryptonote::hash::Hashable, util::ringct::{RctSig, RctSigBase, RctType}, Hash, PublicKey, @@ -397,7 +395,6 @@ mod test { TxOut, }; use tari_common::configuration::Network; - use tari_common_types::types::FixedHash; use tari_test_utils::unpack_enum; use tari_utilities::{ epoch_time::EpochTime, @@ -406,7 +403,7 @@ mod test { }; use super::*; - use crate::proof_of_work::{monero_rx::fixed_array::FixedByteArray, PowAlgorithm, ProofOfWork}; + use crate::proof_of_work::{PowAlgorithm, ProofOfWork}; // This tests checks the hash of monero-rs #[test] @@ -901,7 +898,7 @@ mod test { // like trying to sneek it in. Later on, when we call `verify_header(&block_header)`, it should fail. let mut extra_field = ExtraField::try_parse(&block.miner_tx.prefix.extra).unwrap(); let hash = monero::Hash::from_slice(hash.as_ref()); - extra_field.0.insert(0, SubField::MergeMining(Some(VarInt(0)), hash)); + extra_field.0.insert(0, SubField::MergeMining(VarInt(0), hash)); block.miner_tx.prefix.extra = extra_field.into(); // Trying to extract the Tari hash will fail because there are more than one merge mining tag @@ -1009,7 +1006,7 @@ mod test { let extra_field_after_tag = ExtraField::try_parse(&block.miner_tx.prefix.extra.clone()).unwrap(); assert_eq!( &format!( - "ExtraField([MergeMining(Some(0), 0x{}), \ + "ExtraField([MergeMining(0, 0x{}), \ TxPublicKey(06225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa4782), Nonce([246, 58, 168, \ 109, 46, 133, 127, 7])])", hex::encode(hash) @@ -1267,7 +1264,7 @@ mod test { assert!(res.is_err()); let field = res.unwrap_err(); let mm_tag = SubField::MergeMining( - Some(VarInt(0)), + VarInt(0), Hash::from_slice( hex::decode("9505c642ae2771f344caddde740ad1c238f7fc17f81c2c515b2cd6d3f2030c46") .unwrap() 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 9eaeede7b7..faaeeba537 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 @@ -196,31 +196,58 @@ impl MerkleProof { } /// Calculates the merkle root hash from the provide Monero hash - pub fn calculate_root(&self, hash: &Hash) -> Hash { - self.calculate_root_with_pos(hash).0 + pub fn calculate_root_with_pos(&self, hash: &Hash, aux_chain_count: u8) -> (Hash, u32) { + let root = self.calculate_root(hash); + let pos = self.get_position_from_path(u32::from(aux_chain_count)); + (root, pos) } - pub fn calculate_root_with_pos(&self, hash: &Hash) -> (Hash, u32) { + pub fn calculate_root(&self, hash: &Hash) -> Hash { if self.branch.is_empty() { - return (*hash, 0); + return *hash; } let mut root = *hash; let depth = self.branch.len(); - let mut pos = 0; - let mut multiplier = 1; for d in 0..depth { if (self.path_bitmap >> (depth - d - 1)) & 1 > 0 { root = cn_fast_hash2(&self.branch[d], &root); } else { root = cn_fast_hash2(&root, &self.branch[d]); - pos += multiplier; } - // this cant overflow as the max depth is 32, and 2^32 == u32::MAX - multiplier *= 2; } - (root, pos) + root + } + + pub fn get_position_from_path(&self, aux_chain_count: u32) -> u32 { + if aux_chain_count <= 1 { + return 0; + } + + let mut depth = 0; + let mut k = 1; + + while k < aux_chain_count { + depth += 1; + k <<= 1; + } + + k -= aux_chain_count; + + let mut pos = 0; + let mut path = self.path_bitmap; + + for _i in 1..depth { + pos = (pos << 1) | (path & 1); + path >>= 1; + } + + if pos < k { + return pos; + } + + (((pos - k) << 1) | (path & 1)) + k } } diff --git a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs index fc785315d1..575ad117e3 100644 --- a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree_parameters.rs @@ -20,20 +20,39 @@ // 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::{cmp::min, convert::TryFrom}; +use std::convert::TryFrom; use monero::VarInt; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize, Eq)] +pub enum MerkleTreeParametersError { + #[error("Cannot have zero chains")] + NumberOfChainZero, +} // This is based on https://github.com/SChernykh/p2pool/blob/merge-mining/docs/MERGE_MINING.MD#merge-mining-tx_extra-tag-format #[derive(Debug, Clone, PartialEq)] pub struct MerkleTreeParameters { - pub number_of_chains: u8, - pub aux_nonce: u32, + number_of_chains: u8, + aux_nonce: u32, } impl MerkleTreeParameters { + pub fn new(number_of_chains: u8, aux_nonce: u32) -> Result { + if number_of_chains == 0u8 { + return Err(MerkleTreeParametersError::NumberOfChainZero); + } + Ok(MerkleTreeParameters { + number_of_chains, + aux_nonce, + }) + } + pub fn from_varint(merkle_tree_varint: VarInt) -> MerkleTreeParameters { - let bits = get_decode_bits(merkle_tree_varint.0) + 1; + let bits = get_decode_bits(merkle_tree_varint.0); + let number_of_chains = get_aux_chain_count(merkle_tree_varint.0, bits); let aux_nonce = get_aux_nonce(merkle_tree_varint.0, bits); MerkleTreeParameters { @@ -43,23 +62,35 @@ impl MerkleTreeParameters { } pub fn to_varint(&self) -> VarInt { - let size = u8::try_from(self.number_of_chains.leading_zeros()) + // 1 is encoded as 0 + let num = self.number_of_chains.saturating_sub(1); + let size = u8::try_from(num.leading_zeros()) .expect("This cant fail, u8 can only have 8 leading 0's which will fit in 255"); - let mut size_bits = encode_bits(7 - size); - let mut n_bits = encode_aux_chain_count(self.number_of_chains, 8 - size); + // size must be greater than 0, so saturating sub should be safe. + let mut size_bits = encode_bits(7u8.saturating_sub(size)); + let mut n_bits = encode_aux_chain_count(self.number_of_chains); let mut nonce_bits = encode_aux_nonce(self.aux_nonce); // this wont underflow as max size will be size_bits(3) + n_bits(8) + nonce_bits(32) = 43 let mut zero_bits = vec![0; 64 - size_bits.len() - n_bits.len() - nonce_bits.len()]; - size_bits.append(&mut n_bits); - size_bits.append(&mut nonce_bits); - size_bits.append(&mut zero_bits); - let num: u64 = size_bits.iter().fold(0, |result, &bit| (result << 1) ^ u64::from(bit)); + zero_bits.append(&mut nonce_bits); + zero_bits.append(&mut n_bits); + zero_bits.append(&mut size_bits); + + let num: u64 = zero_bits.iter().fold(0, |result, &bit| (result << 1) ^ u64::from(bit)); VarInt(num) } + + pub fn number_of_chains(&self) -> u8 { + self.number_of_chains + } + + pub fn aux_nonce(&self) -> u32 { + self.aux_nonce + } } fn get_decode_bits(num: u64) -> u8 { - let bits_num: Vec = (61..=63).rev().map(|n| ((num >> n) & 1) as u8).collect(); + let bits_num: Vec = (0..=2).rev().map(|n| ((num >> n) & 1) as u8).collect(); bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit) } @@ -68,18 +99,27 @@ fn encode_bits(num: u8) -> Vec { } fn get_aux_chain_count(num: u64, bits: u8) -> u8 { - let start = 60 - min(8, bits) + 1; - let bits_num: Vec = (start..=60).rev().map(|n| ((num >> n) & 1) as u8).collect(); - bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit) + let end = 3 + bits; + let bits_num: Vec = (3..=end).rev().map(|n| ((num >> n) & 1) as u8).collect(); + (bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit)).saturating_add(1) } -fn encode_aux_chain_count(num: u8, bit_length: u8) -> Vec { +fn encode_aux_chain_count(num: u8) -> Vec { + // 1 is encoded as 0 + let num = num.saturating_sub(1); + if num == 0 { + return vec![0]; + } + let size = u8::try_from(num.leading_zeros()) + .expect("This cant fail, u8 can only have 8 leading 0's which will fit in 255"); + let bit_length = 8 - size; (0..bit_length).rev().map(|n| (num >> n) & 1).collect() } fn get_aux_nonce(num: u64, bits: u8) -> u32 { - let start = 60 - min(8, u32::from(bits)) + 1 - 32; - let end = 60 - min(8, u32::from(bits)); + // 0,1,2 is storing bits, then amount of bits, then start at next bit to read + let start = 3 + bits + 1; + let end = start + 32; let bits_num: Vec = (start..=end).rev().map(|n| ((num >> n) & 1) as u32).collect(); bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit) } @@ -90,39 +130,51 @@ fn encode_aux_nonce(num: u32) -> Vec { #[cfg(test)] mod test { - use crate::proof_of_work::monero_rx::merkle_tree_parameters::{ - encode_aux_chain_count, - encode_aux_nonce, - encode_bits, - get_aux_chain_count, - get_aux_nonce, - get_decode_bits, + use monero::VarInt; + + use crate::proof_of_work::monero_rx::{ + merkle_tree_parameters::{ + encode_aux_chain_count, + encode_aux_nonce, + encode_bits, + get_aux_chain_count, + get_aux_nonce, + get_decode_bits, + }, + MerkleTreeParameters, }; #[test] fn en_decode_bits_test() { + let num = 24u64; // 11000 + let bit = get_decode_bits(num); + assert_eq!(bit, 0); + let bits = encode_bits(0); + let array = vec![0, 0, 0]; + assert_eq!(bits, array); + let num = 0b1100000000000000000000000000000000000000000000000000000000000101; let bit = get_decode_bits(num); - assert_eq!(bit, 6); - let bits = encode_bits(6); - let array = vec![1, 1, 0]; + assert_eq!(bit, 5); + let bits = encode_bits(5); + let array = vec![1, 0, 1]; assert_eq!(bits, array); - let num = 0b0100000000000000000000000000000000000000000000000000000000000101; + let num = 0b0100000000000000000000000000000000000000000000000000000000000110; let bit = get_decode_bits(num); - assert_eq!(bit, 2); - let bits = encode_bits(2); - let array = vec![0, 1, 0]; + assert_eq!(bit, 6); + let bits = encode_bits(6); + let array = vec![1, 1, 0]; assert_eq!(bits, array); - let num = 0b1110000000000000000000000000000000000000000000000000000000000101; + let num = 0b1010000000000000000000000000000000000000000000000000000000000111; let bit = get_decode_bits(num); assert_eq!(bit, 7); let bits = encode_bits(7); let array = vec![1, 1, 1]; assert_eq!(bits, array); - let num = 0b0011000000000000000000000000000000000000000000000000000000000101; + let num = 0b0011000000000000000000000000000000000000000000000000000000000001; let bit = get_decode_bits(num); assert_eq!(bit, 1); let bits = encode_bits(1); @@ -132,104 +184,154 @@ mod test { #[test] fn get_decode_aux_chain_test() { - let num = 0b1101111111100000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 8); + let num = 24u64; // 11000 + let aux_number = get_aux_chain_count(num, 0); + assert_eq!(aux_number, 2); + let bits = encode_aux_chain_count(2); + let array: Vec = vec![1]; + assert_eq!(bits, array); + + let num = 0b1101111111100000000000000000000000000000000000000000011111110000; + let aux_number = get_aux_chain_count(num, 7); assert_eq!(aux_number, 255); - let bits = encode_aux_chain_count(255, 8); - let array = vec![1, 1, 1, 1, 1, 1, 1, 1]; + let bits = encode_aux_chain_count(255); + let array = vec![1, 1, 1, 1, 1, 1, 1, 0]; assert_eq!(bits, array); - let num = 0b1100000000100000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 8); - assert_eq!(aux_number, 1); - let bits = encode_aux_chain_count(1, 8); - let array = vec![0, 0, 0, 0, 0, 0, 0, 1]; + let num = 0b1100000000100000000000000000000000000000000000000000000000101101; + let aux_number = get_aux_chain_count(num, 3); + assert_eq!(aux_number, 6); + let bits = encode_aux_chain_count(6); + let array = vec![1, 0, 1]; assert_eq!(bits, array); - let num = 0b1100000000000000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 8); - assert_eq!(aux_number, 0); - let bits = encode_aux_chain_count(0, 8); - let array = vec![0, 0, 0, 0, 0, 0, 0, 0]; + let num = 0b1100000000000000000000000000000000000000000000000000000000011101; + let aux_number = get_aux_chain_count(num, 2); + assert_eq!(aux_number, 4); + let bits = encode_aux_chain_count(4); + let array = vec![1, 1]; assert_eq!(bits, array); let num = 0b1100111000000000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 8); - assert_eq!(aux_number, 112); - let bits = encode_aux_chain_count(112, 8); - let array = vec![0, 1, 1, 1, 0, 0, 0, 0]; + let aux_number = get_aux_chain_count(num, 1); + assert_eq!(aux_number, 1); + let bits = encode_aux_chain_count(1); + let array = vec![0]; assert_eq!(bits, array); - let num = 0b1100000100000000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 8); + let num = 0b1100000100000000000000000000000000000000000000000000000000111101; + let aux_number = get_aux_chain_count(num, 3); assert_eq!(aux_number, 8); - let bits = encode_aux_chain_count(8, 8); - let array = vec![0, 0, 0, 0, 1, 0, 0, 0]; + let bits = encode_aux_chain_count(8); + let array = vec![1, 1, 1]; + assert_eq!(bits, array); + + let num = 0b1100000001000000000000000000000000000000000000000000000001111101; + let aux_number = get_aux_chain_count(num, 4); + assert_eq!(aux_number, 16); + let bits = encode_aux_chain_count(16); + let array = vec![1, 1, 1, 1]; assert_eq!(bits, array); - let num = 0b1100000001000000000000000000000000000000000000000000000000000101; + let num = 0b1100000010000000000000000000000000000000000000000000001111000101; let aux_number = get_aux_chain_count(num, 7); - assert_eq!(aux_number, 1); - let bits = encode_aux_chain_count(1, 7); - let array = vec![0, 0, 0, 0, 0, 0, 1]; + assert_eq!(aux_number, 121); + let bits = encode_aux_chain_count(121); + let array = vec![1, 1, 1, 1, 0, 0, 0]; assert_eq!(bits, array); - let num = 0b1100000010000000000000000000000000000000000000000000000000000101; + let num = 0b1100000100000000000000000000000000000000000000000000001100000101; + let aux_number = get_aux_chain_count(num, 7); + assert_eq!(aux_number, 97); + let bits = encode_aux_chain_count(97); + let array = vec![1, 1, 0, 0, 0, 0, 0]; + assert_eq!(bits, array); + + let num = 0b1111000110000000000000000000000000000000000000000000000111000101; let aux_number = get_aux_chain_count(num, 6); + assert_eq!(aux_number, 57); + let bits = encode_aux_chain_count(57); + let array = vec![1, 1, 1, 0, 0, 0]; + assert_eq!(bits, array); + } + + #[test] + #[allow(clippy::too_many_lines)] + fn get_decode_aux_nonce_test() { + let num = 24u64; // 11000 + let aux_number = get_aux_nonce(num, 0); assert_eq!(aux_number, 1); - let bits = encode_aux_chain_count(1, 6); - let array = vec![0, 0, 0, 0, 0, 1]; + let bits = encode_aux_nonce(1); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; assert_eq!(bits, array); - let num = 0b1100000100000000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 5); + let num = 0b1100000000110000000000000000000000000000000000000000100000000101; + let aux_number = get_aux_nonce(num, 7); assert_eq!(aux_number, 1); - let bits = encode_aux_chain_count(1, 5); - let array = vec![0, 0, 0, 0, 1]; + let bits = encode_aux_nonce(1); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; assert_eq!(bits, array); - let num = 0b1100000110000000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 5); + let num = 0b1100000000110000000000000000000000000000000000000000010000000101; + let aux_number = get_aux_nonce(num, 6); assert_eq!(aux_number, 1); + let bits = encode_aux_nonce(1); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + assert_eq!(bits, array); - let num = 0b1111000110000000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_chain_count(num, 1); + let num = 0b1100000000110000000000000000000000000000000000000000001000000101; + let aux_number = get_aux_nonce(num, 5); assert_eq!(aux_number, 1); - let bits = encode_aux_chain_count(1, 1); - let array = vec![1]; + let bits = encode_aux_nonce(1); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; assert_eq!(bits, array); - } - #[test] - fn get_decode_aux_nonce_test() { - let num = 0b1100000000110000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_nonce(num, 8); - assert_eq!(aux_number, 2147483648); - let bits = encode_aux_nonce(2147483648); + let num = 0b1100000000110000000000000000000000000000000000000000000100000101; + let aux_number = get_aux_nonce(num, 4); + assert_eq!(aux_number, 1); + let bits = encode_aux_nonce(1); let array = vec![ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ]; assert_eq!(bits, array); - let num = 0b1100000000011111111111111111111111111111111000000000000000000101; - let aux_number = get_aux_nonce(num, 8); - assert_eq!(aux_number, u32::MAX); - let bits = encode_aux_nonce(u32::MAX); + let num = 0b1100000000110000000000000000000000000000000000000000000010000101; + let aux_number = get_aux_nonce(num, 3); + assert_eq!(aux_number, 1); + let bits = encode_aux_nonce(1); let array = vec![ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ]; assert_eq!(bits, array); - let num = 0b1100000000111111111111111111111111111111110000000000000000000101; - let aux_number = get_aux_nonce(num, 7); - assert_eq!(aux_number, u32::MAX); + let num = 0b1100000000110000000000000000000000000000000000000000000001000101; + let aux_number = get_aux_nonce(num, 2); + assert_eq!(aux_number, 1); + let bits = encode_aux_nonce(1); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + assert_eq!(bits, array); - let num = 0b1100111111111111111111111111111111110000000000000000000000000101; + let num = 0b1100000000110000000000000000000000000000000000000000000000100101; let aux_number = get_aux_nonce(num, 1); - assert_eq!(aux_number, u32::MAX); + assert_eq!(aux_number, 1); + let bits = encode_aux_nonce(1); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + ]; + assert_eq!(bits, array); - let num = 0b1100000000100000000000000000000000000000001000000000000000000101; - let aux_number = get_aux_nonce(num, 8); + let num = 0b1100000000110000000000000000000000000000000000000000000000010101; + let aux_number = get_aux_nonce(num, 0); assert_eq!(aux_number, 1); let bits = encode_aux_nonce(1); let array = vec![ @@ -237,14 +339,61 @@ mod test { ]; assert_eq!(bits, array); - let num = 0b1100000000100000000000000000000000000000000000000000000000000101; - let aux_number = get_aux_nonce(num, 8); + let num = 0b1100000000110000000000000000000000000000000000000000010000000101; + let aux_number = get_aux_nonce(num, 7); assert_eq!(aux_number, 0); let bits = encode_aux_nonce(0); let array = vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; assert_eq!(bits, array); + + let num = 0b1100000000110000000001111111111111111111111111111111110000000101; + let aux_number = get_aux_nonce(num, 7); + assert_eq!(aux_number, u32::MAX); + let bits = encode_aux_nonce(u32::MAX); + let array = vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]; + assert_eq!(bits, array); + + let num = 0b1100000000110000000001111111111100011111111111111111110000000101; + let aux_number = get_aux_nonce(num, 7); + assert_eq!(aux_number, 4293132287); + let bits = encode_aux_nonce(4293132287); + let array = vec![ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]; + assert_eq!(bits, array); + + let num = 0b1100000000110000000001010101010101010101010101010101010000000101; + let aux_number = get_aux_nonce(num, 7); + assert_eq!(aux_number, 2863311530); + let bits = encode_aux_nonce(2863311530); + let array = vec![ + 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, + ]; + assert_eq!(bits, array); + + let num = 0b110000000011000000000000000000000000011110011110111010000000101; + let aux_number = get_aux_nonce(num, 7); + assert_eq!(aux_number, 31214); + let bits = encode_aux_nonce(31214); + let array = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, + ]; + assert_eq!(bits, array); + } + + #[test] + fn merkle_complete() { + let num = VarInt(24); + let merkle_tree_params = MerkleTreeParameters::from_varint(num); + assert_eq!(merkle_tree_params.aux_nonce, 1); + assert_eq!(merkle_tree_params.number_of_chains, 2); + + let ser_num = merkle_tree_params.to_varint(); + assert_eq!(ser_num, VarInt(24)); } mod quicktest { diff --git a/base_layer/core/src/proof_of_work/proof_of_work.rs b/base_layer/core/src/proof_of_work/proof_of_work.rs index 036a9be61e..963b0b6682 100644 --- a/base_layer/core/src/proof_of_work/proof_of_work.rs +++ b/base_layer/core/src/proof_of_work/proof_of_work.rs @@ -29,6 +29,7 @@ use tari_utilities::hex::Hex; use crate::proof_of_work::PowAlgorithm; +#[allow(dead_code)] pub trait AchievedDifficulty {} /// The proof of work data structure that is included in the block header. There's some non-Rustlike redundancy here diff --git a/base_layer/core/src/proto/block.proto b/base_layer/core/src/proto/block.proto index db062262bb..9636958120 100644 --- a/base_layer/core/src/proto/block.proto +++ b/base_layer/core/src/proto/block.proto @@ -73,9 +73,9 @@ message NewBlock { // The block header. BlockHeader header = 1; // Coinbase kernel of the block. - tari.types.TransactionKernel coinbase_kernel = 2; + repeated tari.types.TransactionKernel coinbase_kernels = 2; // Coinbase output of the block. - tari.types.TransactionOutput coinbase_output = 3; + repeated tari.types.TransactionOutput coinbase_outputs = 3; // The scalar `s` component of the kernel excess signatures of the transactions contained in the block. repeated bytes kernel_excess_sigs = 4; } diff --git a/base_layer/core/src/proto/block.rs b/base_layer/core/src/proto/block.rs index 8118d1f083..b891538799 100644 --- a/base_layer/core/src/proto/block.rs +++ b/base_layer/core/src/proto/block.rs @@ -184,16 +184,18 @@ impl TryFrom for NewBlock { type Error = String; fn try_from(new_block: proto::NewBlock) -> Result { + let mut coinbase_kernels = Vec::new(); + for coinbase_kernel in new_block.coinbase_kernels { + coinbase_kernels.push(coinbase_kernel.try_into()?) + } + let mut coinbase_outputs = Vec::new(); + for coinbase_output in new_block.coinbase_outputs { + coinbase_outputs.push(coinbase_output.try_into()?) + } Ok(Self { header: new_block.header.ok_or("No new block header provided")?.try_into()?, - coinbase_kernel: new_block - .coinbase_kernel - .ok_or("No coinbase kernel given")? - .try_into()?, - coinbase_output: new_block - .coinbase_output - .ok_or("No coinbase kernel given")? - .try_into()?, + coinbase_kernels, + coinbase_outputs, kernel_excess_sigs: new_block .kernel_excess_sigs .iter() @@ -208,10 +210,18 @@ impl TryFrom for proto::NewBlock { type Error = String; fn try_from(new_block: NewBlock) -> Result { + let mut coinbase_kernels = Vec::new(); + for coinbase_kernel in new_block.coinbase_kernels { + coinbase_kernels.push(coinbase_kernel.into()) + } + let mut coinbase_outputs = Vec::new(); + for coinbase_output in new_block.coinbase_outputs { + coinbase_outputs.push(coinbase_output.try_into()?) + } Ok(Self { header: Some(new_block.header.into()), - coinbase_kernel: Some(new_block.coinbase_kernel.into()), - coinbase_output: Some(new_block.coinbase_output.try_into()?), + coinbase_kernels, + coinbase_outputs, kernel_excess_sigs: new_block.kernel_excess_sigs.into_iter().map(|s| s.to_vec()).collect(), }) } diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index 255401feda..28b312ebda 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -452,7 +452,7 @@ pub async fn create_chained_blocks>( let mut block_hashes = HashMap::new(); block_hashes.insert("GB".to_string(), genesis_block); let rules = ConsensusManager::builder(Network::LocalNet).build().unwrap(); - let km = create_memory_db_key_manager(); + let km = create_memory_db_key_manager().unwrap(); let blocks: BlockSpecs = blocks.into(); let mut block_names = Vec::with_capacity(blocks.len()); let (script_key_id, wallet_payment_address) = default_coinbase_entities(&km).await; @@ -565,7 +565,7 @@ impl TestBlockchain { .try_into_chain_block() .map(Arc::new) .unwrap(); - let km = create_memory_db_key_manager(); + let km = create_memory_db_key_manager().unwrap(); let (script_key_id, wallet_payment_address) = default_coinbase_entities(&km).await; let mut blockchain = Self { db, diff --git a/base_layer/core/src/test_helpers/mod.rs b/base_layer/core/src/test_helpers/mod.rs index d5c54e5135..9a3fcc2c1e 100644 --- a/base_layer/core/src/test_helpers/mod.rs +++ b/base_layer/core/src/test_helpers/mod.rs @@ -48,7 +48,7 @@ use crate::{ generate_coinbase_with_wallet_output, key_manager::{MemoryDbKeyManager, TariKeyId}, tari_amount::MicroMinotari, - transaction_components::{RangeProofType, Transaction, WalletOutput}, + transaction_components::{encrypted_data::PaymentId, RangeProofType, Transaction, WalletOutput}, }, }; @@ -73,9 +73,15 @@ pub fn create_orphan_block(block_height: u64, transactions: Vec, co } pub async fn default_coinbase_entities(key_manager: &MemoryDbKeyManager) -> (TariKeyId, TariAddress) { - let wallet_private_key = PrivateKey::random(&mut OsRng); - let script_key_id = key_manager.import_key(wallet_private_key.clone()).await.unwrap(); - let wallet_payment_address = TariAddress::new(PublicKey::from_secret_key(&wallet_private_key), Network::LocalNet); + let wallet_private_spend_key = PrivateKey::random(&mut OsRng); + let wallet_private_view_key = PrivateKey::random(&mut OsRng); + let _key = key_manager.import_key(wallet_private_view_key.clone()).await.unwrap(); + let script_key_id = key_manager.import_key(wallet_private_spend_key.clone()).await.unwrap(); + let wallet_payment_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&wallet_private_view_key), + PublicKey::from_secret_key(&wallet_private_spend_key), + Network::LocalNet, + ); (script_key_id, wallet_payment_address) } @@ -115,6 +121,7 @@ pub async fn create_block( false, rules.consensus_constants(header.height), range_proof_type.unwrap_or(RangeProofType::BulletProofPlus), + PaymentId::Empty, ) .await .unwrap(); diff --git a/base_layer/core/src/transactions/aggregated_body.rs b/base_layer/core/src/transactions/aggregated_body.rs index 47148fb116..fbc0fc6fa6 100644 --- a/base_layer/core/src/transactions/aggregated_body.rs +++ b/base_layer/core/src/transactions/aggregated_body.rs @@ -27,8 +27,9 @@ use std::{ use borsh::{BorshDeserialize, BorshSerialize}; use log::*; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{Commitment, PrivateKey}; +use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey}; use tari_crypto::commitment::HomomorphicCommitmentFactory; +use tari_utilities::hex::Hex; use crate::transactions::{ crypto_factories::CryptoFactories, @@ -104,11 +105,46 @@ impl AggregateBody { &self.inputs } + /// Update an existing transaction input's script signature (found by matching commitment) + pub fn update_script_signature( + &mut self, + commitment: &Commitment, + script_signature: ComAndPubSignature, + ) -> Result<(), TransactionError> { + let input = self + .inputs + .iter_mut() + .find(|input| match input.commitment() { + Ok(c) => c == commitment, + Err(_) => false, + }) + .ok_or(TransactionError::OutputNotFound(commitment.to_hex()))?; + input.script_signature = script_signature; + + Ok(()) + } + /// Provide read-only access to the output list pub fn outputs(&self) -> &Vec { &self.outputs } + /// Update an existing transaction output's metadata signature (found by matching commitment) + pub fn update_metadata_signature( + &mut self, + commitment: &Commitment, + metadata_signature: ComAndPubSignature, + ) -> Result<(), TransactionError> { + let output = self + .outputs + .iter_mut() + .find(|output| &output.commitment == commitment) + .ok_or(TransactionError::OutputNotFound(commitment.to_hex()))?; + output.metadata_signature = metadata_signature; + + Ok(()) + } + /// Provide read-only access to the kernel list pub fn kernels(&self) -> &Vec { &self.kernels @@ -429,7 +465,7 @@ impl Display for AggregateBody { #[cfg(test)] mod test { - use tari_common_types::types::{ComAndPubSignature, Commitment, FixedHash, PublicKey, Signature}; + use tari_common_types::types::{FixedHash, PublicKey, Signature}; use tari_script::{ExecutionStack, TariScript}; use super::*; diff --git a/base_layer/core/src/transactions/coinbase_builder.rs b/base_layer/core/src/transactions/coinbase_builder.rs index ead4800baa..d9ad3d5cbd 100644 --- a/base_layer/core/src/transactions/coinbase_builder.rs +++ b/base_layer/core/src/transactions/coinbase_builder.rs @@ -21,15 +21,13 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -use chacha20poly1305::aead::OsRng; use log::*; use tari_common_types::{ tari_address::TariAddress, - types::{Commitment, PrivateKey, PublicKey}, + types::{Commitment, PrivateKey}, }; -use tari_crypto::keys::PublicKey as PK; use tari_key_manager::key_manager_service::{KeyManagerInterface, KeyManagerServiceError}; -use tari_script::{one_sided_payment_script, stealth_payment_script, ExecutionStack, TariScript}; +use tari_script::{push_pubkey_script, ExecutionStack, TariScript}; use tari_utilities::ByteArrayError; use thiserror::Error; @@ -40,7 +38,6 @@ use crate::{ }, covenants::Covenant, one_sided::{ - diffie_hellman_stealth_domain_hasher, shared_secret_to_output_encryption_key, shared_secret_to_output_spending_key, stealth_address_script_spending_key, @@ -56,6 +53,7 @@ use crate::{ }, tari_amount::{uT, MicroMinotari}, transaction_components::{ + encrypted_data::PaymentId, KernelBuilder, KernelFeatures, OutputFeatures, @@ -91,8 +89,8 @@ pub enum CoinbaseBuildError { MissingScript, #[error("The range proof type for this coinbase transaction wasn't provided")] MissingRangeProofType, - #[error("The wallet public key for this coinbase transaction wasn't provided")] - MissingWalletPublicKey, + #[error("The wallet public view key for this coinbase transaction wasn't provided")] + MissingWalletPublicViewKey, #[error("The encryption key for this coinbase transaction wasn't provided")] MissingEncryptionKey, #[error("The sender offset key for this coinbase transaction wasn't provided")] @@ -137,7 +135,7 @@ pub struct CoinbaseBuilder { key_manager: TKeyManagerInterface, block_height: Option, fees: Option, - spend_key_id: Option, + commitment_mask_key_id: Option, script_key_id: Option, encryption_key_id: Option, sender_offset_key_id: Option, @@ -157,7 +155,7 @@ where TKeyManagerInterface: TransactionKeyManagerInterface key_manager, block_height: None, fees: None, - spend_key_id: None, + commitment_mask_key_id: None, script_key_id: None, encryption_key_id: None, sender_offset_key_id: None, @@ -180,9 +178,9 @@ where TKeyManagerInterface: TransactionKeyManagerInterface self } - /// Provides the spend key ID for this transaction. This will usually be provided by a miner's wallet instance. - pub fn with_spend_key_id(mut self, key: TariKeyId) -> Self { - self.spend_key_id = Some(key); + /// Provides the commitment mask key ID for this transaction. + pub fn with_commitment_mask_id(mut self, key: TariKeyId) -> Self { + self.commitment_mask_key_id = Some(key); self } @@ -241,10 +239,11 @@ where TKeyManagerInterface: TransactionKeyManagerInterface self, constants: &ConsensusConstants, emission_schedule: &EmissionSchedule, + payment_id: PaymentId, ) -> Result<(Transaction, WalletOutput), CoinbaseBuildError> { let height = self.block_height.ok_or(CoinbaseBuildError::MissingBlockHeight)?; let reward = emission_schedule.block_reward(height); - self.build_with_reward(constants, reward).await + self.build_with_reward(constants, reward, payment_id).await } /// Try and construct a Coinbase Transaction while specifying the block reward. The other parameters (keys, nonces @@ -257,11 +256,12 @@ where TKeyManagerInterface: TransactionKeyManagerInterface self, constants: &ConsensusConstants, block_reward: MicroMinotari, + payment_id: PaymentId, ) -> Result<(Transaction, WalletOutput), CoinbaseBuildError> { // gets tx details let height = self.block_height.ok_or(CoinbaseBuildError::MissingBlockHeight)?; let total_reward = block_reward + self.fees.ok_or(CoinbaseBuildError::MissingFees)?; - let spending_key_id = self.spend_key_id.ok_or(CoinbaseBuildError::MissingSpendKey)?; + let commitment_mask_key_id = self.commitment_mask_key_id.ok_or(CoinbaseBuildError::MissingSpendKey)?; let script_key_id = self.script_key_id.ok_or(CoinbaseBuildError::MissingScriptKey)?; let encryption_key_id = self.encryption_key_id.ok_or(CoinbaseBuildError::MissingEncryptionKey)?; let sender_offset_key_id = self @@ -282,20 +282,23 @@ where TKeyManagerInterface: TransactionKeyManagerInterface &metadata.kernel_features, &metadata.burn_commitment, ); - let (public_nonce_id, public_nonce) = self + let public_nonce = self .key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await?; - let public_spend_key = self.key_manager.get_public_key_at_key_id(&spending_key_id).await?; + let public_commitment_mask_key = self + .key_manager + .get_public_key_at_key_id(&commitment_mask_key_id) + .await?; let kernel_signature = self .key_manager .get_partial_txo_kernel_signature( - &spending_key_id, - &public_nonce_id, - &public_nonce, - &public_spend_key, + &commitment_mask_key_id, + &public_nonce.key_id, + &public_nonce.pub_key, + &public_commitment_mask_key, &kernel_version, &kernel_message, &metadata.kernel_features, @@ -303,14 +306,19 @@ where TKeyManagerInterface: TransactionKeyManagerInterface ) .await?; - let excess = Commitment::from_public_key(&public_spend_key); + let excess = Commitment::from_public_key(&public_commitment_mask_key); // generate tx details let value: u64 = total_reward.into(); let output_features = OutputFeatures::create_coinbase(height + constants.coinbase_min_maturity(), self.extra, range_proof_type); let encrypted_data = self .key_manager - .encrypt_data_for_recovery(&spending_key_id, Some(&encryption_key_id), total_reward.into()) + .encrypt_data_for_recovery( + &commitment_mask_key_id, + Some(&encryption_key_id), + total_reward.into(), + payment_id.clone(), + ) .await?; let minimum_value_promise = match range_proof_type { RangeProofType::BulletProofPlus => MicroMinotari::zero(), @@ -332,7 +340,7 @@ where TKeyManagerInterface: TransactionKeyManagerInterface let metadata_sig = self .key_manager .get_metadata_signature( - &spending_key_id, + &commitment_mask_key_id, &value.into(), &sender_offset_key_id, &output_version, @@ -344,7 +352,7 @@ where TKeyManagerInterface: TransactionKeyManagerInterface let wallet_output = WalletOutput::new( output_version, total_reward, - spending_key_id, + commitment_mask_key_id, output_features, script, ExecutionStack::default(), @@ -355,6 +363,7 @@ where TKeyManagerInterface: TransactionKeyManagerInterface covenant, encrypted_data, minimum_value_promise, + payment_id, &self.key_manager, ) .await?; @@ -399,6 +408,7 @@ pub async fn generate_coinbase( stealth_payment: bool, consensus_constants: &ConsensusConstants, range_proof_type: RangeProofType, + payment_id: PaymentId, ) -> Result<(TransactionOutput, TransactionKernel), CoinbaseBuildError> { // The script key is not used in the Diffie-Hellmann protocol, so we assign default. let script_key_id = TariKeyId::default(); @@ -413,6 +423,7 @@ pub async fn generate_coinbase( stealth_payment, consensus_constants, range_proof_type, + payment_id, ) .await?; Ok((coinbase_output, coinbase_kernel)) @@ -431,40 +442,51 @@ pub async fn generate_coinbase_with_wallet_output( stealth_payment: bool, consensus_constants: &ConsensusConstants, range_proof_type: RangeProofType, + payment_id: PaymentId, ) -> Result<(Transaction, TransactionOutput, TransactionKernel, WalletOutput), CoinbaseBuildError> { - let (sender_offset_key_id, _) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await?; let shared_secret = key_manager - .get_diffie_hellman_shared_secret(&sender_offset_key_id, wallet_payment_address.public_key()) + .get_diffie_hellman_shared_secret( + &sender_offset.key_id, + wallet_payment_address + .public_view_key() + .ok_or(CoinbaseBuildError::MissingWalletPublicViewKey)?, + ) .await?; - let spending_key = shared_secret_to_output_spending_key(&shared_secret)?; + let commitment_mask = shared_secret_to_output_spending_key(&shared_secret)?; let encryption_private_key = shared_secret_to_output_encryption_key(&shared_secret)?; let encryption_key_id = key_manager.import_key(encryption_private_key).await?; - let spending_key_id = key_manager.import_key(spending_key).await?; + let commitment_mask_key_id = key_manager.import_key(commitment_mask).await?; - let script = if stealth_payment { - let (nonce_private_key, nonce_public_key) = PublicKey::random_keypair(&mut OsRng); - let c = diffie_hellman_stealth_domain_hasher(&nonce_private_key, wallet_payment_address.public_key()); - let script_spending_key = stealth_address_script_spending_key(&c, wallet_payment_address.public_key()); - stealth_payment_script(&nonce_public_key, &script_spending_key) + let script_spending_pubkey = if stealth_payment { + let c = key_manager + .get_diffie_hellman_stealth_domain_hasher( + &sender_offset.key_id, + wallet_payment_address + .public_view_key() + .ok_or(CoinbaseBuildError::MissingWalletPublicViewKey)?, + ) + .await?; + stealth_address_script_spending_key(&c, wallet_payment_address.public_spend_key()) } else { - one_sided_payment_script(wallet_payment_address.public_key()) + wallet_payment_address.public_spend_key().clone() }; - + let script = push_pubkey_script(&script_spending_pubkey); let (transaction, wallet_output) = CoinbaseBuilder::new(key_manager.clone()) .with_block_height(height) .with_fees(fee) - .with_spend_key_id(spending_key_id) + .with_commitment_mask_id(commitment_mask_key_id) .with_encryption_key_id(encryption_key_id) - .with_sender_offset_key_id(sender_offset_key_id) + .with_sender_offset_key_id(sender_offset.key_id) .with_script_key_id(script_key_id.clone()) .with_script(script) .with_extra(extra.to_vec()) .with_range_proof_type(range_proof_type) - .build_with_reward(consensus_constants, reward) + .build_with_reward(consensus_constants, reward, payment_id) .await?; let output = transaction @@ -509,7 +531,7 @@ mod test { ) { let network = Network::LocalNet; let rules = ConsensusManagerBuilder::new(network).build().unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let factories = CryptoFactories::default(); (CoinbaseBuilder::new(key_manager.clone()), rules, factories, key_manager) } @@ -520,7 +542,11 @@ mod test { assert_eq!( builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty + ) .await .unwrap_err(), CoinbaseBuildError::MissingBlockHeight @@ -533,7 +559,11 @@ mod test { let builder = builder.with_block_height(42); assert_eq!( builder - .build(rules.consensus_constants(42), rules.emission_schedule(),) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty + ) .await .unwrap_err(), CoinbaseBuildError::MissingFees @@ -548,7 +578,11 @@ mod test { let builder = builder.with_block_height(42).with_fees(fees); assert_eq!( builder - .build(rules.consensus_constants(42), rules.emission_schedule(),) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty + ) .await .unwrap_err(), CoinbaseBuildError::MissingSpendKey @@ -563,21 +597,25 @@ mod test { let builder = builder .with_block_height(42) .with_fees(145 * uT) - .with_spend_key_id(p.spend_key_id.clone()) + .with_commitment_mask_id(p.commitment_mask_key_id.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx, _unblinded_output) = builder - .build(rules.consensus_constants(42), rules.emission_schedule()) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let utxo = &tx.body.outputs()[0]; let block_reward = rules.emission_schedule().block_reward(42) + 145 * uT; let commitment = key_manager - .get_commitment(&p.spend_key_id, &block_reward.into()) + .get_commitment(&p.commitment_mask_key_id, &block_reward.into()) .await .unwrap(); assert_eq!(&commitment, utxo.commitment()); @@ -614,14 +652,18 @@ mod test { let builder = builder .with_block_height(42) .with_fees(145 * uT) - .with_spend_key_id(p.spend_key_id) + .with_commitment_mask_id(p.commitment_mask_key_id) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (mut tx, _) = builder - .build(rules.consensus_constants(42), rules.emission_schedule()) + .build( + rules.consensus_constants(42), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let mut outputs = tx.body.outputs().clone(); @@ -649,14 +691,18 @@ mod test { let builder = builder .with_block_height(42) .with_fees(1 * uT) - .with_spend_key_id(p.spend_key_id.clone()) + .with_commitment_mask_id(p.commitment_mask_key_id.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (mut tx, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let block_reward = rules.emission_schedule().block_reward(42) + missing_fee; @@ -664,14 +710,18 @@ mod test { let builder = builder .with_block_height(4_200_000) .with_fees(1 * uT) - .with_spend_key_id(p.spend_key_id.clone()) + .with_commitment_mask_id(p.commitment_mask_key_id.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (tx2, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let mut coinbase2 = tx2.body.outputs()[0].clone(); @@ -696,14 +746,18 @@ mod test { let builder = builder .with_block_height(42) .with_fees(missing_fee) - .with_spend_key_id(p.spend_key_id) + .with_commitment_mask_id(p.commitment_mask_key_id) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::BulletProofPlus); let (tx3, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); assert!(tx3 @@ -717,7 +771,7 @@ mod test { .is_ok()); } use tari_key_manager::key_manager_service::KeyManagerInterface; - use tari_script::one_sided_payment_script; + use tari_script::push_pubkey_script; use crate::transactions::{ aggregated_body::AggregateBody, @@ -729,7 +783,7 @@ mod test { TransactionKeyManagerInterface, TxoStage, }, - transaction_components::{KernelBuilder, RangeProofType, TransactionKernelVersion}, + transaction_components::{encrypted_data::PaymentId, KernelBuilder, RangeProofType, TransactionKernelVersion}, }; #[tokio::test] @@ -746,14 +800,18 @@ mod test { let builder = builder .with_block_height(42) .with_fees(1 * uT) - .with_spend_key_id(p.spend_key_id.clone()) + .with_commitment_mask_id(p.commitment_mask_key_id.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (mut tx, _) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); @@ -763,14 +821,18 @@ mod test { let builder = builder .with_block_height(4200000) .with_fees(1 * uT) - .with_spend_key_id(p.spend_key_id.clone()) + .with_commitment_mask_id(p.commitment_mask_key_id.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx2, output) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); let mut tx_kernel_test = tx.clone(); @@ -781,7 +843,7 @@ mod test { let mut coinbase_kernel2 = tx2.body.kernels()[0].clone(); assert!(coinbase_kernel2.is_coinbase()); coinbase_kernel2.features = KernelFeatures::empty(); - let (new_nonce, nonce) = key_manager + let new_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); @@ -793,14 +855,14 @@ mod test { &None, ); let excess = key_manager - .get_txo_kernel_signature_excess_with_offset(&output.spending_key_id, &new_nonce) + .get_txo_kernel_signature_excess_with_offset(&output.spending_key_id, &new_nonce.key_id) .await .unwrap(); let sig = key_manager .get_partial_txo_kernel_signature( &output.spending_key_id, - &new_nonce, - &nonce, + &new_nonce.key_id, + &new_nonce.pub_key, &excess, &TransactionKernelVersion::get_current_version(), &kernel_message, @@ -811,12 +873,12 @@ mod test { .unwrap(); // we verify that the created signature is correct let offset = key_manager - .get_txo_private_kernel_offset(&output.spending_key_id, &new_nonce) + .get_txo_private_kernel_offset(&output.spending_key_id, &new_nonce.key_id) .await .unwrap(); let sig_challenge = TransactionKernel::finalize_kernel_signature_challenge( &TransactionKernelVersion::get_current_version(), - &nonce, + &new_nonce.pub_key, &excess, &kernel_message, ); @@ -878,14 +940,18 @@ mod test { let builder = builder .with_block_height(42) .with_fees(1 * uT) - .with_spend_key_id(p.spend_key_id.clone()) + .with_commitment_mask_id(p.commitment_mask_key_id.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id.clone()) .with_script_key_id(p.script_key_id.clone()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx1, wo1) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); @@ -895,14 +961,18 @@ mod test { let builder = builder .with_block_height(4200000) .with_fees(1 * uT) - .with_spend_key_id(p.spend_key_id.clone()) + .with_commitment_mask_id(p.commitment_mask_key_id.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(p.sender_offset_key_id) .with_script_key_id(p.script_key_id) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue); let (tx2, wo2) = builder - .build(rules.consensus_constants(0), rules.emission_schedule()) + .build( + rules.consensus_constants(0), + rules.emission_schedule(), + PaymentId::Empty, + ) .await .unwrap(); @@ -927,15 +997,15 @@ mod test { body1.verify_kernel_signatures().unwrap_err(); // lets create a new kernel with a correct signature - let (new_nonce1, nonce1) = key_manager + let new_nonce1 = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); - let (new_nonce2, nonce2) = key_manager + let new_nonce2 = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); - let nonce = &nonce1 + &nonce2; + let nonce = &new_nonce1.pub_key + &new_nonce2.pub_key; let kernel_message = TransactionKernel::build_kernel_signature_message( &TransactionKernelVersion::get_current_version(), kernel_1.fee, @@ -947,7 +1017,7 @@ mod test { let mut kernel_signature = key_manager .get_partial_txo_kernel_signature( &wo1.spending_key_id, - &new_nonce1, + &new_nonce1.key_id, &nonce, excess.as_public_key(), &TransactionKernelVersion::get_current_version(), @@ -961,7 +1031,7 @@ mod test { &key_manager .get_partial_txo_kernel_signature( &wo2.spending_key_id, - &new_nonce2, + &new_nonce2.key_id, &nonce, excess.as_public_key(), &TransactionKernelVersion::get_current_version(), diff --git a/base_layer/core/src/transactions/crypto_factories.rs b/base_layer/core/src/transactions/crypto_factories.rs index c2cc0f9841..7b1e560f50 100644 --- a/base_layer/core/src/transactions/crypto_factories.rs +++ b/base_layer/core/src/transactions/crypto_factories.rs @@ -31,7 +31,7 @@ impl CryptoFactories { /// /// ## Parameters /// - /// * `max_proof_range`: Sets the the maximum value in range proofs, where `max = 2^max_proof_range` + /// * `max_proof_range`: Sets the maximum value in range proofs, where `max = 2^max_proof_range` pub fn new(max_proof_range: usize) -> Self { Self { commitment: Arc::new(CommitmentFactory::default()), diff --git a/base_layer/core/src/transactions/key_manager/error.rs b/base_layer/core/src/transactions/key_manager/error.rs index c26a7f618d..ef92873c28 100644 --- a/base_layer/core/src/transactions/key_manager/error.rs +++ b/base_layer/core/src/transactions/key_manager/error.rs @@ -20,10 +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 serde::{Deserialize, Serialize}; use tari_crypto::signatures::CommitmentAndPublicKeySignatureError; use tari_key_manager::error::KeyManagerError; -use tari_utilities::ByteArrayError; use thiserror::Error; use crate::transactions::transaction_components::TransactionError; @@ -43,35 +41,3 @@ impl From for CoreKeyManagerError { CoreKeyManagerError::CommitmentAndPublicKeySignatureError(err.to_string()) } } - -/// Ledger device errors. -#[derive(Debug, PartialEq, Error, Deserialize, Serialize, Clone, Eq)] -pub enum LedgerDeviceError { - /// HID API error - #[error("HID API error `{0}`")] - HidApi(String), - /// Native HID transport error - #[error("Native HID transport error `{0}`")] - NativeTransport(String), - /// Ledger application not started - #[error("Ledger application not started")] - ApplicationNotStarted, - /// Ledger application instruction error - #[error("Ledger application instruction error `{0}`")] - Instruction(String), - /// Ledger application processing error - #[error("Processing error `{0}`")] - Processing(String), - /// Conversion error to or from ledger - #[error("Conversion failed: {0}")] - ByteArrayError(String), - /// Not yet supported - #[error("Ledger is not fully supported")] - NotSupported, -} - -impl From for LedgerDeviceError { - fn from(e: ByteArrayError) -> Self { - LedgerDeviceError::ByteArrayError(e.to_string()) - } -} diff --git a/base_layer/core/src/transactions/key_manager/initializer.rs b/base_layer/core/src/transactions/key_manager/initializer.rs index 55c6824cbe..3b2c4be68c 100644 --- a/base_layer/core/src/transactions/key_manager/initializer.rs +++ b/base_layer/core/src/transactions/key_manager/initializer.rs @@ -80,7 +80,7 @@ where T: KeyManagerBackend + 'static self.master_seed.clone(), KeyManagerDatabase::new(backend), self.crypto_factories.clone(), - self.wallet_type, + self.wallet_type.clone(), )?; context.register_handle(key_manager); diff --git a/base_layer/core/src/transactions/key_manager/inner.rs b/base_layer/core/src/transactions/key_manager/inner.rs index 94bbe955c2..d0a41d34ac 100644 --- a/base_layer/core/src/transactions/key_manager/inner.rs +++ b/base_layer/core/src/transactions/key_manager/inner.rs @@ -19,21 +19,20 @@ // 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. -#[cfg(feature = "ledger")] -use std::sync::{Arc, Mutex}; use std::{collections::HashMap, ops::Shl}; use blake2::Blake2b; use digest::consts::U64; -#[cfg(feature = "ledger")] -use ledger_transport::APDUCommand; -#[cfg(feature = "ledger")] -use ledger_transport_hid::TransportNativeHID; use log::*; #[cfg(feature = "ledger")] -use once_cell::sync::Lazy; +use minotari_ledger_wallet_comms::{ + error::LedgerDeviceError, + ledger_wallet::{get_transport, Instruction, LedgerCommands}, +}; use rand::rngs::OsRng; use strum::IntoEnumIterator; +#[cfg(feature = "ledger")] +use tari_common_types::wallet_types::LedgerWallet; use tari_common_types::{ types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, Signature}, wallet_types::WalletType, @@ -42,7 +41,6 @@ use tari_comms::types::CommsDHKE; use tari_crypto::{ commitment::{ExtensionDegree, HomomorphicCommitmentFactory}, extended_range_proof::ExtendedRangeProofService, - hash_domain, hashing::{DomainSeparatedHash, DomainSeparatedHasher}, keys::{PublicKey as PublicKeyTrait, SecretKey}, range_proof::RangeProofService as RPService, @@ -51,34 +49,37 @@ use tari_crypto::{ RistrettoComSig, }, }; +use tari_hashing::KeyManagerTransactionsHashDomain; use tari_key_manager::{ cipher_seed::CipherSeed, key_manager::KeyManager, key_manager_service::{ storage::database::{KeyManagerBackend, KeyManagerDatabase, KeyManagerState}, AddResult, + KeyAndId, KeyDigest, KeyId, KeyManagerServiceError, }, }; -use tari_utilities::{hex::Hex, ByteArray}; +use tari_script::CheckSigSchnorrSignature; +use tari_utilities::ByteArray; use tokio::sync::RwLock; -const LOG_TARGET: &str = "key_manager::key_manager_service"; -const KEY_MANAGER_MAX_SEARCH_DEPTH: u64 = 1_000_000; +const LOG_TARGET: &str = "c::bn::key_manager::key_manager_service"; +const TRANSACTION_KEY_MANAGER_MAX_SEARCH_DEPTH: u64 = 1_000_000; use crate::{ common::ConfidentialOutputHasher, one_sided::diffie_hellman_stealth_domain_hasher, transactions::{ key_manager::{ - interface::{TransactionKeyManagerBranch, TxoStage}, - LedgerDeviceError, + interface::{TransactionKeyManagerBranch, TransactionKeyManagerLabel, TxoStage}, TariKeyId, }, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, KernelFeatures, RangeProofType, @@ -94,12 +95,6 @@ use crate::{ }, }; -hash_domain!( - KeyManagerHashingDomain, - "com.tari.base_layer.core.transactions.key_manager", - 1 -); - pub struct TransactionKeyManagerInner { key_managers: HashMap>>, db: KeyManagerDatabase, @@ -108,9 +103,6 @@ pub struct TransactionKeyManagerInner { wallet_type: WalletType, } -#[cfg(feature = "ledger")] -pub static TRANSPORT: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(None))); - impl TransactionKeyManagerInner where TBackend: KeyManagerBackend + 'static { @@ -170,23 +162,33 @@ where TBackend: KeyManagerBackend + 'static Ok(result) } - pub async fn get_next_key(&self, branch: &str) -> Result<(TariKeyId, PublicKey), KeyManagerServiceError> { - let mut km = self - .key_managers - .get(branch) - .ok_or(KeyManagerServiceError::UnknownKeyBranch)? - .write() - .await; - self.db.increment_key_index(branch)?; - let index = km.increment_key_index(1); - let key = km.derive_public_key(index)?.key; - Ok(( - KeyId::Managed { - branch: branch.to_string(), - index, - }, - key, - )) + pub async fn get_next_key(&self, branch: &str) -> Result, KeyManagerServiceError> { + let index = { + let mut km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .write() + .await; + self.db.increment_key_index(branch)?; + km.increment_key_index(1) + }; + let key_id = KeyId::Managed { + branch: branch.to_string(), + index, + }; + let key = self.get_public_key_at_key_id(&key_id).await?; + Ok(KeyAndId { key_id, pub_key: key }) + } + + pub async fn get_random_key(&self) -> Result, KeyManagerServiceError> { + let random_private_key = PrivateKey::random(&mut OsRng); + let key_id = self.import_key(random_private_key).await?; + let public_key = self.get_public_key_at_key_id(&key_id).await?; + Ok(KeyAndId { + key_id, + pub_key: public_key, + }) } pub async fn get_static_key(&self, branch: &str) -> Result { @@ -202,6 +204,51 @@ where TBackend: KeyManagerBackend + 'static pub async fn get_public_key_at_key_id(&self, key_id: &TariKeyId) -> Result { match key_id { KeyId::Managed { branch, index } => { + // If we have the unique case of being a ledger wallet, and the key is a Managed EphemeralNonce, or + // SenderOffset than we fetch from the ledger, all other keys are fetched below. + #[allow(unused_variables)] + if let WalletType::Ledger(ledger) = &self.wallet_type { + if branch == &TransactionKeyManagerBranch::SenderOffsetLedger.get_branch_key() { + #[cfg(not(feature = "ledger"))] + { + return Err(KeyManagerServiceError::LedgerError( + "Ledger is not supported".to_string(), + )); + } + + #[cfg(feature = "ledger")] + { + let transport = + get_transport().map_err(|e| KeyManagerServiceError::LedgerError(e.to_string()))?; + let mut data = index.to_le_bytes().to_vec(); + let branch_u8 = TransactionKeyManagerBranch::from_key(branch).as_byte(); + data.extend_from_slice(&u64::from(branch_u8).to_le_bytes()); + let command = ledger.build_command(Instruction::GetPublicKey, data); + + match command.execute_with_transport(&transport) { + Ok(result) => { + debug!(target: LOG_TARGET, "result length: {}, data: {:?}", result.data().len(), result.data()); + if result.data().len() < 33 { + debug!(target: LOG_TARGET, "result less than 33"); + } + + return PublicKey::from_canonical_bytes(&result.data()[1..33]) + .map_err(|e| KeyManagerServiceError::LedgerError(e.to_string())); + }, + Err(e) => return Err(KeyManagerServiceError::LedgerError(e.to_string())), + } + } + } + + if &TransactionKeyManagerBranch::DataEncryption.get_branch_key() == branch { + let view_key = ledger + .view_key + .clone() + .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible)?; + return Ok(PublicKey::from_secret_key(&view_key)); + } + } + let km = self .key_managers .get(branch) @@ -210,44 +257,269 @@ where TBackend: KeyManagerBackend + 'static .await; Ok(km.derive_public_key(*index)?.key) }, + KeyId::Derived { branch, label, index } => { + let public_alpha = self.get_spend_key().await?.pub_key; + let km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let branch_key = km.get_private_key(*index)?; + let hasher = Self::get_domain_hasher(label)?; + let hasher = hasher.chain(branch_key.as_bytes()).finalize(); + let private_key = PrivateKey::from_uniform_bytes(hasher.as_ref()).map_err(|_| { + KeyManagerServiceError::UnknownError( + "Invalid private key for sender offset private key".to_string(), + ) + })?; + let public_key = PublicKey::from_secret_key(&private_key); + let public_key = public_alpha + &public_key; + Ok(public_key) + }, KeyId::Imported { key } => Ok(key.clone()), KeyId::Zero => Ok(PublicKey::default()), } } - pub async fn get_next_spend_and_script_key_ids( + pub(crate) async fn get_private_key(&self, key_id: &TariKeyId) -> Result { + match key_id { + KeyId::Managed { branch, index } => { + match &self.wallet_type { + WalletType::DerivedKeys => {}, + WalletType::Ledger(wallet) => { + if &TransactionKeyManagerBranch::DataEncryption.get_branch_key() == branch { + return wallet + .view_key + .clone() + .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible); + } + + // If we're trying to access any of the private keys, just say no bueno + if &TransactionKeyManagerBranch::Spend.get_branch_key() == branch || + &TransactionKeyManagerBranch::SenderOffset.get_branch_key() == branch + { + return Err(KeyManagerServiceError::LedgerPrivateKeyInaccessible); + } + }, + WalletType::ProvidedKeys(wallet) => { + if &TransactionKeyManagerBranch::DataEncryption.get_branch_key() == branch { + return Ok(wallet.view_key.clone()); + } + + // If we're trying to access any of the private keys, just say no bueno + if &TransactionKeyManagerBranch::Spend.get_branch_key() == branch { + return wallet + .private_spend_key + .clone() + .ok_or(KeyManagerServiceError::ImportedPrivateKeyInaccessible); + } + }, + } + + let km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let key = km.get_private_key(*index)?; + Ok(key) + }, + KeyId::Derived { branch, label, index } => match &self.wallet_type { + WalletType::Ledger(_) => Err(KeyManagerServiceError::LedgerPrivateKeyInaccessible), + WalletType::DerivedKeys => { + let km = self + .key_managers + .get(&TransactionKeyManagerBranch::Spend.get_branch_key()) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let private_alpha = km.get_private_key(0)?; + let km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let branch_key = km.get_private_key(*index)?; + let hasher = Self::get_domain_hasher(label)?; + let hasher = hasher.chain(branch_key.as_bytes()).finalize(); + let private_key = PrivateKey::from_uniform_bytes(hasher.as_ref()).map_err(|_| { + KeyManagerServiceError::UnknownError(format!("Invalid private key for {}", label)) + })?; + let private_key = private_key + private_alpha; + Ok(private_key) + }, + WalletType::ProvidedKeys(wallet) => { + let private_alpha = wallet + .private_spend_key + .clone() + .ok_or(KeyManagerServiceError::ImportedPrivateKeyInaccessible)?; + + let km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let branch_key = km.get_private_key(*index)?; + let hasher = Self::get_domain_hasher(label)?; + let hasher = hasher.chain(branch_key.as_bytes()).finalize(); + let private_key = PrivateKey::from_uniform_bytes(hasher.as_ref()).map_err(|_| { + KeyManagerServiceError::UnknownError(format!("Invalid private key for {}", label)) + })?; + let private_key = private_key + private_alpha; + Ok(private_key) + }, + }, + KeyId::Imported { key } => { + let pvt_key = self.db.get_imported_key(key)?; + Ok(pvt_key) + }, + KeyId::Zero => Ok(PrivateKey::default()), + } + } + + pub async fn get_view_key(&self) -> Result, KeyManagerServiceError> { + let key_id = KeyId::Managed { + branch: TransactionKeyManagerBranch::DataEncryption.get_branch_key(), + index: 0, + }; + let key = PublicKey::from_secret_key(&self.get_private_view_key().await?); + Ok(KeyAndId { key_id, pub_key: key }) + } + + pub async fn get_spend_key(&self) -> Result, KeyManagerServiceError> { + let key_id = KeyId::Managed { + branch: TransactionKeyManagerBranch::Spend.get_branch_key(), + index: 0, + }; + + let key = match &self.wallet_type { + WalletType::DerivedKeys => { + let private_key = self.get_private_key(&key_id).await?; + PublicKey::from_secret_key(&private_key) + }, + WalletType::Ledger(ledger) => ledger.public_alpha.clone().ok_or(KeyManagerServiceError::LedgerError( + "Key manager set to use ledger, ledger alpha public key missing".to_string(), + ))?, + WalletType::ProvidedKeys(wallet) => wallet.public_spend_key.clone(), + }; + Ok(KeyAndId { key_id, pub_key: key }) + } + + pub async fn get_comms_key(&self) -> Result, KeyManagerServiceError> { + let key_id = KeyId::Managed { + branch: TransactionKeyManagerBranch::Spend.get_branch_key(), + index: 0, + }; + let private_key = self.get_private_comms_key().await?; + let key = PublicKey::from_secret_key(&private_key); + Ok(KeyAndId { key_id, pub_key: key }) + } + + pub async fn get_next_commitment_mask_and_script_key( &self, - ) -> Result<(TariKeyId, PublicKey, TariKeyId, PublicKey), KeyManagerServiceError> { - let (spend_key_id, spend_public_key) = self + ) -> Result<(KeyAndId, KeyAndId), KeyManagerServiceError> { + let commitment_mask = self .get_next_key(&TransactionKeyManagerBranch::CommitmentMask.get_branch_key()) .await?; - let index = spend_key_id + let index = commitment_mask + .key_id .managed_index() .ok_or(KeyManagerServiceError::KyeIdWithoutIndex)?; - self.db - .set_key_index(&TransactionKeyManagerBranch::ScriptKey.get_branch_key(), index)?; - let script_key_id = KeyId::Managed { - branch: TransactionKeyManagerBranch::ScriptKey.get_branch_key(), + let script_key_id = KeyId::Derived { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), + label: TransactionKeyManagerLabel::ScriptKey.get_branch_key(), index, }; let script_public_key = self.get_public_key_at_key_id(&script_key_id).await?; - Ok((spend_key_id, spend_public_key, script_key_id, script_public_key)) + Ok((commitment_mask, KeyAndId { + key_id: script_key_id, + pub_key: script_public_key, + })) + } + + pub async fn import_key(&self, private_key: PrivateKey) -> Result { + let public_key = PublicKey::from_secret_key(&private_key); + self.db.insert_imported_key(public_key.clone(), private_key)?; + let key_id = KeyId::Imported { key: public_key }; + Ok(key_id) + } + + async fn get_private_view_key(&self) -> Result { + match &self.wallet_type { + WalletType::DerivedKeys => { + self.get_private_key(&TariKeyId::Managed { + branch: TransactionKeyManagerBranch::DataEncryption.get_branch_key(), + index: 0, + }) + .await + }, + WalletType::Ledger(ledger) => ledger + .view_key + .clone() + .ok_or(KeyManagerServiceError::LedgerViewKeyInaccessible), + WalletType::ProvidedKeys(wallet) => Ok(wallet.view_key.clone()), + } + } + + async fn get_private_comms_key(&self) -> Result { + let branch = TransactionKeyManagerBranch::Spend.get_branch_key(); + let index = 0; + + match self.wallet_type { + WalletType::DerivedKeys | WalletType::ProvidedKeys(_) => { + self.get_private_key(&TariKeyId::Managed { + branch: branch.clone(), + index, + }) + .await + }, + WalletType::Ledger(_) => { + let km = self + .key_managers + .get(&branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let key = km.get_private_key(index)?; + Ok(key) + }, + } + } + + fn get_domain_hasher( + label: &str, + ) -> Result, KeyManagerTransactionsHashDomain>, KeyManagerServiceError> { + let tx_label = label.parse::().map_err(|e| { + KeyManagerServiceError::UnknownError(format!("Could not retrieve label for derived key: {}", e)) + })?; + match tx_label { + TransactionKeyManagerLabel::ScriptKey => Ok(DomainSeparatedHasher::< + Blake2b, + KeyManagerTransactionsHashDomain, + >::new_with_label("script key")), + } } /// Calculates a script key id from the spend key id, if a public key is provided, it will only return a result of /// the public keys match - pub async fn find_script_key_id_from_spend_key_id( + pub async fn find_script_key_id_from_commitment_mask_key_id( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, public_script_key: Option<&PublicKey>, ) -> Result, KeyManagerServiceError> { - let index = match spend_key_id { + let index = match commitment_mask_key_id { KeyId::Managed { index, .. } => *index, + KeyId::Derived { .. } => return Ok(None), KeyId::Imported { .. } => return Ok(None), KeyId::Zero => return Ok(None), }; - let script_key_id = KeyId::Managed { - branch: TransactionKeyManagerBranch::ScriptKey.get_branch_key(), + let script_key_id = KeyId::Derived { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), + label: TransactionKeyManagerLabel::ScriptKey.get_branch_key(), index, }; @@ -272,11 +544,20 @@ where TBackend: KeyManagerBackend + 'static let current_index = km.key_index(); - for i in 0u64..current_index + KEY_MANAGER_MAX_SEARCH_DEPTH { - let public_key = PublicKey::from_secret_key(&km.derive_key(i)?.key); + for i in 0u64..TRANSACTION_KEY_MANAGER_MAX_SEARCH_DEPTH { + let index = current_index + i; + let public_key = PublicKey::from_secret_key(&km.derive_key(index)?.key); if public_key == *key { trace!(target: LOG_TARGET, "Key found in {} Key Chain at index {}", branch, i); - return Ok(i); + return Ok(index); + } + if i <= current_index && i != 0u64 { + let index = current_index - i; + let public_key = PublicKey::from_secret_key(&km.derive_key(index)?.key); + if public_key == *key { + trace!(target: LOG_TARGET, "Key found in {} Key Chain at index {}", branch, index); + return Ok(index); + } } } @@ -294,11 +575,21 @@ where TBackend: KeyManagerBackend + 'static let current_index = km.key_index(); - for i in 0u64..current_index + KEY_MANAGER_MAX_SEARCH_DEPTH { - let private_key = &km.derive_key(i)?.key; + // its most likely that the key is close to the current index, so we start searching from the current index + for i in 0u64..TRANSACTION_KEY_MANAGER_MAX_SEARCH_DEPTH { + let index = current_index + i; + let private_key = &km.derive_key(index)?.key; if private_key == key { - trace!(target: LOG_TARGET, "Key found in {} Key Chain at index {}", branch, i); - return Ok(i); + trace!(target: LOG_TARGET, "Key found in {} Key Chain at index {}", branch, index); + return Ok(index); + } + if i <= current_index && i != 0u64 { + let index = current_index - i; + let private_key = &km.derive_key(index)?.key; + if private_key == key { + trace!(target: LOG_TARGET, "Key found in {} Key Chain at index {}", branch, index); + return Ok(index); + } } } @@ -326,35 +617,6 @@ where TBackend: KeyManagerBackend + 'static Ok(()) } - pub async fn import_key(&self, private_key: PrivateKey) -> Result { - let public_key = PublicKey::from_secret_key(&private_key); - let hex_key = public_key.to_hex(); - self.db.insert_imported_key(public_key.clone(), private_key)?; - trace!(target: LOG_TARGET, "Imported key {}", hex_key); - let key_id = KeyId::Imported { key: public_key }; - Ok(key_id) - } - - pub(crate) async fn get_private_key(&self, key_id: &TariKeyId) -> Result { - match key_id { - KeyId::Managed { branch, index } => { - let km = self - .key_managers - .get(branch) - .ok_or(KeyManagerServiceError::UnknownKeyBranch)? - .read() - .await; - let key = km.get_private_key(*index)?; - Ok(key) - }, - KeyId::Imported { key } => { - let pvt_key = self.db.get_imported_key(key)?; - Ok(pvt_key) - }, - KeyId::Zero => Ok(PrivateKey::default()), - } - } - // ----------------------------------------------------------------------------------------------------------------- // General crypto section // ----------------------------------------------------------------------------------------------------------------- @@ -387,6 +649,23 @@ where TBackend: KeyManagerBackend + 'static secret_key_id: &TariKeyId, public_key: &PublicKey, ) -> Result { + #[allow(unused_variables)] + if let WalletType::Ledger(ledger) = &self.wallet_type { + if let KeyId::Managed { branch, index } = secret_key_id { + if branch == &TransactionKeyManagerBranch::SenderOffsetLedger.get_branch_key() { + #[cfg(not(feature = "ledger"))] + { + return Err(TransactionError::LedgerNotSupported); + } + + #[cfg(feature = "ledger")] + { + return self.device_diffie_hellman(ledger, branch, index, public_key).await; + } + } + } + } + let secret_key = self.get_private_key(secret_key_id).await?; let shared_secret = CommsDHKE::new(&secret_key, public_key); Ok(shared_secret) @@ -397,8 +676,59 @@ where TBackend: KeyManagerBackend + 'static secret_key_id: &TariKeyId, public_key: &PublicKey, ) -> Result>, TransactionError> { + #[allow(unused_variables)] + if let WalletType::Ledger(ledger) = &self.wallet_type { + if let KeyId::Managed { branch, index } = secret_key_id { + if branch == &TransactionKeyManagerBranch::SenderOffsetLedger.get_branch_key() { + #[cfg(not(feature = "ledger"))] + { + return Err(TransactionError::LedgerNotSupported); + } + + #[cfg(feature = "ledger")] + { + return self + .device_diffie_hellman(ledger, branch, index, public_key) + .await + .map(diffie_hellman_stealth_domain_hasher); + } + } + } + } + let secret_key = self.get_private_key(secret_key_id).await?; - Ok(diffie_hellman_stealth_domain_hasher(&secret_key, public_key)) + let dh = CommsDHKE::new(&secret_key, public_key); + Ok(diffie_hellman_stealth_domain_hasher(dh)) + } + + #[allow(unused_variables)] // conditionally compiled paths + #[cfg(feature = "ledger")] + async fn device_diffie_hellman( + &self, + ledger: &LedgerWallet, + branch: &str, + index: &u64, + public_key: &PublicKey, + ) -> Result { + let transport = get_transport().map_err(|e| KeyManagerServiceError::LedgerError(e.to_string()))?; + let mut data = index.to_le_bytes().to_vec(); + let branch_u8 = TransactionKeyManagerBranch::from_key(branch).as_byte(); + data.extend_from_slice(&u64::from(branch_u8).to_le_bytes()); + data.extend_from_slice(public_key.as_bytes()); + let command = ledger.build_command(Instruction::GetDHSharedSecret, data); + + return match command.execute_with_transport(&transport) { + Ok(result) => { + debug!(target: LOG_TARGET, "result length: {}, data: {:?}", result.data().len(), result.data()); + if result.data().len() < 33 { + debug!(target: LOG_TARGET, "result less than 33"); + } + + return CommsDHKE::from_canonical_bytes(&result.data()[1..33]) + .map_err(|e| LedgerDeviceError::ByteArrayError(e.to_string()).into()); + }, + Err(e) => Err(KeyManagerServiceError::LedgerError(e.to_string()).into()), + }; } pub async fn import_add_offset_to_private_key( @@ -412,7 +742,7 @@ where TBackend: KeyManagerBackend + 'static pub async fn generate_burn_proof( &self, - spending_key: &TariKeyId, + commitment_mask_key_id: &TariKeyId, amount: &PrivateKey, claim_public_key: &PublicKey, ) -> Result { @@ -420,7 +750,7 @@ where TBackend: KeyManagerBackend + 'static let nonce_x = PrivateKey::random(&mut OsRng); let pub_nonce = self.crypto_factories.commitment.commit(&nonce_x, &nonce_a); - let commitment = self.get_commitment(spending_key, amount).await?; + let commitment = self.get_commitment(commitment_mask_key_id, amount).await?; let challenge = ConfidentialOutputHasher::new("commitment_signature") .chain(&pub_nonce) @@ -428,11 +758,11 @@ where TBackend: KeyManagerBackend + 'static .chain(claim_public_key) .finalize(); - let spend_key = self.get_private_key(spending_key).await?; + let commitment_mask = self.get_private_key(commitment_mask_key_id).await?; RistrettoComSig::sign( amount, - &spend_key, + &commitment_mask, &nonce_a, &nonce_x, &challenge, @@ -445,79 +775,147 @@ where TBackend: KeyManagerBackend + 'static // Transaction input section (transactions > transaction_components > transaction_input) // ----------------------------------------------------------------------------------------------------------------- - pub async fn get_script_private_key(&self, script_key_id: &TariKeyId) -> Result { - match self.wallet_type { - WalletType::Software => self.get_private_key(script_key_id).await.map_err(|e| e.into()), - WalletType::Ledger(_account) => { + pub async fn get_script_signature( + &self, + script_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, + value: &PrivateKey, + txi_version: &TransactionInputVersion, + script_message: &[u8; 32], + ) -> Result { + let commitment = self.get_commitment(commitment_mask_key_id, value).await?; + let commitment_private_key = self.get_private_key(commitment_mask_key_id).await?; + + #[allow(unused_variables)] // When ledger isn't enabled + match (&self.wallet_type, script_key_id) { + ( + WalletType::Ledger(ledger), + KeyId::Derived { + branch, + label: _, + index, + }, + ) => { #[cfg(not(feature = "ledger"))] - return Err(TransactionError::LedgerDeviceError(LedgerDeviceError::NotSupported)); + { + Err(TransactionError::LedgerNotSupported) + } #[cfg(feature = "ledger")] { - let data = script_key_id.managed_index().expect("and index").to_le_bytes().to_vec(); - let command = APDUCommand { - cla: 0x80, - ins: 0x02, // GetPrivateKey - see `./applications/mp_ledger/src/main.rs/Instruction` - p1: 0x00, - p2: 0x00, - data, - }; - let binding = TRANSPORT.lock().expect("lock exists"); - let transport = binding.as_ref().expect("transport exists"); - match transport.exchange(&command) { + let km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let branch_key = km + .get_private_key(*index) + .map_err(|e| TransactionError::KeyManagerError(e.to_string()))?; + + let mut data = u64::from(ledger.network.as_byte()).to_le_bytes().to_vec(); + data.extend_from_slice(&u64::from(txi_version.as_u8()).to_le_bytes()); + data.extend_from_slice(branch_key.as_bytes()); + data.extend_from_slice(value.as_bytes()); + data.extend_from_slice(commitment_private_key.as_bytes()); + data.extend_from_slice(commitment.as_bytes()); + data.extend_from_slice(script_message); + + let command = ledger.build_command(Instruction::GetScriptSignature, data); + let transport = get_transport()?; + + match command.execute_with_transport(&transport) { Ok(result) => { - if result.data().len() < 33 { + if result.data().len() < 161 { + debug!(target: LOG_TARGET, "result less than 161"); return Err(LedgerDeviceError::Processing(format!( - "'get_private_key' insufficient data - expected 33 got {} bytes ({:?})", + "'get_script_signature' insufficient data - expected 161 got {} bytes ({:?})", result.data().len(), result )) .into()); } - PrivateKey::from_canonical_bytes(&result.data()[1..33]) - .map_err(|e| TransactionError::InvalidSignatureError(e.to_string())) + let data = result.data(); + debug!(target: LOG_TARGET, "result length: {}, data: {:?}", result.data().len(), result.data()); + Ok(ComAndPubSignature::new( + Commitment::from_canonical_bytes(&data[1..33]) + .map_err(|e| TransactionError::InvalidSignatureError(e.to_string()))?, + PublicKey::from_canonical_bytes(&data[33..65]) + .map_err(|e| TransactionError::InvalidSignatureError(e.to_string()))?, + PrivateKey::from_canonical_bytes(&data[65..97]) + .map_err(|e| TransactionError::InvalidSignatureError(e.to_string()))?, + PrivateKey::from_canonical_bytes(&data[97..129]) + .map_err(|e| TransactionError::InvalidSignatureError(e.to_string()))?, + PrivateKey::from_canonical_bytes(&data[129..161]) + .map_err(|e| TransactionError::InvalidSignatureError(e.to_string()))?, + )) }, - Err(e) => Err(LedgerDeviceError::Instruction(format!("GetPrivateKey: {}", e)).into()), + Err(e) => Err(LedgerDeviceError::Instruction(format!("GetScriptSignature: {}", e)).into()), } } - // end script private key + }, + (_, _) => { + let r_a = PrivateKey::random(&mut OsRng); + let r_x = PrivateKey::random(&mut OsRng); + let r_y = PrivateKey::random(&mut OsRng); + let ephemeral_commitment = self.crypto_factories.commitment.commit(&r_x, &r_a); + let ephemeral_pubkey = PublicKey::from_secret_key(&r_y); + let script_private_key = self.get_private_key(script_key_id).await?; + + let challenge = TransactionInput::finalize_script_signature_challenge( + txi_version, + &ephemeral_commitment, + &ephemeral_pubkey, + &self.get_public_key_at_key_id(script_key_id).await?, + &commitment, + script_message, + ); + + let script_signature = ComAndPubSignature::sign( + value, + &commitment_private_key, + &script_private_key, + &r_a, + &r_x, + &r_y, + &challenge, + &*self.crypto_factories.commitment, + )?; + Ok(script_signature) }, } } - pub async fn get_script_signature( + pub async fn get_partial_script_signature( &self, - script_key_id: &TariKeyId, - spend_key_id: &TariKeyId, + commitment_mask_id: &TariKeyId, value: &PrivateKey, txi_version: &TransactionInputVersion, + ephemeral_pubkey: &PublicKey, + script_public_key: &PublicKey, script_message: &[u8; 32], ) -> Result { + let private_commitment_mask = self.get_private_key(commitment_mask_id).await?; + let commitment = self.get_commitment(commitment_mask_id, value).await?; let r_a = PrivateKey::random(&mut OsRng); let r_x = PrivateKey::random(&mut OsRng); - let r_y = PrivateKey::random(&mut OsRng); let ephemeral_commitment = self.crypto_factories.commitment.commit(&r_x, &r_a); - let ephemeral_pubkey = PublicKey::from_secret_key(&r_y); - let commitment = self.get_commitment(spend_key_id, value).await?; - let spend_private_key = self.get_private_key(spend_key_id).await?; - let script_private_key = self.get_script_private_key(script_key_id).await?; - let challenge = TransactionInput::finalize_script_signature_challenge( txi_version, &ephemeral_commitment, - &ephemeral_pubkey, - &PublicKey::from_secret_key(&script_private_key), + ephemeral_pubkey, + script_public_key, &commitment, script_message, ); let script_signature = ComAndPubSignature::sign( value, - &spend_private_key, - &script_private_key, + &private_commitment_mask, + &PrivateKey::default(), &r_a, &r_x, - &r_y, + &PrivateKey::default(), &challenge, &*self.crypto_factories.commitment, )?; @@ -544,7 +942,7 @@ where TBackend: KeyManagerBackend + 'static pub async fn construct_range_proof( &self, - private_key: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: u64, min_value: u64, ) -> Result { @@ -556,14 +954,14 @@ where TBackend: KeyManagerBackend + 'static )); } - let spend_private_key = self.get_private_key(private_key).await?; + let commitment_private_key = self.get_private_key(commitment_mask_key_id).await?; let proof_bytes_result = if min_value == 0 { self.crypto_factories .range_proof - .construct_proof(&spend_private_key, value) + .construct_proof(&commitment_private_key, value) } else { let extended_mask = - RistrettoExtendedMask::assign(ExtensionDegree::DefaultPedersen, vec![spend_private_key])?; + RistrettoExtendedMask::assign(ExtensionDegree::DefaultPedersen, vec![commitment_private_key])?; let extended_witness = RistrettoExtendedWitness { mask: extended_mask, @@ -584,22 +982,131 @@ where TBackend: KeyManagerBackend + 'static }) } + #[allow(clippy::too_many_lines)] pub async fn get_script_offset( &self, script_key_ids: &[TariKeyId], sender_offset_key_ids: &[TariKeyId], ) -> Result { - let mut total_sender_offset_private_key = PrivateKey::default(); - for sender_offset_key_id in sender_offset_key_ids { - total_sender_offset_private_key = - total_sender_offset_private_key + self.get_private_key(sender_offset_key_id).await?; - } let mut total_script_private_key = PrivateKey::default(); + let mut derived_key_commitments = vec![]; for script_key_id in script_key_ids { - total_script_private_key = total_script_private_key + self.get_private_key(script_key_id).await?; + match script_key_id { + KeyId::Imported { .. } | KeyId::Managed { .. } | KeyId::Zero => { + total_script_private_key = total_script_private_key + self.get_private_key(script_key_id).await? + }, + KeyId::Derived { + branch, + label: _, + index, + } => match &self.wallet_type { + WalletType::DerivedKeys | WalletType::ProvidedKeys(_) => { + total_script_private_key = + total_script_private_key + self.get_private_key(script_key_id).await?; + }, + WalletType::Ledger(_) => { + let km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .read() + .await; + let branch_key = km + .get_private_key(*index) + .map_err(|e| TransactionError::KeyManagerError(e.to_string()))?; + derived_key_commitments.push(branch_key); + }, + }, + } + } + + match &self.wallet_type { + WalletType::DerivedKeys | WalletType::ProvidedKeys(_) => { + let mut total_sender_offset_private_key = PrivateKey::default(); + for sender_offset_key_id in sender_offset_key_ids { + total_sender_offset_private_key = + total_sender_offset_private_key + self.get_private_key(sender_offset_key_id).await?; + } + let script_offset = total_script_private_key - total_sender_offset_private_key; + Ok(script_offset) + }, + #[allow(unused_variables)] + WalletType::Ledger(ledger) => { + #[cfg(not(feature = "ledger"))] + { + Err(TransactionError::LedgerNotSupported) + } + + #[cfg(feature = "ledger")] + { + let mut sender_offset_indexes = vec![]; + for sender_offset_key_id in sender_offset_key_ids { + match sender_offset_key_id { + TariKeyId::Managed { branch: _, index } | + TariKeyId::Derived { + branch: _, + label: _, + index, + } => { + sender_offset_indexes.push(index); + }, + TariKeyId::Imported { .. } | TariKeyId::Zero => {}, + } + } + + let num_commitments = derived_key_commitments.len() as u64; + let num_offset_key = sender_offset_indexes.len() as u64; + + let mut instructions = num_offset_key.to_le_bytes().to_vec(); + instructions.extend_from_slice(&num_commitments.to_le_bytes()); + + let mut data: Vec> = vec![instructions.to_vec()]; + data.push(total_script_private_key.to_vec()); + + for sender_offset_index in sender_offset_indexes { + data.push(sender_offset_index.to_le_bytes().to_vec()); + } + + for derived_key_commitment in derived_key_commitments { + data.push(derived_key_commitment.to_vec()); + } + + let commands = ledger.chunk_command(Instruction::GetScriptOffset, data); + let transport = get_transport()?; + + let mut result = None; + for command in commands { + match command.execute_with_transport(&transport) { + Ok(r) => result = Some(r), + Err(e) => { + return Err(LedgerDeviceError::Instruction(format!("GetScriptOffset: {}", e)).into()) + }, + } + } + + if let Some(result) = result { + if result.data().len() < 33 { + debug!(target: LOG_TARGET, "result less than 33"); + return Err(LedgerDeviceError::Processing(format!( + "'get_script_offset' insufficient data - expected 33 got {} bytes ({:?})", + result.data().len(), + result + )) + .into()); + } + let data = result.data(); + debug!(target: LOG_TARGET, "result length: {}, data: {:?}", result.data().len(), result.data()); + return PrivateKey::from_canonical_bytes(&data[1..33]) + .map_err(|e| TransactionError::InvalidSignatureError(e.to_string())); + } + + Err( + LedgerDeviceError::Instruction("GetScriptOffset failed to process correctly".to_string()) + .into(), + ) + } + }, } - let script_offset = total_script_private_key - total_sender_offset_private_key; - Ok(script_offset) } async fn get_metadata_signature_ephemeral_private_key_pair( @@ -612,7 +1119,7 @@ where TBackend: KeyManagerBackend + 'static // With RevealedValue type range proofs, the nonce is always 0 and the minimum value promise equal to the value let nonce_a = match range_proof_type { RangeProofType::BulletProofPlus => { - let hasher_a = DomainSeparatedHasher::, KeyManagerHashingDomain>::new_with_label( + let hasher_a = DomainSeparatedHasher::, KeyManagerTransactionsHashDomain>::new_with_label( "metadata_signature_ephemeral_nonce_a", ); let a_hash = hasher_a.chain(nonce_private_key.as_bytes()).finalize(); @@ -623,7 +1130,7 @@ where TBackend: KeyManagerBackend + 'static RangeProofType::RevealedValue => Ok(PrivateKey::default()), }?; - let hasher_b = DomainSeparatedHasher::, KeyManagerHashingDomain>::new_with_label( + let hasher_b = DomainSeparatedHasher::, KeyManagerTransactionsHashDomain>::new_with_label( "metadata_signature_ephemeral_nonce_b", ); let b_hash = hasher_b.chain(nonce_private_key.as_bytes()).finalize(); @@ -644,43 +1151,28 @@ where TBackend: KeyManagerBackend + 'static Ok(self.crypto_factories.commitment.commit(&nonce_b, &nonce_a)) } - pub async fn get_metadata_signature_raw( + pub async fn sign_script_message( &self, - spending_key_id: &TariKeyId, - value_as_private_key: &PrivateKey, - ephemeral_private_nonce_id: &TariKeyId, - sender_offset_key_id: &TariKeyId, - ephemeral_pubkey: &PublicKey, - ephemeral_commitment: &Commitment, - txo_version: &TransactionOutputVersion, - metadata_signature_message: &[u8; 32], - range_proof_type: RangeProofType, - ) -> Result { - let sender_offset_public_key = self.get_public_key_at_key_id(sender_offset_key_id).await?; - let receiver_partial_metadata_signature = self - .get_receiver_partial_metadata_signature( - spending_key_id, - value_as_private_key, - &sender_offset_public_key, - ephemeral_pubkey, - txo_version, - metadata_signature_message, - range_proof_type, - ) - .await?; - let commitment = self.get_commitment(spending_key_id, value_as_private_key).await?; - let sender_partial_metadata_signature = self - .get_sender_partial_metadata_signature( - ephemeral_private_nonce_id, - sender_offset_key_id, - &commitment, - ephemeral_commitment, - txo_version, - metadata_signature_message, - ) - .await?; - let metadata_signature = &receiver_partial_metadata_signature + &sender_partial_metadata_signature; - Ok(metadata_signature) + private_key_id: &TariKeyId, + challenge: &[u8], + ) -> Result { + let private_key = self.get_private_key(private_key_id).await?; + let signature = CheckSigSchnorrSignature::sign(&private_key, challenge, &mut OsRng)?; + + Ok(signature) + } + + pub async fn sign_with_nonce_and_message( + &self, + private_key_id: &TariKeyId, + nonce: &TariKeyId, + challenge: &[u8; 64], + ) -> Result { + let private_key = self.get_private_key(private_key_id).await?; + let private_nonce = self.get_private_key(nonce).await?; + let signature = Signature::sign_raw_uniform(&private_key, private_nonce, challenge)?; + + Ok(signature) } pub async fn get_metadata_signature( @@ -693,15 +1185,16 @@ where TBackend: KeyManagerBackend + 'static range_proof_type: RangeProofType, ) -> Result { let sender_offset_public_key = self.get_public_key_at_key_id(sender_offset_key_id).await?; - let (ephemeral_private_nonce_id, ephemeral_pubkey) = self - .get_next_key(&TransactionKeyManagerBranch::Nonce.get_branch_key()) + // Use the pubkey, but generate the nonce on ledger + let ephemeral_pubkey = self + .get_next_key(&TransactionKeyManagerBranch::MetadataEphemeralNonce.get_branch_key()) .await?; let receiver_partial_metadata_signature = self .get_receiver_partial_metadata_signature( spending_key_id, value_as_private_key, &sender_offset_public_key, - &ephemeral_pubkey, + &ephemeral_pubkey.pub_key, txo_version, metadata_signature_message, range_proof_type, @@ -711,7 +1204,7 @@ where TBackend: KeyManagerBackend + 'static let ephemeral_commitment = receiver_partial_metadata_signature.ephemeral_commitment(); let sender_partial_metadata_signature = self .get_sender_partial_metadata_signature( - &ephemeral_private_nonce_id, + &ephemeral_pubkey.key_id, sender_offset_key_id, &commitment, ephemeral_commitment, @@ -725,7 +1218,7 @@ where TBackend: KeyManagerBackend + 'static pub async fn get_receiver_partial_metadata_signature( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: &PrivateKey, sender_offset_public_key: &PublicKey, ephemeral_pubkey: &PublicKey, @@ -733,15 +1226,15 @@ where TBackend: KeyManagerBackend + 'static metadata_signature_message: &[u8; 32], range_proof_type: RangeProofType, ) -> Result { - let (ephemeral_commitment_nonce_id, _) = self + let ephemeral_commitment_nonce = self .get_next_key(&TransactionKeyManagerBranch::Nonce.get_branch_key()) .await?; let (nonce_a, nonce_b) = self - .get_metadata_signature_ephemeral_private_key_pair(&ephemeral_commitment_nonce_id, range_proof_type) + .get_metadata_signature_ephemeral_private_key_pair(&ephemeral_commitment_nonce.key_id, range_proof_type) .await?; let ephemeral_commitment = self.crypto_factories.commitment.commit(&nonce_b, &nonce_a); - let spend_private_key = self.get_private_key(spend_key_id).await?; - let commitment = self.crypto_factories.commitment.commit(&spend_private_key, value); + let commitment_private_key = self.get_private_key(commitment_mask_key_id).await?; + let commitment = self.crypto_factories.commitment.commit(&commitment_private_key, value); let challenge = TransactionOutput::finalize_metadata_signature_challenge( txo_version, sender_offset_public_key, @@ -753,7 +1246,7 @@ where TBackend: KeyManagerBackend + 'static let metadata_signature = ComAndPubSignature::sign( value, - &spend_private_key, + &commitment_private_key, &PrivateKey::default(), &nonce_a, &nonce_b, @@ -764,6 +1257,9 @@ where TBackend: KeyManagerBackend + 'static Ok(metadata_signature) } + // In the case where the sender is an aggregated signer, we need to parse in the total public key shares, this is + // done in: aggregated_sender_offset_public_keys and aggregated_ephemeral_public_keys. If there is no aggregated + // signers, this can be left as none pub async fn get_sender_partial_metadata_signature( &self, ephemeral_private_nonce_id: &TariKeyId, @@ -775,7 +1271,7 @@ where TBackend: KeyManagerBackend + 'static ) -> Result { let ephemeral_private_key = self.get_private_key(ephemeral_private_nonce_id).await?; let ephemeral_pubkey = PublicKey::from_secret_key(&ephemeral_private_key); - let sender_offset_private_key = self.get_private_key(sender_offset_key_id).await?; + let sender_offset_private_key = self.get_private_key(sender_offset_key_id).await?; // Take the index and use it to find the key from ledger let sender_offset_public_key = PublicKey::from_secret_key(&sender_offset_private_key); let challenge = TransactionOutput::finalize_metadata_signature_challenge( @@ -806,15 +1302,16 @@ where TBackend: KeyManagerBackend + 'static pub async fn get_txo_private_kernel_offset( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, ) -> Result { - let hasher = - DomainSeparatedHasher::, KeyManagerHashingDomain>::new_with_label("kernel_excess_offset"); - let spending_private_key = self.get_private_key(spend_key_id).await?; + let hasher = DomainSeparatedHasher::, KeyManagerTransactionsHashDomain>::new_with_label( + "kernel_excess_offset", + ); + let commitment_private_key = self.get_private_key(commitment_mask_key_id).await?; let nonce_private_key = self.get_private_key(nonce_id).await?; let key_hash = hasher - .chain(spending_private_key.as_bytes()) + .chain(commitment_private_key.as_bytes()) .chain(nonce_private_key.as_bytes()) .finalize(); PrivateKey::from_uniform_bytes(key_hash.as_ref()).map_err(|_| { @@ -824,7 +1321,7 @@ where TBackend: KeyManagerBackend + 'static pub async fn get_partial_txo_kernel_signature( &self, - spending_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, total_nonce: &PublicKey, total_excess: &PublicKey, @@ -833,14 +1330,17 @@ where TBackend: KeyManagerBackend + 'static kernel_features: &KernelFeatures, txo_type: TxoStage, ) -> Result { - let private_key = self.get_private_key(spending_key_id).await?; + let private_key = self.get_private_key(commitment_mask_key_id).await?; // We cannot use an offset with a coinbase tx as this will not allow us to check the coinbase commitment and // because the offset function does not know if its a coinbase or not, we need to know if we need to bypass it // or not let private_signing_key = if kernel_features.is_coinbase() { private_key } else { - private_key - &self.get_txo_private_kernel_offset(spending_key_id, nonce_id).await? + private_key - + &self + .get_txo_private_kernel_offset(commitment_mask_key_id, nonce_id) + .await? }; // We need to check if its input or output for which we are singing. Signing with an input, we need to sign @@ -865,11 +1365,13 @@ where TBackend: KeyManagerBackend + 'static pub async fn get_txo_kernel_signature_excess_with_offset( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, ) -> Result { - let private_key = self.get_private_key(spend_key_id).await?; - let offset = self.get_txo_private_kernel_offset(spend_key_id, nonce_id).await?; + let private_key = self.get_private_key(commitment_mask_key_id).await?; + let offset = self + .get_txo_private_kernel_offset(commitment_mask_key_id, nonce_id) + .await?; let excess = private_key - &offset; Ok(PublicKey::from_secret_key(&excess)) } @@ -878,29 +1380,28 @@ where TBackend: KeyManagerBackend + 'static // Encrypted data section (transactions > transaction_components > encrypted_data) // ----------------------------------------------------------------------------------------------------------------- - async fn get_recovery_key(&self) -> Result { - let recovery_id = KeyId::Managed { - branch: TransactionKeyManagerBranch::DataEncryption.get_branch_key(), - index: 0, - }; - self.get_private_key(&recovery_id).await - } - pub async fn encrypt_data_for_recovery( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, custom_recovery_key_id: Option<&TariKeyId>, value: u64, + payment_id: PaymentId, ) -> Result { let recovery_key = if let Some(key_id) = custom_recovery_key_id { self.get_private_key(key_id).await? } else { - self.get_recovery_key().await? + self.get_private_view_key().await? }; let value_key = value.into(); - let commitment = self.get_commitment(spend_key_id, &value_key).await?; - let spend_key = self.get_private_key(spend_key_id).await?; - let data = EncryptedData::encrypt_data(&recovery_key, &commitment, value.into(), &spend_key)?; + let commitment = self.get_commitment(commitment_mask_key_id, &value_key).await?; + let commitment_private_key = self.get_private_key(commitment_mask_key_id).await?; + let data = EncryptedData::encrypt_data( + &recovery_key, + &commitment, + value.into(), + &commitment_private_key, + payment_id, + )?; Ok(data) } @@ -908,23 +1409,18 @@ where TBackend: KeyManagerBackend + 'static &self, output: &TransactionOutput, custom_recovery_key_id: Option<&TariKeyId>, - ) -> Result<(TariKeyId, MicroMinotari), TransactionError> { + ) -> Result<(TariKeyId, MicroMinotari, PaymentId), TransactionError> { let recovery_key = if let Some(key_id) = custom_recovery_key_id { self.get_private_key(key_id).await? } else { - self.get_recovery_key().await? + self.get_private_view_key().await? }; - let (value, private_key) = + let (value, private_key, payment_id) = EncryptedData::decrypt_data(&recovery_key, output.commitment(), output.encrypted_data())?; self.crypto_factories .range_proof .verify_mask(output.commitment(), &private_key, value.into())?; - // Detect the branch we need to scan on for the key. - let branch = if output.is_coinbase() { - TransactionKeyManagerBranch::Coinbase.get_branch_key() - } else { - TransactionKeyManagerBranch::CommitmentMask.get_branch_key() - }; + let branch = TransactionKeyManagerBranch::CommitmentMask.get_branch_key(); let key = match self.find_private_key_index(&branch, &private_key).await { Ok(index) => { self.update_current_key_index_if_higher(&branch, index).await?; @@ -936,6 +1432,6 @@ where TBackend: KeyManagerBackend + 'static KeyId::Imported { key: public_key } }, }; - Ok((key, value)) + Ok((key, value, payment_id)) } } diff --git a/base_layer/core/src/transactions/key_manager/interface.rs b/base_layer/core/src/transactions/key_manager/interface.rs index 40c211e27e..b0a1051296 100644 --- a/base_layer/core/src/transactions/key_manager/interface.rs +++ b/base_layer/core/src/transactions/key_manager/interface.rs @@ -20,20 +20,27 @@ // 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::str::FromStr; + use blake2::Blake2b; use digest::consts::U64; use strum_macros::EnumIter; -use tari_common_types::types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, Signature}; +use tari_common_types::{ + types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, Signature}, + WALLET_COMMS_AND_SPEND_KEY_BRANCH, +}; use tari_comms::types::CommsDHKE; use tari_crypto::{ hashing::DomainSeparatedHash, ristretto::{RistrettoComSig, RistrettoSchnorr}, }; -use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface, KeyManagerServiceError}; +use tari_key_manager::key_manager_service::{KeyAndId, KeyId, KeyManagerInterface, KeyManagerServiceError}; +use tari_script::CheckSigSchnorrSignature; use crate::transactions::{ tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, KernelFeatures, RangeProofType, @@ -53,17 +60,20 @@ pub enum TxoStage { Output, } +#[repr(u8)] #[derive(Clone, Copy, EnumIter)] +// These byte reps must stay in sync with the ledger representations at: +// applications/minotari_ledger_wallet/wallet/src/main.rs pub enum TransactionKeyManagerBranch { - DataEncryption, - Coinbase, - CoinbaseScript, - CommitmentMask, - Nonce, - KernelNonce, - ScriptKey, - SenderOffset, - CodeTemplateAuthor, + DataEncryption = 0x00, + MetadataEphemeralNonce = 0x01, + CommitmentMask = 0x02, + Nonce = 0x03, + KernelNonce = 0x04, + SenderOffset = 0x05, + SenderOffsetLedger = 0x06, + Spend = 0x07, + CodeTemplateAuthor = 0x08, } impl TransactionKeyManagerBranch { @@ -72,16 +82,60 @@ impl TransactionKeyManagerBranch { pub fn get_branch_key(self) -> String { match self { TransactionKeyManagerBranch::DataEncryption => "data encryption".to_string(), - TransactionKeyManagerBranch::Coinbase => "coinbase".to_string(), - TransactionKeyManagerBranch::CoinbaseScript => "coinbase script".to_string(), TransactionKeyManagerBranch::CommitmentMask => "commitment mask".to_string(), TransactionKeyManagerBranch::Nonce => "nonce".to_string(), + TransactionKeyManagerBranch::MetadataEphemeralNonce => "metadata ephemeral nonce".to_string(), TransactionKeyManagerBranch::KernelNonce => "kernel nonce".to_string(), - TransactionKeyManagerBranch::ScriptKey => "script key".to_string(), TransactionKeyManagerBranch::SenderOffset => "sender offset".to_string(), + TransactionKeyManagerBranch::SenderOffsetLedger => "sender offset ledger".to_string(), + TransactionKeyManagerBranch::Spend => WALLET_COMMS_AND_SPEND_KEY_BRANCH.to_string(), TransactionKeyManagerBranch::CodeTemplateAuthor => "code_template_author".to_string(), } } + + pub fn from_key(key: &str) -> Self { + match key { + "data encryption" => TransactionKeyManagerBranch::DataEncryption, + "commitment mask" => TransactionKeyManagerBranch::CommitmentMask, + "metadata ephemeral nonce" => TransactionKeyManagerBranch::MetadataEphemeralNonce, + "kernel nonce" => TransactionKeyManagerBranch::KernelNonce, + "sender offset" => TransactionKeyManagerBranch::SenderOffset, + "sender offset ledger" => TransactionKeyManagerBranch::SenderOffsetLedger, + "nonce" => TransactionKeyManagerBranch::Nonce, + WALLET_COMMS_AND_SPEND_KEY_BRANCH => TransactionKeyManagerBranch::Spend, + _ => TransactionKeyManagerBranch::Nonce, + } + } + + pub fn as_byte(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy, EnumIter)] +pub enum TransactionKeyManagerLabel { + ScriptKey, +} + +impl TransactionKeyManagerLabel { + /// Warning: Changing these strings will affect the backwards compatibility of the wallet with older databases or + /// recovery. + pub fn get_branch_key(self) -> String { + match self { + TransactionKeyManagerLabel::ScriptKey => "script key".to_string(), + } + } +} + +impl FromStr for TransactionKeyManagerLabel { + type Err = String; + + fn from_str(id: &str) -> Result { + match id { + "script key" => Ok(TransactionKeyManagerLabel::ScriptKey), + _ => Err("Unknown label".to_string()), + } + } } #[async_trait::async_trait] @@ -89,26 +143,30 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { /// Gets the pedersen commitment for the specified index async fn get_commitment( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: &PrivateKey, ) -> Result; async fn verify_mask( &self, commitment: &Commitment, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: u64, ) -> Result; - async fn get_recovery_key_id(&self) -> Result; + async fn get_view_key(&self) -> Result, KeyManagerServiceError>; - async fn get_next_spend_and_script_key_ids( + async fn get_spend_key(&self) -> Result, KeyManagerServiceError>; + + async fn get_comms_key(&self) -> Result, KeyManagerServiceError>; + + async fn get_next_commitment_mask_and_script_key( &self, - ) -> Result<(TariKeyId, PublicKey, TariKeyId, PublicKey), KeyManagerServiceError>; + ) -> Result<(KeyAndId, KeyAndId), KeyManagerServiceError>; - async fn find_script_key_id_from_spend_key_id( + async fn find_script_key_id_from_commitment_mask_key_id( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, public_script_key: Option<&PublicKey>, ) -> Result, KeyManagerServiceError>; @@ -134,7 +192,7 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { async fn construct_range_proof( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: u64, min_value: u64, ) -> Result; @@ -142,15 +200,25 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { async fn get_script_signature( &self, script_key_id: &TariKeyId, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: &PrivateKey, txi_version: &TransactionInputVersion, script_message: &[u8; 32], ) -> Result; + async fn get_partial_script_signature( + &self, + commitment_mask_id: &TariKeyId, + value: &PrivateKey, + txi_version: &TransactionInputVersion, + ephemeral_pubkey: &PublicKey, + script_public_key: &PublicKey, + script_message: &[u8; 32], + ) -> Result; + async fn get_partial_txo_kernel_signature( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, total_nonce: &PublicKey, total_excess: &PublicKey, @@ -162,28 +230,29 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { async fn get_txo_kernel_signature_excess_with_offset( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce: &TariKeyId, ) -> Result; async fn get_txo_private_kernel_offset( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, ) -> Result; async fn encrypt_data_for_recovery( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, custom_recovery_key_id: Option<&TariKeyId>, value: u64, + payment_id: PaymentId, ) -> Result; async fn try_output_key_recovery( &self, output: &TransactionOutput, custom_recovery_key_id: Option<&TariKeyId>, - ) -> Result<(TariKeyId, MicroMinotari), TransactionError>; + ) -> Result<(TariKeyId, MicroMinotari, PaymentId), TransactionError>; async fn get_script_offset( &self, @@ -209,9 +278,22 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { range_proof_type: RangeProofType, ) -> Result; + async fn sign_script_message( + &self, + private_key_id: &TariKeyId, + challenge: &[u8], + ) -> Result; + + async fn sign_with_nonce_and_message( + &self, + private_key_id: &TariKeyId, + nonce: &TariKeyId, + challenge: &[u8; 64], + ) -> Result; + async fn get_receiver_partial_metadata_signature( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: &PrivateKey, sender_offset_public_key: &PublicKey, ephemeral_pubkey: &PublicKey, @@ -220,6 +302,9 @@ pub trait TransactionKeyManagerInterface: KeyManagerInterface { range_proof_type: RangeProofType, ) -> Result; + // In the case where the sender is an aggregated signer, we need to parse in the other public key shares, this is + // done in: aggregated_sender_offset_public_keys and aggregated_ephemeral_public_keys. If there is no aggregated + // signers, this can be left as none async fn get_sender_partial_metadata_signature( &self, ephemeral_private_nonce_id: &TariKeyId, @@ -256,3 +341,47 @@ pub trait SecretTransactionKeyManagerInterface: TransactionKeyManagerInterface { Ok(sig) } } + +#[cfg(test)] +mod test { + use core::iter; + use std::str::FromStr; + + use rand::{distributions::Alphanumeric, rngs::OsRng, Rng}; + use tari_common_types::types::{PrivateKey, PublicKey}; + use tari_crypto::keys::{PublicKey as PK, SecretKey as SK}; + + use crate::transactions::key_manager::TariKeyId; + + fn random_string(len: usize) -> String { + iter::repeat(()) + .map(|_| OsRng.sample(Alphanumeric) as char) + .take(len) + .collect() + } + + #[test] + fn key_id_converts_correctly() { + let managed_key_id: TariKeyId = TariKeyId::Managed { + branch: random_string(8), + index: { + let mut rng = rand::thread_rng(); + let random_value: u64 = rng.gen(); + random_value + }, + }; + let imported_key_id: TariKeyId = TariKeyId::Imported { + key: PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), + }; + let zero_key_id: TariKeyId = TariKeyId::Zero; + + let managed_key_id_str = managed_key_id.to_string(); + let imported_key_id_str = imported_key_id.to_string(); + let zero_key_id_str = zero_key_id.to_string(); + + assert_eq!(managed_key_id, TariKeyId::from_str(&managed_key_id_str).unwrap()); + println!("imported_key_id_str: {}", imported_key_id_str); + assert_eq!(imported_key_id, TariKeyId::from_str(&imported_key_id_str).unwrap()); + assert_eq!(zero_key_id, TariKeyId::from_str(&zero_key_id_str).unwrap()); + } +} diff --git a/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs b/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs index d5c35fe2a6..1518dfb546 100644 --- a/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs +++ b/base_layer/core/src/transactions/key_manager/memory_db_key_manager.rs @@ -28,8 +28,12 @@ use tari_common_sqlite::connection::{DbConnection, DbConnectionUrl}; use tari_common_types::wallet_types::WalletType; use tari_key_manager::{ cipher_seed::CipherSeed, - key_manager_service::storage::{database::KeyManagerDatabase, sqlite_db::KeyManagerSqliteDatabase}, + key_manager_service::{ + storage::{database::KeyManagerDatabase, sqlite_db::KeyManagerSqliteDatabase}, + KeyManagerServiceError, + }, }; +use zeroize::Zeroizing; use crate::transactions::{key_manager::TransactionKeyManagerWrapper, CryptoFactories}; @@ -42,26 +46,25 @@ fn random_string(len: usize) -> String { .collect() } -pub fn create_memory_db_key_manager_with_range_proof_size(size: usize) -> MemoryDbKeyManager { - let connection = DbConnection::connect_url(&DbConnectionUrl::MemoryShared(random_string(8))).unwrap(); +pub fn create_memory_db_key_manager_with_range_proof_size( + size: usize, +) -> Result { + let connection = DbConnection::connect_url(&DbConnectionUrl::MemoryShared(random_string(8)))?; let cipher = CipherSeed::new(); - let mut key = [0u8; size_of::()]; - OsRng.fill_bytes(&mut key); - let key_ga = Key::from_slice(&key); + let mut key = Zeroizing::new([0u8; size_of::()]); + OsRng.fill_bytes(key.as_mut()); + let key_ga = Key::from_slice(key.as_ref()); let db_cipher = XChaCha20Poly1305::new(key_ga); let factory = CryptoFactories::new(size); - let wallet_type = WalletType::Software; - TransactionKeyManagerWrapper::>::new( cipher, KeyManagerDatabase::new(KeyManagerSqliteDatabase::init(connection, db_cipher)), factory, - wallet_type, + WalletType::default(), ) - .unwrap() } -pub fn create_memory_db_key_manager() -> MemoryDbKeyManager { +pub fn create_memory_db_key_manager() -> Result { create_memory_db_key_manager_with_range_proof_size(64) } diff --git a/base_layer/core/src/transactions/key_manager/mod.rs b/base_layer/core/src/transactions/key_manager/mod.rs index ec8033b7df..b622a3715e 100644 --- a/base_layer/core/src/transactions/key_manager/mod.rs +++ b/base_layer/core/src/transactions/key_manager/mod.rs @@ -29,6 +29,7 @@ pub use interface::{ TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface, + TransactionKeyManagerLabel, TxoStage, }; @@ -46,4 +47,4 @@ pub use memory_db_key_manager::{ }; mod error; -pub use error::{CoreKeyManagerError, LedgerDeviceError}; +pub use error::CoreKeyManagerError; diff --git a/base_layer/core/src/transactions/key_manager/wrapper.rs b/base_layer/core/src/transactions/key_manager/wrapper.rs index 9e316892e0..58415fdcea 100644 --- a/base_layer/core/src/transactions/key_manager/wrapper.rs +++ b/base_layer/core/src/transactions/key_manager/wrapper.rs @@ -35,22 +35,24 @@ use tari_key_manager::{ key_manager_service::{ storage::database::{KeyManagerBackend, KeyManagerDatabase}, AddResult, + KeyAndId, KeyManagerInterface, KeyManagerServiceError, }, }; +use tari_script::CheckSigSchnorrSignature; use tokio::sync::RwLock; use crate::transactions::{ key_manager::{ interface::{SecretTransactionKeyManagerInterface, TxoStage}, TariKeyId, - TransactionKeyManagerBranch, TransactionKeyManagerInner, TransactionKeyManagerInterface, }, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, KernelFeatures, RangeProofType, @@ -110,7 +112,7 @@ where TBackend: KeyManagerBackend + 'static async fn get_next_key + Send>( &self, branch: T, - ) -> Result<(TariKeyId, PublicKey), KeyManagerServiceError> { + ) -> Result, KeyManagerServiceError> { self.transaction_key_manager_inner .read() .await @@ -118,6 +120,10 @@ where TBackend: KeyManagerBackend + 'static .await } + async fn get_random_key(&self) -> Result, KeyManagerServiceError> { + self.transaction_key_manager_inner.read().await.get_random_key().await + } + async fn get_static_key + Send>(&self, branch: T) -> Result { self.transaction_key_manager_inner .read() @@ -173,53 +179,60 @@ where TBackend: KeyManagerBackend + 'static { async fn get_commitment( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: &PrivateKey, ) -> Result { self.transaction_key_manager_inner .read() .await - .get_commitment(spend_key_id, value) + .get_commitment(commitment_mask_key_id, value) .await } async fn verify_mask( &self, commitment: &Commitment, - spending_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: u64, ) -> Result { self.transaction_key_manager_inner .read() .await - .verify_mask(commitment, spending_key_id, value) + .verify_mask(commitment, commitment_mask_key_id, value) .await } - async fn get_recovery_key_id(&self) -> Result { - self.get_static_key(TransactionKeyManagerBranch::DataEncryption.get_branch_key()) - .await + async fn get_view_key(&self) -> Result, KeyManagerServiceError> { + self.transaction_key_manager_inner.read().await.get_view_key().await + } + + async fn get_spend_key(&self) -> Result, KeyManagerServiceError> { + self.transaction_key_manager_inner.read().await.get_spend_key().await + } + + async fn get_comms_key(&self) -> Result, KeyManagerServiceError> { + self.transaction_key_manager_inner.read().await.get_comms_key().await } - async fn get_next_spend_and_script_key_ids( + async fn get_next_commitment_mask_and_script_key( &self, - ) -> Result<(TariKeyId, PublicKey, TariKeyId, PublicKey), KeyManagerServiceError> { + ) -> Result<(KeyAndId, KeyAndId), KeyManagerServiceError> { self.transaction_key_manager_inner .read() .await - .get_next_spend_and_script_key_ids() + .get_next_commitment_mask_and_script_key() .await } - async fn find_script_key_id_from_spend_key_id( + async fn find_script_key_id_from_commitment_mask_key_id( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, public_script_key: Option<&PublicKey>, ) -> Result, KeyManagerServiceError> { self.transaction_key_manager_inner .read() .await - .find_script_key_id_from_spend_key_id(spend_key_id, public_script_key) + .find_script_key_id_from_commitment_mask_key_id(commitment_mask_key_id, public_script_key) .await } @@ -269,35 +282,64 @@ where TBackend: KeyManagerBackend + 'static async fn construct_range_proof( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: u64, min_value: u64, ) -> Result { self.transaction_key_manager_inner .read() .await - .construct_range_proof(spend_key_id, value, min_value) + .construct_range_proof(commitment_mask_key_id, value, min_value) .await } async fn get_script_signature( &self, script_key_id: &TariKeyId, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, + value: &PrivateKey, + txi_version: &TransactionInputVersion, + script_message: &[u8; 32], + ) -> Result { + self.transaction_key_manager_inner + .read() + .await + .get_script_signature( + script_key_id, + commitment_mask_key_id, + value, + txi_version, + script_message, + ) + .await + } + + async fn get_partial_script_signature( + &self, + commitment_mask_id: &TariKeyId, value: &PrivateKey, txi_version: &TransactionInputVersion, + ephemeral_pubkey: &PublicKey, + script_public_key: &PublicKey, script_message: &[u8; 32], ) -> Result { self.transaction_key_manager_inner .read() .await - .get_script_signature(script_key_id, spend_key_id, value, txi_version, script_message) + .get_partial_script_signature( + commitment_mask_id, + value, + txi_version, + ephemeral_pubkey, + script_public_key, + script_message, + ) .await } async fn get_partial_txo_kernel_signature( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, total_nonce: &PublicKey, total_excess: &PublicKey, @@ -310,7 +352,7 @@ where TBackend: KeyManagerBackend + 'static .read() .await .get_partial_txo_kernel_signature( - spend_key_id, + commitment_mask_key_id, nonce_id, total_nonce, total_excess, @@ -324,38 +366,39 @@ where TBackend: KeyManagerBackend + 'static async fn get_txo_kernel_signature_excess_with_offset( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, ) -> Result { self.transaction_key_manager_inner .read() .await - .get_txo_kernel_signature_excess_with_offset(spend_key_id, nonce_id) + .get_txo_kernel_signature_excess_with_offset(commitment_mask_key_id, nonce_id) .await } async fn get_txo_private_kernel_offset( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, nonce_id: &TariKeyId, ) -> Result { self.transaction_key_manager_inner .read() .await - .get_txo_private_kernel_offset(spend_key_id, nonce_id) + .get_txo_private_kernel_offset(commitment_mask_key_id, nonce_id) .await } async fn encrypt_data_for_recovery( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, custom_recovery_key_id: Option<&TariKeyId>, value: u64, + payment_id: PaymentId, ) -> Result { self.transaction_key_manager_inner .read() .await - .encrypt_data_for_recovery(spend_key_id, custom_recovery_key_id, value) + .encrypt_data_for_recovery(commitment_mask_key_id, custom_recovery_key_id, value, payment_id) .await } @@ -363,7 +406,7 @@ where TBackend: KeyManagerBackend + 'static &self, output: &TransactionOutput, custom_recovery_key_id: Option<&TariKeyId>, - ) -> Result<(TariKeyId, MicroMinotari), TransactionError> { + ) -> Result<(TariKeyId, MicroMinotari, PaymentId), TransactionError> { self.transaction_key_manager_inner .read() .await @@ -418,9 +461,34 @@ where TBackend: KeyManagerBackend + 'static .await } + async fn sign_script_message( + &self, + private_key_id: &TariKeyId, + challenge: &[u8], + ) -> Result { + self.transaction_key_manager_inner + .read() + .await + .sign_script_message(private_key_id, challenge) + .await + } + + async fn sign_with_nonce_and_message( + &self, + private_key_id: &TariKeyId, + nonce: &TariKeyId, + challenge: &[u8; 64], + ) -> Result { + self.transaction_key_manager_inner + .read() + .await + .sign_with_nonce_and_message(private_key_id, nonce, challenge) + .await + } + async fn get_receiver_partial_metadata_signature( &self, - spend_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, value: &PrivateKey, sender_offset_public_key: &PublicKey, ephemeral_pubkey: &PublicKey, @@ -432,7 +500,7 @@ where TBackend: KeyManagerBackend + 'static .read() .await .get_receiver_partial_metadata_signature( - spend_key_id, + commitment_mask_key_id, value, sender_offset_public_key, ephemeral_pubkey, @@ -443,6 +511,9 @@ where TBackend: KeyManagerBackend + 'static .await } + // In the case where the sender is an aggregated signer, we need to parse in the other public key shares, this is + // done in: aggregated_sender_offset_public_keys and aggregated_ephemeral_public_keys. If there is no aggregated + // signers, this can be left as none async fn get_sender_partial_metadata_signature( &self, ephemeral_private_nonce_id: &TariKeyId, diff --git a/base_layer/core/src/transactions/tari_amount.rs b/base_layer/core/src/transactions/tari_amount.rs index 4110f016fb..b48451aab5 100644 --- a/base_layer/core/src/transactions/tari_amount.rs +++ b/base_layer/core/src/transactions/tari_amount.rs @@ -395,8 +395,6 @@ impl DivAssign for Minotari { #[cfg(test)] mod test { - use std::{convert::TryFrom, str::FromStr}; - use super::*; #[test] diff --git a/base_layer/core/src/transactions/test_helpers.rs b/base_layer/core/src/transactions/test_helpers.rs index 586f6c7e3b..d1f3d36f47 100644 --- a/base_layer/core/src/transactions/test_helpers.rs +++ b/base_layer/core/src/transactions/test_helpers.rs @@ -27,7 +27,7 @@ use tari_common::configuration::Network; use tari_common_sqlite::{error::SqliteStorageError, sqlite_connection_pool::PooledDbConnection}; use tari_common_types::types::{Commitment, PrivateKey, PublicKey, Signature}; use tari_crypto::keys::{PublicKey as PK, SecretKey}; -use tari_key_manager::key_manager_service::{storage::sqlite_db::KeyManagerSqliteDatabase, KeyManagerInterface}; +use tari_key_manager::key_manager_service::{storage::sqlite_db::KeyManagerSqliteDatabase, KeyId, KeyManagerInterface}; use tari_script::{inputs, script, ExecutionStack, TariScript}; use super::transaction_components::{TransactionInputVersion, TransactionOutputVersion}; @@ -44,11 +44,13 @@ use crate::{ TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface, + TransactionKeyManagerLabel, TransactionKeyManagerWrapper, TxoStage, }, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, KernelBuilder, KernelFeatures, OutputFeatures, @@ -93,8 +95,7 @@ pub async fn create_test_input< #[derive(Clone)] pub struct TestParams { - pub spend_key_id: TariKeyId, - pub spend_key_pk: PublicKey, + pub commitment_mask_key_id: TariKeyId, pub script_key_id: TariKeyId, pub script_key_pk: PublicKey, pub sender_offset_key_id: TariKeyId, @@ -112,38 +113,36 @@ impl TestParams { pub async fn new + Clone + 'static>( key_manager: &TransactionKeyManagerWrapper>, ) -> TestParams { - let (spend_key_id, spend_key_pk, script_key_id, script_key_pk) = - key_manager.get_next_spend_and_script_key_ids().await.unwrap(); - let (sender_offset_key_id, sender_offset_key_pk) = key_manager + let (commitment_mask_key, script_key) = key_manager.get_next_commitment_mask_and_script_key().await.unwrap(); + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); - let (kernel_nonce_key_id, kernel_nonce_key_pk) = key_manager + let kernel_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); - let (public_nonce_key_id, public_nonce_key_pk) = key_manager + let public_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::Nonce.get_branch_key()) .await .unwrap(); - let (ephemeral_public_nonce_key_id, ephemeral_public_nonce_key_pk) = key_manager + let ephemeral_public_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::Nonce.get_branch_key()) .await .unwrap(); Self { - spend_key_id, - spend_key_pk, - script_key_id, - script_key_pk, - sender_offset_key_id, - sender_offset_key_pk, - kernel_nonce_key_id, - kernel_nonce_key_pk, - public_nonce_key_id, - public_nonce_key_pk, - ephemeral_public_nonce_key_id, - ephemeral_public_nonce_key_pk, + commitment_mask_key_id: commitment_mask_key.key_id, + script_key_id: script_key.key_id, + script_key_pk: script_key.pub_key, + sender_offset_key_id: sender_offset.key_id, + sender_offset_key_pk: sender_offset.pub_key, + kernel_nonce_key_id: kernel_nonce.key_id, + kernel_nonce_key_pk: kernel_nonce.pub_key, + public_nonce_key_id: public_nonce.key_id, + public_nonce_key_pk: public_nonce.pub_key, + ephemeral_public_nonce_key_id: ephemeral_public_nonce.key_id, + ephemeral_public_nonce_key_pk: ephemeral_public_nonce.pub_key, transaction_weight: TransactionWeight::v1(), } } @@ -165,10 +164,10 @@ impl TestParams { }; let input_data = params.input_data.unwrap_or_else(|| inputs!(self.script_key_pk.clone())); - let output = WalletOutputBuilder::new(params.value, self.spend_key_id.clone()) + let output = WalletOutputBuilder::new(params.value, self.commitment_mask_key_id.clone()) .with_features(params.features) .with_script(params.script.clone()) - .encrypt_data_for_recovery(key_manager, None) + .encrypt_data_for_recovery(key_manager, None, PaymentId::Empty) .await .unwrap() .with_input_data(input_data) @@ -292,7 +291,7 @@ pub async fn create_random_signature_from_secret_key( txo_type: TxoStage, ) -> (PublicKey, Signature) { let tx_meta = TransactionMetadata::new_with_features(fee, lock_height, kernel_features); - let (nonce_id, total_nonce) = key_manager + let total_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); @@ -308,8 +307,8 @@ pub async fn create_random_signature_from_secret_key( let kernel_signature = key_manager .get_partial_txo_kernel_signature( &secret_key_id, - &nonce_id, - &total_nonce, + &total_nonce.key_id, + &total_nonce.pub_key, &total_excess, &kernel_version, &kernel_message, @@ -332,7 +331,7 @@ pub async fn create_coinbase_wallet_output( range_proof_type: RangeProofType, ) -> WalletOutput { let rules = create_consensus_manager(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let constants = rules.consensus_constants(height); test_params .create_output( @@ -370,7 +369,12 @@ pub async fn create_wallet_output_with_data< UtxoTestParams { value, script, - features: output_features, + features: output_features.clone(), + minimum_value_promise: if output_features.range_proof_type == RangeProofType::BulletProofPlus { + MicroMinotari::zero() + } else { + value + }, ..Default::default() }, key_manager, @@ -643,7 +647,7 @@ pub async fn create_transaction_with( TariScript::default(), ExecutionStack::default(), change.script_key_id, - change.spend_key_id, + change.commitment_mask_key_id, Covenant::default(), ); for input in inputs { @@ -713,7 +717,7 @@ pub async fn create_stx_protocol_internal( script!(PushPubKey(Box::new(script_public_key))), ExecutionStack::default(), change.script_key_id, - change.spend_key_id, + change.commitment_mask_key_id, Covenant::default(), ); @@ -721,18 +725,19 @@ pub async fn create_stx_protocol_internal( stx_builder.with_input(tx_input.clone()).await.unwrap(); } for val in schema.to { - let (spending_key, _) = key_manager + let commitment_mask = key_manager .get_next_key(TransactionKeyManagerBranch::CommitmentMask.get_branch_key()) .await .unwrap(); - let (sender_offset_key_id, sender_offset_public_key) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); - let (script_key_id, _) = key_manager - .get_next_key(TransactionKeyManagerBranch::ScriptKey.get_branch_key()) - .await - .unwrap(); + let script_key_id = KeyId::Derived { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), + label: TransactionKeyManagerLabel::ScriptKey.get_branch_key(), + index: commitment_mask.key_id.managed_index().unwrap(), + }; let script_public_key = key_manager.get_public_key_at_key_id(&script_key_id).await.unwrap(); let input_data = match &schema.input_data { Some(data) => data.clone(), @@ -742,18 +747,18 @@ pub async fn create_stx_protocol_internal( Some(data) => data, None => TransactionOutputVersion::get_current_version(), }; - let output = WalletOutputBuilder::new(val, spending_key) + let output = WalletOutputBuilder::new(val, commitment_mask.key_id) .with_features(schema.features.clone()) .with_script(schema.script.clone()) - .encrypt_data_for_recovery(key_manager, None) + .encrypt_data_for_recovery(key_manager, None, PaymentId::Empty) .await .unwrap() .with_input_data(input_data) .with_covenant(schema.covenant.clone()) .with_version(version) - .with_sender_offset_public_key(sender_offset_public_key) + .with_sender_offset_public_key(sender_offset.pub_key) .with_script_key(script_key_id.clone()) - .sign_as_sender_and_receiver(key_manager, &sender_offset_key_id) + .sign_as_sender_and_receiver(key_manager, &sender_offset.key_id) .await .unwrap() .try_build(key_manager) @@ -761,10 +766,10 @@ pub async fn create_stx_protocol_internal( .unwrap(); outputs.push(output.clone()); - stx_builder.with_output(output, sender_offset_key_id).await.unwrap(); + stx_builder.with_output(output, sender_offset.key_id).await.unwrap(); } for mut utxo in schema.to_outputs { - let (sender_offset_key_id, _) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); @@ -773,7 +778,7 @@ pub async fn create_stx_protocol_internal( .get_metadata_signature( &utxo.spending_key_id, &utxo.value.into(), - &sender_offset_key_id, + &sender_offset.key_id, &utxo.version, &metadata_message, utxo.features.range_proof_type, @@ -781,32 +786,35 @@ pub async fn create_stx_protocol_internal( .await .unwrap(); - stx_builder.with_output(utxo, sender_offset_key_id).await.unwrap(); + stx_builder.with_output(utxo, sender_offset.key_id).await.unwrap(); } stx_builder } pub async fn create_coinbase_kernel( - spending_key_id: &TariKeyId, + commitment_mask_key_id: &TariKeyId, key_manager: &MemoryDbKeyManager, ) -> TransactionKernel { let kernel_version = TransactionKernelVersion::get_current_version(); let kernel_features = KernelFeatures::COINBASE_KERNEL; let kernel_message = TransactionKernel::build_kernel_signature_message(&kernel_version, 0.into(), 0, &kernel_features, &None); - let (public_nonce_id, public_nonce) = key_manager + let public_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); - let public_spend_key = key_manager.get_public_key_at_key_id(spending_key_id).await.unwrap(); + let public_commitment_mask = key_manager + .get_public_key_at_key_id(commitment_mask_key_id) + .await + .unwrap(); let kernel_signature = key_manager .get_partial_txo_kernel_signature( - spending_key_id, - &public_nonce_id, - &public_nonce, - &public_spend_key, + commitment_mask_key_id, + &public_nonce.key_id, + &public_nonce.pub_key, + &public_commitment_mask, &kernel_version, &kernel_message, &kernel_features, @@ -817,7 +825,7 @@ pub async fn create_coinbase_kernel( KernelBuilder::new() .with_features(kernel_features) - .with_excess(&Commitment::from_public_key(&public_spend_key)) + .with_excess(&Commitment::from_public_key(&public_commitment_mask)) .with_signature(kernel_signature) .build() .unwrap() @@ -845,15 +853,15 @@ pub async fn create_utxo( covenant: &Covenant, minimum_value_promise: MicroMinotari, ) -> (TransactionOutput, TariKeyId, TariKeyId) { - let (spending_key_id, _) = key_manager + let commitment_mask = key_manager .get_next_key(TransactionKeyManagerBranch::CommitmentMask.get_branch_key()) .await .unwrap(); let encrypted_data = key_manager - .encrypt_data_for_recovery(&spending_key_id, None, value.into()) + .encrypt_data_for_recovery(&commitment_mask.key_id, None, value.into(), PaymentId::Empty) .await .unwrap(); - let (sender_offset_key_id, sender_offset_public_key) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); @@ -867,9 +875,9 @@ pub async fn create_utxo( ); let metadata_sig = key_manager .get_metadata_signature( - &spending_key_id, + &commitment_mask.key_id, &value.into(), - &sender_offset_key_id, + &sender_offset.key_id, &TransactionOutputVersion::get_current_version(), &metadata_message, features.range_proof_type, @@ -877,13 +885,13 @@ pub async fn create_utxo( .await .unwrap(); let commitment = key_manager - .get_commitment(&spending_key_id, &value.into()) + .get_commitment(&commitment_mask.key_id, &value.into()) .await .unwrap(); let proof = if features.range_proof_type == RangeProofType::BulletProofPlus { Some( key_manager - .construct_range_proof(&spending_key_id, value.into(), minimum_value_promise.into()) + .construct_range_proof(&commitment_mask.key_id, value.into(), minimum_value_promise.into()) .await .unwrap(), ) @@ -896,7 +904,7 @@ pub async fn create_utxo( commitment, proof, script.clone(), - sender_offset_public_key, + sender_offset.pub_key, metadata_sig, covenant.clone(), encrypted_data, @@ -904,7 +912,7 @@ pub async fn create_utxo( ); utxo.verify_range_proof(&CryptoFactories::default().range_proof) .unwrap(); - (utxo, spending_key_id, sender_offset_key_id) + (utxo, commitment_mask.key_id, sender_offset.key_id) } pub async fn schema_to_transaction( diff --git a/base_layer/core/src/transactions/transaction_components/encrypted_data.rs b/base_layer/core/src/transactions/transaction_components/encrypted_data.rs index 273fd14740..a3329e9fa2 100644 --- a/base_layer/core/src/transactions/transaction_components/encrypted_data.rs +++ b/base_layer/core/src/transactions/transaction_components/encrypted_data.rs @@ -23,9 +23,14 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. -//! Encrypted data using the the extended-nonce variant XChaCha20-Poly1305 encryption with secure random nonce. +//! Encrypted data using the extended-nonce variant XChaCha20-Poly1305 encryption with secure random nonce. -use std::mem::size_of; +use std::{ + convert::TryInto, + fmt, + fmt::{Display, Formatter}, + mem::size_of, +}; use blake2::Blake2b; use borsh::{BorshDeserialize, BorshSerialize}; @@ -37,8 +42,12 @@ use chacha20poly1305::{ XNonce, }; use digest::{consts::U32, generic_array::GenericArray, FixedOutput}; +use primitive_types::U256; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{Commitment, PrivateKey}; +use tari_common_types::{ + tari_address::dual_address::DualAddress, + types::{Commitment, PrivateKey}, +}; use tari_crypto::{hashing::DomainSeparatedHasher, keys::SecretKey}; use tari_hashing::TransactionSecureNonceKdfDomain; use tari_utilities::{ @@ -58,17 +67,83 @@ const SIZE_NONCE: usize = size_of::(); const SIZE_VALUE: usize = size_of::(); const SIZE_MASK: usize = PrivateKey::KEY_LEN; const SIZE_TAG: usize = size_of::(); -const SIZE_TOTAL: usize = SIZE_NONCE + SIZE_VALUE + SIZE_MASK + SIZE_TAG; +pub const STATIC_ENCRYPTED_DATA_SIZE_TOTAL: usize = SIZE_NONCE + SIZE_VALUE + SIZE_MASK + SIZE_TAG; // Number of hex characters of encrypted data to display on each side of ellipsis when truncating const DISPLAY_CUTOFF: usize = 16; -#[derive( - Debug, Copy, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, Zeroize, -)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, Hash, BorshSerialize, BorshDeserialize, Zeroize)] pub struct EncryptedData { #[serde(with = "tari_utilities::serde::hex")] - data: [u8; SIZE_TOTAL], // nonce, encrypted value, encrypted mask, tag + data: Vec, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub enum PaymentId { + Empty, + U64(u64), + U256(U256), + Address(DualAddress), + Open(Vec), +} + +impl PaymentId { + pub fn get_size(&self) -> usize { + match self { + PaymentId::Empty => 0, + PaymentId::U64(_) => size_of::(), + PaymentId::U256(_) => size_of::(), + PaymentId::Address(_) => 67, + PaymentId::Open(v) => v.len(), + } + } + + pub fn as_bytes(&self) -> Vec { + match self { + PaymentId::Empty => Vec::new(), + PaymentId::U64(v) => (*v).to_le_bytes().to_vec(), + PaymentId::U256(v) => { + let mut bytes = vec![0; 32]; + v.to_little_endian(&mut bytes); + bytes + }, + PaymentId::Address(v) => v.to_bytes().to_vec(), + PaymentId::Open(v) => v.clone(), + } + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + match bytes.len() { + 0 => Ok(PaymentId::Empty), + 8 => { + let bytes: [u8; 8] = bytes.try_into().expect("Cannot fail, as we already test the length"); + let v = u64::from_le_bytes(bytes); + Ok(PaymentId::U64(v)) + }, + 32 => { + let v = U256::from_little_endian(bytes); + Ok(PaymentId::U256(v)) + }, + 67 => { + let v = + DualAddress::from_bytes(bytes).map_err(|e| EncryptedDataError::ByteArrayError(e.to_string()))?; + Ok(PaymentId::Address(v)) + }, + _ => Ok(PaymentId::Open(bytes.to_vec())), + } + } +} + +impl Display for PaymentId { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + PaymentId::Empty => write!(f, "N/A"), + PaymentId::U64(v) => write!(f, "{}", v), + PaymentId::U256(v) => write!(f, "{}", v), + PaymentId::Address(v) => write!(f, "{}", v.to_emoji_string()), + PaymentId::Open(v) => write!(f, "byte vector of len: {}", v.len()), + } + } } /// AEAD associated data @@ -85,11 +160,13 @@ impl EncryptedData { commitment: &Commitment, value: MicroMinotari, mask: &PrivateKey, + payment_id: PaymentId, ) -> Result { // Encode the value and mask - let mut bytes = Zeroizing::new([0u8; SIZE_VALUE + SIZE_MASK]); + let mut bytes = Zeroizing::new(vec![0; SIZE_VALUE + SIZE_MASK + payment_id.get_size()]); bytes[..SIZE_VALUE].clone_from_slice(value.as_u64().to_le_bytes().as_ref()); - bytes[SIZE_VALUE..].clone_from_slice(mask.as_bytes()); + bytes[SIZE_VALUE..SIZE_VALUE + SIZE_MASK].clone_from_slice(mask.as_bytes()); + bytes[SIZE_VALUE + SIZE_MASK..].clone_from_slice(&payment_id.as_bytes()); // Produce a secure random nonce let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng); @@ -102,10 +179,11 @@ impl EncryptedData { let tag = cipher.encrypt_in_place_detached(&nonce, ENCRYPTED_DATA_AAD, bytes.as_mut_slice())?; // Put everything together: nonce, ciphertext, tag - let mut data = [0u8; SIZE_TOTAL]; - data[..SIZE_NONCE].clone_from_slice(&nonce); - data[SIZE_NONCE..SIZE_NONCE + SIZE_VALUE + SIZE_MASK].clone_from_slice(bytes.as_slice()); - data[SIZE_NONCE + SIZE_VALUE + SIZE_MASK..].clone_from_slice(&tag); + let mut data = vec![0; STATIC_ENCRYPTED_DATA_SIZE_TOTAL + payment_id.get_size()]; + data[..SIZE_TAG].clone_from_slice(&tag); + data[SIZE_TAG..SIZE_TAG + SIZE_NONCE].clone_from_slice(&nonce); + data[SIZE_TAG + SIZE_NONCE..SIZE_TAG + SIZE_NONCE + SIZE_VALUE + SIZE_MASK + payment_id.get_size()] + .clone_from_slice(bytes.as_slice()); Ok(Self { data }) } @@ -117,12 +195,19 @@ impl EncryptedData { encryption_key: &PrivateKey, commitment: &Commitment, encrypted_data: &EncryptedData, - ) -> Result<(MicroMinotari, PrivateKey), EncryptedDataError> { + ) -> Result<(MicroMinotari, PrivateKey, PaymentId), EncryptedDataError> { // Extract the nonce, ciphertext, and tag - let nonce = XNonce::from_slice(&encrypted_data.as_bytes()[..SIZE_NONCE]); - let mut bytes = Zeroizing::new([0u8; SIZE_VALUE + SIZE_MASK]); - bytes.clone_from_slice(&encrypted_data.as_bytes()[SIZE_NONCE..SIZE_NONCE + SIZE_VALUE + SIZE_MASK]); - let tag = Tag::from_slice(&encrypted_data.as_bytes()[SIZE_NONCE + SIZE_VALUE + SIZE_MASK..]); + let tag = Tag::from_slice(&encrypted_data.as_bytes()[..SIZE_TAG]); + let nonce = XNonce::from_slice(&encrypted_data.as_bytes()[SIZE_TAG..SIZE_TAG + SIZE_NONCE]); + let mut bytes = Zeroizing::new(vec![ + 0; + encrypted_data + .data + .len() + .saturating_sub(SIZE_TAG) + .saturating_sub(SIZE_NONCE) + ]); + bytes.clone_from_slice(&encrypted_data.as_bytes()[SIZE_TAG + SIZE_NONCE..]); // Set up the AEAD let aead_key = kdf_aead(encryption_key, commitment); @@ -136,32 +221,33 @@ impl EncryptedData { value_bytes.clone_from_slice(&bytes[0..SIZE_VALUE]); Ok(( u64::from_le_bytes(value_bytes).into(), - PrivateKey::from_canonical_bytes(&bytes[SIZE_VALUE..])?, + PrivateKey::from_canonical_bytes(&bytes[SIZE_VALUE..SIZE_VALUE + SIZE_MASK])?, + PaymentId::from_bytes(&bytes[SIZE_VALUE + SIZE_MASK..])?, )) } /// Parse encrypted data from a byte slice pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() != SIZE_TOTAL { + if bytes.len() < STATIC_ENCRYPTED_DATA_SIZE_TOTAL { return Err(EncryptedDataError::IncorrectLength(format!( - "Expected {} bytes, got {}", - SIZE_TOTAL, + "Expected bytes to be at least {}, got {}", + STATIC_ENCRYPTED_DATA_SIZE_TOTAL, bytes.len() ))); } - let mut data = [0u8; SIZE_TOTAL]; + let mut data = vec![0; bytes.len()]; data.copy_from_slice(bytes); Ok(Self { data }) } - /// Get a byte vector with the encrypted data contents - pub fn to_byte_vec(&self) -> Vec { - self.data.to_vec() + #[cfg(test)] + pub fn from_vec_unsafe(data: Vec) -> Self { + Self { data } } - /// Get a byte array with the encrypted data contents - pub fn to_bytes(&self) -> [u8; SIZE_TOTAL] { - self.data + /// Get a byte vector with the encrypted data contents + pub fn to_byte_vec(&self) -> Vec { + self.data.clone() } /// Get a byte slice with the encrypted data contents @@ -186,6 +272,12 @@ impl EncryptedData { } } } + + /// Returns the size of the payment id + pub fn get_payment_id_size(&self) -> usize { + // the length should always at least be the static total size, the extra len is the payment id + self.data.len().saturating_sub(STATIC_ENCRYPTED_DATA_SIZE_TOTAL) + } } impl Hex for EncryptedData { @@ -202,7 +294,7 @@ impl Hex for EncryptedData { impl Default for EncryptedData { fn default() -> Self { Self { - data: [0u8; SIZE_TOTAL], + data: vec![0; STATIC_ENCRYPTED_DATA_SIZE_TOTAL], } } } @@ -243,54 +335,93 @@ fn kdf_aead(encryption_key: &PrivateKey, commitment: &Commitment) -> EncryptedDa #[cfg(test)] mod test { - use rand::rngs::OsRng; - use tari_common_types::types::{CommitmentFactory, PrivateKey}; - use tari_crypto::{commitment::HomomorphicCommitmentFactory, keys::SecretKey}; + use tari_common_types::types::CommitmentFactory; + use tari_crypto::commitment::HomomorphicCommitmentFactory; use super::*; #[test] fn it_encrypts_and_decrypts_correctly() { - for (value, mask) in [ - (0, PrivateKey::default()), - (0, PrivateKey::random(&mut OsRng)), - (123456, PrivateKey::default()), - (654321, PrivateKey::random(&mut OsRng)), - (u64::MAX, PrivateKey::random(&mut OsRng)), + for payment_id in [ + PaymentId::Empty, + PaymentId::U64(1), + PaymentId::U64(156486946518564), + PaymentId::U256( + U256::from_dec_str("465465489789785458694894263185648978947864164681631").expect("Should not fail"), + ), + PaymentId::Address( + DualAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + ), + PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + PaymentId::Open(vec![1; 256]), ] { - let commitment = CommitmentFactory::default().commit(&mask, &PrivateKey::from(value)); - let encryption_key = PrivateKey::random(&mut OsRng); - let amount = MicroMinotari::from(value); - let encrypted_data = EncryptedData::encrypt_data(&encryption_key, &commitment, amount, &mask).unwrap(); - let (decrypted_value, decrypted_mask) = - EncryptedData::decrypt_data(&encryption_key, &commitment, &encrypted_data).unwrap(); - assert_eq!(amount, decrypted_value); - assert_eq!(mask, decrypted_mask); - if let Ok((decrypted_value, decrypted_mask)) = - EncryptedData::decrypt_data(&PrivateKey::random(&mut OsRng), &commitment, &encrypted_data) - { - assert_ne!(amount, decrypted_value); - assert_ne!(mask, decrypted_mask); + for (value, mask) in [ + (0, PrivateKey::default()), + (0, PrivateKey::random(&mut OsRng)), + (123456, PrivateKey::default()), + (654321, PrivateKey::random(&mut OsRng)), + (u64::MAX, PrivateKey::random(&mut OsRng)), + ] { + let commitment = CommitmentFactory::default().commit(&mask, &PrivateKey::from(value)); + let encryption_key = PrivateKey::random(&mut OsRng); + let amount = MicroMinotari::from(value); + let encrypted_data = + EncryptedData::encrypt_data(&encryption_key, &commitment, amount, &mask, payment_id.clone()) + .unwrap(); + let (decrypted_value, decrypted_mask, decrypted_payment_id) = + EncryptedData::decrypt_data(&encryption_key, &commitment, &encrypted_data).unwrap(); + assert_eq!(amount, decrypted_value); + assert_eq!(mask, decrypted_mask); + assert_eq!(payment_id, decrypted_payment_id); + if let Ok((decrypted_value, decrypted_mask, decrypted_payment_id)) = + EncryptedData::decrypt_data(&PrivateKey::random(&mut OsRng), &commitment, &encrypted_data) + { + assert_ne!(amount, decrypted_value); + assert_ne!(mask, decrypted_mask); + assert_ne!(payment_id, decrypted_payment_id); + } } } } #[test] fn it_converts_correctly() { - for (value, mask) in [ - (0, PrivateKey::default()), - (0, PrivateKey::random(&mut OsRng)), - (123456, PrivateKey::default()), - (654321, PrivateKey::random(&mut OsRng)), - (u64::MAX, PrivateKey::random(&mut OsRng)), + for payment_id in [ + PaymentId::Empty, + PaymentId::U64(1), + PaymentId::U64(156486946518564), + PaymentId::U256( + U256::from_dec_str("465465489789785458694894263185648978947864164681631").expect("Should not fail"), + ), + PaymentId::Address( + DualAddress::from_base58( + "f425UWsDp714RiN53c1G6ek57rfFnotB5NCMyrn4iDgbR8i2sXVHa4xSsedd66o9KmkRgErQnyDdCaAdNLzcKrj7eUb", + ) + .unwrap(), + ), + PaymentId::Open(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]), + PaymentId::Open(vec![1; 256]), ] { - let commitment = CommitmentFactory::default().commit(&mask, &PrivateKey::from(value)); - let encryption_key = PrivateKey::random(&mut OsRng); - let amount = MicroMinotari::from(value); - let encrypted_data = EncryptedData::encrypt_data(&encryption_key, &commitment, amount, &mask).unwrap(); - let bytes = encrypted_data.to_byte_vec(); - let encrypted_data_from_bytes = EncryptedData::from_bytes(&bytes).unwrap(); - assert_eq!(encrypted_data, encrypted_data_from_bytes); + for (value, mask) in [ + (0, PrivateKey::default()), + (0, PrivateKey::random(&mut OsRng)), + (123456, PrivateKey::default()), + (654321, PrivateKey::random(&mut OsRng)), + (u64::MAX, PrivateKey::random(&mut OsRng)), + ] { + let commitment = CommitmentFactory::default().commit(&mask, &PrivateKey::from(value)); + let encryption_key = PrivateKey::random(&mut OsRng); + let amount = MicroMinotari::from(value); + let encrypted_data = + EncryptedData::encrypt_data(&encryption_key, &commitment, amount, &mask, payment_id.clone()) + .unwrap(); + let bytes = encrypted_data.to_byte_vec(); + let encrypted_data_from_bytes = EncryptedData::from_bytes(&bytes).unwrap(); + assert_eq!(encrypted_data, encrypted_data_from_bytes); + } } } } diff --git a/base_layer/core/src/transactions/transaction_components/error.rs b/base_layer/core/src/transactions/transaction_components/error.rs index ab038bffa5..28e0bcaed4 100644 --- a/base_layer/core/src/transactions/transaction_components/error.rs +++ b/base_layer/core/src/transactions/transaction_components/error.rs @@ -23,6 +23,8 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. +#[cfg(feature = "ledger")] +use minotari_ledger_wallet_comms::error::LedgerDeviceError; use serde::{Deserialize, Serialize}; use tari_crypto::{ errors::RangeProofError, @@ -32,7 +34,7 @@ use tari_key_manager::key_manager_service::KeyManagerServiceError; use tari_script::ScriptError; use thiserror::Error; -use crate::transactions::{key_manager::LedgerDeviceError, transaction_components::EncryptedDataError}; +use crate::transactions::transaction_components::EncryptedDataError; //---------------------------------------- TransactionError ----------------------------------------------------// #[derive(Clone, Debug, PartialEq, Error, Deserialize, Serialize, Eq)] @@ -73,10 +75,15 @@ pub enum TransactionError { KeyManagerError(String), #[error("EncryptedData error: {0}")] EncryptedDataError(String), + #[cfg(feature = "ledger")] #[error("Ledger device error: {0}")] LedgerDeviceError(#[from] LedgerDeviceError), + #[error("Ledger is not supported")] + LedgerNotSupported, #[error("Transaction has a zero weight, not possible")] ZeroWeight, + #[error("Output with commitment {0} not found in transaction body")] + OutputNotFound(String), } impl From for TransactionError { diff --git a/base_layer/core/src/transactions/transaction_components/kernel_features.rs b/base_layer/core/src/transactions/transaction_components/kernel_features.rs index 099a07a056..b17521478e 100644 --- a/base_layer/core/src/transactions/transaction_components/kernel_features.rs +++ b/base_layer/core/src/transactions/transaction_components/kernel_features.rs @@ -81,7 +81,7 @@ mod test { ); let x = super::KernelFeatures::from_bits(4); assert_eq!(x, None); - for i in 5..=u8::max_value() { + for i in 5..=u8::MAX { assert_eq!(None, super::KernelFeatures::from_bits(i)); } } diff --git a/base_layer/core/src/transactions/transaction_components/range_proof_type.rs b/base_layer/core/src/transactions/transaction_components/range_proof_type.rs index 1d9e88d9ad..50fb8b1195 100644 --- a/base_layer/core/src/transactions/transaction_components/range_proof_type.rs +++ b/base_layer/core/src/transactions/transaction_components/range_proof_type.rs @@ -23,7 +23,10 @@ // Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License, // Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0. -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; use borsh::{BorshDeserialize, BorshSerialize}; use num_derive::FromPrimitive; @@ -74,6 +77,18 @@ impl Display for RangeProofType { } } +impl FromStr for RangeProofType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "bullet_proof_plus" => Ok(RangeProofType::BulletProofPlus), + "revealed_value" => Ok(RangeProofType::RevealedValue), + _ => Err("Invalid range proof type".to_string()), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/base_layer/core/src/transactions/transaction_components/test.rs b/base_layer/core/src/transactions/transaction_components/test.rs index 067ca86aef..0bec358517 100644 --- a/base_layer/core/src/transactions/transaction_components/test.rs +++ b/base_layer/core/src/transactions/transaction_components/test.rs @@ -20,8 +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 rand::{self, rngs::OsRng}; -use tari_common_types::types::{ComAndPubSignature, PrivateKey, PublicKey, Signature}; +use rand::rngs::OsRng; +use tari_common_types::types::{PrivateKey, Signature}; use tari_crypto::{ commitment::HomomorphicCommitmentFactory, keys::SecretKey as SecretKeyTrait, @@ -42,10 +42,14 @@ use crate::{ create_memory_db_key_manager_with_range_proof_size, TransactionKeyManagerInterface, }, - tari_amount::{uT, MicroMinotari, T}, + tari_amount::{uT, T}, test_helpers, test_helpers::{TestParams, UtxoTestParams}, - transaction_components::{transaction_output::batch_verify_range_proofs, EncryptedData, OutputFeatures}, + transaction_components::{ + encrypted_data::PaymentId, + transaction_output::batch_verify_range_proofs, + OutputFeatures, + }, transaction_protocol::TransactionProtocolError, CryptoFactories, }, @@ -55,7 +59,7 @@ use crate::{ #[tokio::test] async fn input_and_output_and_wallet_output_hash_match() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let i = test_params @@ -79,7 +83,7 @@ fn test_smt_hashes() { #[tokio::test] async fn key_manager_input() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let i = test_params @@ -97,14 +101,14 @@ async fn key_manager_input() { .expect("Should be able to create transaction output"); assert_eq!(*input.features().unwrap(), OutputFeatures::default()); - let (_, value) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); + let (_, value, _) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); assert_eq!(value, i.value); } #[tokio::test] async fn range_proof_verification() { let factories = CryptoFactories::new(32); - let key_manager = create_memory_db_key_manager_with_range_proof_size(32); + let key_manager = create_memory_db_key_manager_with_range_proof_size(32).unwrap(); // Directly test the tx_output verification let test_params_1 = TestParams::new(&key_manager).await; let test_params_2 = TestParams::new(&key_manager).await; @@ -123,22 +127,25 @@ async fn range_proof_verification() { let tx_output1 = wallet_output1.to_transaction_output(&key_manager).await.unwrap(); tx_output1.verify_range_proof(&factories.range_proof).unwrap(); let input_data = inputs!(test_params_2.script_key_pk.clone()); - let wallet_output2 = WalletOutputBuilder::new((2u64.pow(32) + 1u64).into(), test_params_2.spend_key_id.clone()) - .with_features(OutputFeatures::default()) - .with_script(script![Nop]) - .encrypt_data_for_recovery(&key_manager, None) - .await - .unwrap() - .with_input_data(input_data) - .with_covenant(Covenant::default()) - .with_version(TransactionOutputVersion::get_current_version()) - .with_sender_offset_public_key(test_params_2.sender_offset_key_pk.clone()) - .with_script_key(test_params_2.script_key_id.clone()) - .sign_as_sender_and_receiver(&key_manager, &test_params_2.sender_offset_key_id) - .await - .unwrap() - .try_build(&key_manager) - .await; + let wallet_output2 = WalletOutputBuilder::new( + (2u64.pow(32) + 1u64).into(), + test_params_2.commitment_mask_key_id.clone(), + ) + .with_features(OutputFeatures::default()) + .with_script(script![Nop]) + .encrypt_data_for_recovery(&key_manager, None, PaymentId::Empty) + .await + .unwrap() + .with_input_data(input_data) + .with_covenant(Covenant::default()) + .with_version(TransactionOutputVersion::get_current_version()) + .with_sender_offset_public_key(test_params_2.sender_offset_key_pk.clone()) + .with_script_key(test_params_2.script_key_id.clone()) + .sign_as_sender_and_receiver(&key_manager, &test_params_2.sender_offset_key_id) + .await + .unwrap() + .try_build(&key_manager) + .await; match wallet_output2 { Ok(_) => panic!("Range proof should have failed to verify"), @@ -164,7 +171,7 @@ async fn range_proof_verification() { #[tokio::test] async fn range_proof_verification_batch() { let factories = CryptoFactories::new(64); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let wallet_output1 = TestParams::new(&key_manager) .await .create_output( @@ -255,7 +262,7 @@ async fn range_proof_verification_batch() { #[tokio::test] async fn sender_signature_verification() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let wallet_output = test_params .create_output(Default::default(), &key_manager) @@ -416,7 +423,7 @@ fn check_timelocks() { #[tokio::test] async fn test_validate_internal_consistency() { let features = OutputFeatures { ..Default::default() }; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (tx, _, _) = test_helpers::create_tx(5000.into(), 3.into(), 1, 2, 1, 4, features, &key_manager) .await .expect("Failed to create tx"); @@ -429,7 +436,7 @@ async fn test_validate_internal_consistency() { #[tokio::test] #[allow(clippy::identity_op)] async fn check_cut_through() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (tx, _, outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2, Default::default(), &key_manager) .await @@ -486,7 +493,7 @@ async fn check_cut_through() { #[tokio::test] async fn check_duplicate_inputs_outputs() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (tx, _, _outputs) = test_helpers::create_tx(50000000.into(), 3.into(), 1, 2, 1, 2, Default::default(), &key_manager) .await @@ -509,7 +516,7 @@ async fn check_duplicate_inputs_outputs() { #[tokio::test] async fn inputs_not_malleable() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (inputs, outputs) = test_helpers::create_wallet_outputs( 5000.into(), 1, @@ -544,7 +551,7 @@ async fn inputs_not_malleable() { #[tokio::test] async fn test_output_recover_openings() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let v = MicroMinotari::from(42); @@ -560,23 +567,21 @@ async fn test_output_recover_openings() { .unwrap(); let output = wallet_output.to_transaction_output(&key_manager).await.unwrap(); - let (mask, value) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); + let (mask, value, _) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); assert_eq!(value, wallet_output.value); - assert_eq!(mask, test_params.spend_key_id); + assert_eq!(mask, test_params.commitment_mask_key_id); } mod validate_internal_consistency { - use blake2::Blake2b; - use digest::{consts::U32, Digest}; - use tari_common_types::types::FixedHash; + use digest::Digest; use tari_crypto::hashing::DomainSeparation; use super::*; use crate::{ covenants::{BaseLayerCovenantsDomain, COVENANTS_FIELD_HASHER_LABEL}, transactions::{ - key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, + key_manager::MemoryDbKeyManager, test_helpers::{create_transaction_with, create_wallet_outputs}, }, }; @@ -619,7 +624,7 @@ mod validate_internal_consistency { //---------------------------------- Case1 - PASS --------------------------------------------// let covenant = covenant!(fields_preserved(@fields( @field::covenant))); let features = OutputFeatures { ..Default::default() }; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); test_case( &UtxoTestParams { features: features.clone(), diff --git a/base_layer/core/src/transactions/transaction_components/transaction_input.rs b/base_layer/core/src/transactions/transaction_components/transaction_input.rs index 8186dbabd6..d659e4bfe4 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_input.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_input.rs @@ -383,6 +383,7 @@ impl TransactionInput { script_public_key, commitment, ); + if self.script_signature.verify_challenge( commitment, script_public_key, diff --git a/base_layer/core/src/transactions/transaction_components/transaction_output.rs b/base_layer/core/src/transactions/transaction_components/transaction_output.rs index 49fd8fa53d..0dc042326a 100644 --- a/base_layer/core/src/transactions/transaction_components/transaction_output.rs +++ b/base_layer/core/src/transactions/transaction_components/transaction_output.rs @@ -196,7 +196,7 @@ impl TransactionOutput { } } } else { - "None".to_string() + format!("None({})", self.minimum_value_promise) } } @@ -318,6 +318,7 @@ impl TransactionOutput { &self.encrypted_data, self.minimum_value_promise, ); + if !self.metadata_signature.verify_challenge( &self.commitment, &self.sender_offset_public_key, @@ -471,7 +472,8 @@ impl TransactionOutput { pub fn get_features_and_scripts_size(&self) -> std::io::Result { Ok(self.features.get_serialized_size()? + self.script.get_serialized_size()? + - self.covenant.get_serialized_size()?) + self.covenant.get_serialized_size()? + + self.encrypted_data.get_payment_id_size()) } } @@ -581,7 +583,7 @@ mod test { #[tokio::test] async fn it_builds_correctly() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let value = MicroMinotari(10); @@ -598,14 +600,14 @@ mod test { assert!(tx_output.verify_range_proof(&factories.range_proof).is_ok()); assert!(tx_output.verify_metadata_signature().is_ok()); - let (_, recovered_value) = key_manager.try_output_key_recovery(&tx_output, None).await.unwrap(); + let (_, recovered_value, _) = key_manager.try_output_key_recovery(&tx_output, None).await.unwrap(); assert_eq!(recovered_value, value); } #[tokio::test] async fn it_does_not_verify_incorrect_minimum_value() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let value = MicroMinotari(10); @@ -625,7 +627,7 @@ mod test { #[tokio::test] async fn it_does_batch_verify_correct_minimum_values() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let outputs = [ @@ -663,7 +665,7 @@ mod test { #[tokio::test] async fn it_does_batch_verify_with_mixed_range_proof_types() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let factories = CryptoFactories::default(); let test_params = TestParams::new(&key_manager).await; @@ -711,7 +713,7 @@ mod test { #[tokio::test] async fn invalid_revealed_value_proofs_are_blocked() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; assert!(create_output( &test_params, @@ -742,7 +744,7 @@ mod test { #[tokio::test] async fn revealed_value_proofs_only_succeed_with_valid_metadata_signatures() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let mut output = create_output( &test_params, @@ -769,7 +771,7 @@ mod test { #[tokio::test] async fn it_does_not_batch_verify_incorrect_minimum_values() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let outputs = [ diff --git a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs index b3c2b0650c..d20047143c 100644 --- a/base_layer/core/src/transactions/transaction_components/unblinded_output.rs +++ b/base_layer/core/src/transactions/transaction_components/unblinded_output.rs @@ -29,16 +29,22 @@ use std::{ }; use serde::{Deserialize, Serialize}; -use tari_common_types::types::{ComAndPubSignature, PrivateKey, PublicKey}; +use tari_common_types::types::{ComAndPubSignature, PrivateKey, PublicKey, RangeProof}; use tari_script::{ExecutionStack, TariScript}; -use super::TransactionOutputVersion; +use super::{RangeProofType, TransactionOutputVersion}; use crate::{ covenants::Covenant, transactions::{ key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, - transaction_components::{EncryptedData, OutputFeatures, TransactionError, WalletOutput}, + transaction_components::{ + encrypted_data::PaymentId, + EncryptedData, + OutputFeatures, + TransactionError, + WalletOutput, + }, }, }; @@ -60,6 +66,7 @@ pub struct UnblindedOutput { pub script_lock_height: u64, pub encrypted_data: EncryptedData, pub minimum_value_promise: MicroMinotari, + pub range_proof: Option, } impl UnblindedOutput { @@ -80,7 +87,13 @@ impl UnblindedOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + range_proof: Option, ) -> Self { + let range_proof = if features.range_proof_type == RangeProofType::BulletProofPlus { + range_proof + } else { + None + }; Self { version, value, @@ -95,9 +108,11 @@ impl UnblindedOutput { covenant, encrypted_data, minimum_value_promise, + range_proof, } } + #[allow(clippy::too_many_arguments)] pub fn new_current_version( value: MicroMinotari, spending_key: PrivateKey, @@ -111,6 +126,7 @@ impl UnblindedOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + range_proof: Option, ) -> Self { Self::new( TransactionOutputVersion::get_current_version(), @@ -126,16 +142,18 @@ impl UnblindedOutput { covenant, encrypted_data, minimum_value_promise, + range_proof, ) } pub async fn to_wallet_output( self, key_manager: &KM, + payment_id: PaymentId, ) -> Result { let spending_key_id = key_manager.import_key(self.spending_key).await?; let script_key_id = key_manager.import_key(self.script_private_key).await?; - let wallet_output = WalletOutput::new( + let wallet_output = WalletOutput::new_with_rangeproof( self.version, self.value, spending_key_id, @@ -149,9 +167,9 @@ impl UnblindedOutput { self.covenant, self.encrypted_data, self.minimum_value_promise, - key_manager, - ) - .await?; + self.range_proof, + payment_id, + ); Ok(wallet_output) } @@ -161,6 +179,11 @@ impl UnblindedOutput { ) -> Result { let spending_key = key_manager.get_private_key(&output.spending_key_id).await?; let script_private_key = key_manager.get_private_key(&output.script_key_id).await?; + let range_proof = if output.features.range_proof_type == RangeProofType::BulletProofPlus { + output.range_proof + } else { + None + }; let unblinded_output = UnblindedOutput { version: output.version, value: output.value, @@ -175,6 +198,7 @@ impl UnblindedOutput { script_lock_height: output.script_lock_height, encrypted_data: output.encrypted_data, minimum_value_promise: output.minimum_value_promise, + range_proof, }; Ok(unblinded_output) } diff --git a/base_layer/core/src/transactions/transaction_components/wallet_output.rs b/base_layer/core/src/transactions/transaction_components/wallet_output.rs index 955e4178ae..ec1d50ca8e 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output.rs @@ -41,6 +41,7 @@ use crate::{ tari_amount::MicroMinotari, transaction_components, transaction_components::{ + encrypted_data::PaymentId, transaction_input::{SpentOutput, TransactionInput}, transaction_output::TransactionOutput, EncryptedData, @@ -70,7 +71,8 @@ pub struct WalletOutput { pub script_lock_height: u64, pub encrypted_data: EncryptedData, pub minimum_value_promise: MicroMinotari, - pub rangeproof: Option, + pub range_proof: Option, + pub payment_id: PaymentId, } impl WalletOutput { @@ -91,9 +93,10 @@ impl WalletOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + payment_id: PaymentId, key_manager: &KM, ) -> Result { - let rangeproof = if features.range_proof_type == RangeProofType::BulletProofPlus { + let range_proof = if features.range_proof_type == RangeProofType::BulletProofPlus { Some( key_manager .construct_range_proof(&spending_key_id, value.into(), minimum_value_promise.into()) @@ -116,7 +119,8 @@ impl WalletOutput { covenant, encrypted_data, minimum_value_promise, - rangeproof, + range_proof, + payment_id, }) } @@ -136,6 +140,7 @@ impl WalletOutput { encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, rangeproof: Option, + payment_id: PaymentId, ) -> Self { Self { version, @@ -151,7 +156,8 @@ impl WalletOutput { covenant, encrypted_data, minimum_value_promise, - rangeproof, + range_proof: rangeproof, + payment_id, } } @@ -169,6 +175,7 @@ impl WalletOutput { covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + payment_id: PaymentId, key_manager: &KM, ) -> Result { Self::new( @@ -185,6 +192,7 @@ impl WalletOutput { covenant, encrypted_data, minimum_value_promise, + payment_id, key_manager, ) .await @@ -197,7 +205,7 @@ impl WalletOutput { ) -> Result { let value = self.value.into(); let commitment = key_manager.get_commitment(&self.spending_key_id, &value).await?; - let rangeproof_hash = match &self.rangeproof { + let rangeproof_hash = match &self.range_proof { Some(rp) => rp.hash(), None => FixedHash::zero(), }; @@ -221,7 +229,7 @@ impl WalletOutput { sender_offset_public_key: self.sender_offset_public_key.clone(), covenant: self.covenant.clone(), version: self.version, - encrypted_data: self.encrypted_data, + encrypted_data: self.encrypted_data.clone(), metadata_signature: self.metadata_signature.clone(), rangeproof_hash, minimum_value_promise: self.minimum_value_promise, @@ -231,6 +239,70 @@ impl WalletOutput { )) } + /// It creates a transaction input given an updated multi-party script public keys and nonces. The inputs + /// `script_signature_public_nonces` and `script_public_key_shares` exclude the caller's data. + pub async fn to_transaction_input_with_multi_party_script_signature( + &self, + aggregated_script_signature_public_nonces: &PublicKey, + aggregated_script_public_key_shares: &PublicKey, + key_manager: &KM, + ) -> Result<(TransactionInput, PublicKey), TransactionError> { + let value = self.value.into(); + let version = TransactionInputVersion::get_current_version(); + let commitment = key_manager.get_commitment(&self.spending_key_id, &value).await?; + + let message = TransactionInput::build_script_signature_message(&version, &self.script, &self.input_data); + let ephemeral_public_key_self = key_manager.get_random_key().await?; + let script_public_key_self = key_manager.get_public_key_at_key_id(&self.script_key_id).await?; + let script_public_key = aggregated_script_public_key_shares + script_public_key_self; + + let total_ephemeral_public_key = aggregated_script_signature_public_nonces + &ephemeral_public_key_self.pub_key; + let commitment_partial_script_signature = key_manager + .get_partial_script_signature( + &self.spending_key_id, + &value, + &version, + &total_ephemeral_public_key, + &script_public_key, + &message, + ) + .await?; + let challenge = TransactionInput::finalize_script_signature_challenge( + &version, + commitment_partial_script_signature.ephemeral_commitment(), + &total_ephemeral_public_key, + &script_public_key, + &commitment, + &message, + ); + let script_key_partial_script_signature = key_manager + .sign_with_nonce_and_message(&self.script_key_id, &ephemeral_public_key_self.key_id, &challenge) + .await?; + let script_signature = &commitment_partial_script_signature + &script_key_partial_script_signature; + + let input = TransactionInput::new_current_version( + SpentOutput::OutputData { + features: self.features.clone(), + commitment, + script: self.script.clone(), + sender_offset_public_key: self.sender_offset_public_key.clone(), + covenant: self.covenant.clone(), + encrypted_data: self.encrypted_data.clone(), + metadata_signature: self.metadata_signature.clone(), + version: self.version, + minimum_value_promise: self.minimum_value_promise, + rangeproof_hash: match &self.range_proof { + Some(rp) => rp.hash(), + None => FixedHash::zero(), + }, + }, + self.input_data.clone(), + script_signature, + ); + + Ok((input, script_public_key)) + } + /// Commits an WalletOutput into a TransactionInput that only contains the hash of the spent output data pub async fn to_compact_transaction_input( &self, @@ -263,12 +335,12 @@ impl WalletOutput { self.version, self.features.clone(), commitment, - self.rangeproof.clone(), + self.range_proof.clone(), self.script.clone(), self.sender_offset_public_key.clone(), self.metadata_signature.clone(), self.covenant.clone(), - self.encrypted_data, + self.encrypted_data.clone(), self.minimum_value_promise, ); diff --git a/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs b/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs index 2eaf9ebb9a..7280e139b3 100644 --- a/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs +++ b/base_layer/core/src/transactions/transaction_components/wallet_output_builder.rs @@ -27,9 +27,10 @@ use tari_script::{ExecutionStack, TariScript}; use crate::{ covenants::Covenant, transactions::{ - key_manager::{TariKeyId, TransactionKeyManagerInterface}, + key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, OutputFeatures, TransactionError, @@ -45,9 +46,10 @@ use crate::{ pub struct WalletOutputBuilder { version: TransactionOutputVersion, value: MicroMinotari, - spending_key_id: TariKeyId, + commitment_mask_key_id: TariKeyId, features: OutputFeatures, script: Option, + script_lock_height: u64, covenant: Covenant, input_data: Option, script_key_id: Option, @@ -58,17 +60,19 @@ pub struct WalletOutputBuilder { encrypted_data: EncryptedData, custom_recovery_key_id: Option, minimum_value_promise: MicroMinotari, + payment_id: PaymentId, } #[allow(dead_code)] impl WalletOutputBuilder { - pub fn new(value: MicroMinotari, spending_key_id: TariKeyId) -> Self { + pub fn new(value: MicroMinotari, commitment_mask_key_id: TariKeyId) -> Self { Self { version: TransactionOutputVersion::get_current_version(), value, - spending_key_id, + commitment_mask_key_id, features: OutputFeatures::default(), script: None, + script_lock_height: 0, covenant: Covenant::default(), input_data: None, script_key_id: None, @@ -79,6 +83,7 @@ impl WalletOutputBuilder { encrypted_data: EncryptedData::default(), custom_recovery_key_id: None, minimum_value_promise: MicroMinotari::zero(), + payment_id: PaymentId::Empty, } } @@ -97,6 +102,11 @@ impl WalletOutputBuilder { self } + pub fn with_script_lock_height(mut self, height: u64) -> Self { + self.script_lock_height = height; + self + } + pub fn with_input_data(mut self, input_data: ExecutionStack) -> Self { self.input_data = Some(input_data); self @@ -111,9 +121,15 @@ impl WalletOutputBuilder { mut self, key_manager: &KM, custom_recovery_key_id: Option<&TariKeyId>, + payment_id: PaymentId, ) -> Result { self.encrypted_data = key_manager - .encrypt_data_for_recovery(&self.spending_key_id, custom_recovery_key_id, self.value.as_u64()) + .encrypt_data_for_recovery( + &self.commitment_mask_key_id, + custom_recovery_key_id, + self.value.as_u64(), + payment_id, + ) .await?; Ok(self) } @@ -169,7 +185,7 @@ impl WalletOutputBuilder { ); let metadata_signature = key_manager .get_metadata_signature( - &self.spending_key_id, + &self.commitment_mask_key_id, &self.value.into(), sender_offset_key_id, &self.version, @@ -184,6 +200,74 @@ impl WalletOutputBuilder { Ok(self) } + /// Sign a partial multi-party metadata signature as the sender and receiver - `sender_offset_public_key_shares` and + /// `ephemeral_pubkey_shares` from other participants are combined to enable creation of the challenge. + pub async fn sign_partial_as_sender_and_receiver( + mut self, + key_manager: &KM, + sender_offset_key_id: &TariKeyId, + aggregated_sender_offset_public_key_shares: &PublicKey, + aggregated_ephemeral_public_key_shares: &PublicKey, + ) -> Result { + let script = self + .script + .as_ref() + .ok_or_else(|| TransactionError::BuilderError("Cannot sign metadata without a script".to_string()))?; + let metadata_message = TransactionOutput::metadata_signature_message_from_parts( + &self.version, + script, + &self.features, + &self.covenant, + &self.encrypted_data, + &self.minimum_value_promise, + ); + + let sender_offset_public_key_self = key_manager.get_public_key_at_key_id(sender_offset_key_id).await?; + let aggregate_sender_offset_public_key = + aggregated_sender_offset_public_key_shares + &sender_offset_public_key_self; + + let ephemeral_pubkey_self = key_manager + .get_next_key(TransactionKeyManagerBranch::MetadataEphemeralNonce.get_branch_key()) + .await?; + let aggregate_ephemeral_pubkey = aggregated_ephemeral_public_key_shares + &ephemeral_pubkey_self.pub_key; + + let receiver_partial_metadata_signature = key_manager + .get_receiver_partial_metadata_signature( + &self.commitment_mask_key_id, + &self.value.into(), + &aggregate_sender_offset_public_key, + &aggregate_ephemeral_pubkey, + &TransactionOutputVersion::get_current_version(), + &metadata_message, + self.features.range_proof_type, + ) + .await?; + + let commitment = key_manager + .get_commitment(&self.commitment_mask_key_id, &self.value.into()) + .await?; + let ephemeral_commitment = receiver_partial_metadata_signature.ephemeral_commitment(); + let challenge = TransactionOutput::finalize_metadata_signature_challenge( + &TransactionOutputVersion::get_current_version(), + &aggregate_sender_offset_public_key, + ephemeral_commitment, + &aggregate_ephemeral_pubkey, + &commitment, + &metadata_message, + ); + let sender_partial_metadata_signature_self = key_manager + .sign_with_nonce_and_message(sender_offset_key_id, &ephemeral_pubkey_self.key_id, &challenge) + .await?; + + let metadata_signature = &receiver_partial_metadata_signature + &sender_partial_metadata_signature_self; + + self.metadata_signature = Some(metadata_signature); + self.metadata_signed_by_receiver = true; + self.metadata_signed_by_sender = true; + self.sender_offset_public_key = Some(aggregate_sender_offset_public_key); + Ok(self) + } + pub async fn try_build( self, key_manager: &KM, @@ -201,7 +285,7 @@ impl WalletOutputBuilder { let ub = WalletOutput::new( self.version, self.value, - self.spending_key_id, + self.commitment_mask_key_id, self.features, self.script .ok_or_else(|| TransactionError::BuilderError("script must be set".to_string()))?, @@ -213,10 +297,11 @@ impl WalletOutputBuilder { .ok_or_else(|| TransactionError::BuilderError("sender_offset_public_key must be set".to_string()))?, self.metadata_signature .ok_or_else(|| TransactionError::BuilderError("metadata_signature must be set".to_string()))?, - 0, + self.script_lock_height, self.covenant, self.encrypted_data, self.minimum_value_promise, + self.payment_id, key_manager, ) .await?; @@ -229,30 +314,30 @@ mod test { use tari_key_manager::key_manager_service::KeyManagerInterface; use super::*; - use crate::transactions::key_manager::{create_memory_db_key_manager, TransactionKeyManagerBranch}; + use crate::transactions::key_manager::create_memory_db_key_manager; #[tokio::test] async fn test_try_build() { - let key_manager = create_memory_db_key_manager(); - let (spending_key_id, _, script_key_id, _) = key_manager.get_next_spend_and_script_key_ids().await.unwrap(); + let key_manager = create_memory_db_key_manager().unwrap(); + let (commitment_mask_key, script_key_id) = key_manager.get_next_commitment_mask_and_script_key().await.unwrap(); let value = MicroMinotari(100); - let kmob = WalletOutputBuilder::new(value, spending_key_id.clone()); + let kmob = WalletOutputBuilder::new(value, commitment_mask_key.key_id.clone()); let kmob = kmob.with_script(TariScript::new(vec![])); assert!(kmob.clone().try_build(&key_manager).await.is_err()); - let (sender_offset_private_key_id, sender_offset_public_key) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); - let kmob = kmob.with_sender_offset_public_key(sender_offset_public_key); + let kmob = kmob.with_sender_offset_public_key(sender_offset.pub_key); assert!(kmob.clone().try_build(&key_manager).await.is_err()); let kmob = kmob.with_input_data(ExecutionStack::new(vec![])); - let kmob = kmob.with_script_key(script_key_id); + let kmob = kmob.with_script_key(script_key_id.key_id); let kmob = kmob.with_features(OutputFeatures::default()); let kmob = kmob - .encrypt_data_for_recovery(&key_manager, None) + .encrypt_data_for_recovery(&key_manager, None, PaymentId::Empty) .await .unwrap() - .sign_as_sender_and_receiver(&key_manager, &sender_offset_private_key_id) + .sign_as_sender_and_receiver(&key_manager, &sender_offset.key_id) .await .unwrap(); match kmob.clone().try_build(&key_manager).await { @@ -260,13 +345,13 @@ mod test { let output = val.to_transaction_output(&key_manager).await.unwrap(); assert!(output.verify_metadata_signature().is_ok()); assert!(key_manager - .verify_mask(output.commitment(), &spending_key_id, value.into()) + .verify_mask(output.commitment(), &commitment_mask_key.key_id, value.into()) .await .unwrap()); - let (recovered_key_id, recovered_value) = + let (recovered_key_id, recovered_value, _) = key_manager.try_output_key_recovery(&output, None).await.unwrap(); - assert_eq!(recovered_key_id, spending_key_id); + assert_eq!(recovered_key_id, commitment_mask_key.key_id); assert_eq!(recovered_value, value); }, Err(e) => panic!("{}", e), @@ -275,24 +360,24 @@ mod test { #[tokio::test] async fn test_partial_metadata_signatures() { - let key_manager = create_memory_db_key_manager(); - let (spending_key_id, _, script_key_id, _) = key_manager.get_next_spend_and_script_key_ids().await.unwrap(); + let key_manager = create_memory_db_key_manager().unwrap(); + let (commitment_mask_key, script_key) = key_manager.get_next_commitment_mask_and_script_key().await.unwrap(); let value = MicroMinotari(100); - let kmob = WalletOutputBuilder::new(value, spending_key_id.clone()); + let kmob = WalletOutputBuilder::new(value, commitment_mask_key.key_id.clone()); let kmob = kmob.with_script(TariScript::new(vec![])); - let (sender_offset_private_key_id, sender_offset_public_key) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); - let kmob = kmob.with_sender_offset_public_key(sender_offset_public_key); + let kmob = kmob.with_sender_offset_public_key(sender_offset.pub_key); let kmob = kmob.with_input_data(ExecutionStack::new(vec![])); - let kmob = kmob.with_script_key(script_key_id); + let kmob = kmob.with_script_key(script_key.key_id); let kmob = kmob.with_features(OutputFeatures::default()); let kmob = kmob - .encrypt_data_for_recovery(&key_manager, None) + .encrypt_data_for_recovery(&key_manager, None, PaymentId::Empty) .await .unwrap() - .sign_as_sender_and_receiver(&key_manager, &sender_offset_private_key_id) + .sign_as_sender_and_receiver(&key_manager, &sender_offset.key_id) .await .unwrap(); match kmob.clone().try_build(&key_manager).await { @@ -301,7 +386,7 @@ mod test { assert!(output.verify_metadata_signature().is_ok()); // Now we can swap out the metadata signature for one built from partial sender and receiver signatures - let (ephemeral_pubkey_id, ephemeral_pubkey) = key_manager + let ephemeral_key = key_manager .get_next_key(TransactionKeyManagerBranch::Nonce.get_branch_key()) .await .unwrap(); @@ -312,7 +397,7 @@ mod test { &wallet_output.spending_key_id, &wallet_output.value.into(), &wallet_output.sender_offset_public_key, - &ephemeral_pubkey, + &ephemeral_key.pub_key, &wallet_output.version, &metadata_message, wallet_output.features.range_proof_type, @@ -326,8 +411,8 @@ mod test { .unwrap(); let sender_metadata_signature = key_manager .get_sender_partial_metadata_signature( - &ephemeral_pubkey_id, - &sender_offset_private_key_id, + &ephemeral_key.key_id, + &sender_offset.key_id, &commitment, receiver_metadata_signature.ephemeral_commitment(), &wallet_output.version, diff --git a/base_layer/core/src/transactions/transaction_protocol/mod.rs b/base_layer/core/src/transactions/transaction_protocol/mod.rs index 2cc8781464..aa13274328 100644 --- a/base_layer/core/src/transactions/transaction_protocol/mod.rs +++ b/base_layer/core/src/transactions/transaction_protocol/mod.rs @@ -85,9 +85,7 @@ // #![allow(clippy::op_ref)] -use blake2::Blake2b; use derivative::Derivative; -use digest::consts::U32; use serde::{Deserialize, Serialize}; use tari_common_types::types::PrivateKey; use tari_crypto::{errors::RangeProofError, signatures::SchnorrSignatureError}; @@ -101,7 +99,6 @@ pub mod sender; pub mod single_receiver; pub mod transaction_initializer; use tari_common_types::types::Commitment; -use tari_crypto::{hash_domain, hashing::DomainSeparatedHasher}; use tari_key_manager::key_manager_service::KeyManagerServiceError; use crate::transactions::transaction_components::KernelFeatures; @@ -198,13 +195,3 @@ impl TransactionMetadata { pub struct RecoveryData { pub encryption_key: PrivateKey, } - -// hash domain -hash_domain!( - CalculateTxIdTransactionProtocolHashDomain, - "com.tari.base_layer.core.transactions.transaction_protocol.calculate_tx_id", - 1 -); - -pub type CalculateTxIdTransactionProtocolHasherBlake256 = - DomainSeparatedHasher, CalculateTxIdTransactionProtocolHashDomain>; diff --git a/base_layer/core/src/transactions/transaction_protocol/recipient.rs b/base_layer/core/src/transactions/transaction_protocol/recipient.rs index 115e5edba8..e516c3d031 100644 --- a/base_layer/core/src/transactions/transaction_protocol/recipient.rs +++ b/base_layer/core/src/transactions/transaction_protocol/recipient.rs @@ -25,7 +25,7 @@ use std::fmt; use serde::{Deserialize, Serialize}; use tari_common_types::{ transaction::TxId, - types::{FixedHash, PrivateKey, PublicKey, Signature}, + types::{PrivateKey, PublicKey, Signature}, }; use crate::{ @@ -63,25 +63,6 @@ impl fmt::Display for RecipientState { } } -/// An enum describing the types of information that a recipient can send back to the receiver -#[derive(Debug, Clone, PartialEq)] -pub(super) enum RecipientInfo { - Single(Option>), -} - -#[allow(clippy::derivable_impls)] -impl Default for RecipientInfo { - fn default() -> Self { - RecipientInfo::Single(None) - } -} - -#[derive(Debug, Clone, PartialEq)] -pub(super) struct MultiRecipientInfo { - pub commitment: FixedHash, - pub data: RecipientSignedMessage, -} - /// This is the message containing the public data that the Receiver will send back to the Sender #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RecipientSignedMessage { @@ -218,7 +199,7 @@ mod test { #[tokio::test] async fn single_round_recipient() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let factories = CryptoFactories::default(); let sender_test_params = TestParams::new(&key_manager).await; let m = TransactionMetadata::new(MicroMinotari(125), 0); @@ -228,8 +209,8 @@ mod test { let msg = SingleRoundSenderData { tx_id: 15u64.into(), amount, - public_excess: sender_test_params.spend_key_pk, // any random key will do - public_nonce: sender_test_params.public_nonce_key_pk, // any random key will do + public_excess: sender_test_params.kernel_nonce_key_pk, // any random key will do + public_nonce: sender_test_params.public_nonce_key_pk, // any random key will do metadata: m.clone(), message: "".to_string(), features, @@ -255,7 +236,7 @@ mod test { assert!(receiver.is_finalized()); let data = receiver.get_signed_data().unwrap(); let pubkey = key_manager - .get_public_key_at_key_id(&receiver_test_params.spend_key_id) + .get_public_key_at_key_id(&receiver_test_params.commitment_mask_key_id) .await .unwrap(); let offset = data.offset.clone(); @@ -264,7 +245,7 @@ mod test { assert_eq!(data.tx_id.as_u64(), 15); assert_eq!(data.public_spend_key, signing_pubkey); let commitment = key_manager - .get_commitment(&receiver_test_params.spend_key_id, &500.into()) + .get_commitment(&receiver_test_params.commitment_mask_key_id, &500.into()) .await .unwrap(); assert_eq!(&commitment, &data.output.commitment); @@ -289,15 +270,15 @@ mod test { &m.burn_commitment, ); let p_nonce = key_manager.get_public_key_at_key_id(&nonce_id).await.unwrap(); - let p_spend_key = key_manager - .get_txo_kernel_signature_excess_with_offset(&receiver_test_params.spend_key_id, &nonce_id) + let p_commitment_mask_key = key_manager + .get_txo_kernel_signature_excess_with_offset(&receiver_test_params.commitment_mask_key_id, &nonce_id) .await .unwrap(); let r_sum = &msg.public_nonce + &p_nonce; - let excess = &msg.public_excess + &p_spend_key; + let excess = &msg.public_excess + &p_commitment_mask_key; let kernel_signature = key_manager .get_partial_txo_kernel_signature( - &receiver_test_params.spend_key_id, + &receiver_test_params.commitment_mask_key_id, &nonce_id, &r_sum, &excess, @@ -310,7 +291,7 @@ mod test { .unwrap(); assert_eq!(data.partial_signature, kernel_signature); - let (mask, value) = key_manager.try_output_key_recovery(&data.output, None).await.unwrap(); + let (mask, value, _) = key_manager.try_output_key_recovery(&data.output, None).await.unwrap(); assert_eq!(output.spending_key_id, mask); assert_eq!(output.value, value); } diff --git a/base_layer/core/src/transactions/transaction_protocol/sender.rs b/base_layer/core/src/transactions/transaction_protocol/sender.rs index 2e6ae88294..af5a275ac8 100644 --- a/base_layer/core/src/transactions/transaction_protocol/sender.rs +++ b/base_layer/core/src/transactions/transaction_protocol/sender.rs @@ -27,11 +27,10 @@ use tari_common_types::{ transaction::TxId, types::{ComAndPubSignature, PrivateKey, PublicKey, Signature}, }; -use tari_crypto::{ristretto::pedersen::PedersenCommitment, tari_utilities::ByteArray}; +use tari_crypto::ristretto::pedersen::PedersenCommitment; pub use tari_key_manager::key_manager_service::KeyId; use tari_script::TariScript; -use super::CalculateTxIdTransactionProtocolHasherBlake256; use crate::{ consensus::ConsensusConstants, covenants::Covenant, @@ -343,6 +342,22 @@ impl SenderTransactionProtocol { } } + pub fn change_recipient_sender_offset_private_key(&mut self, key_id: TariKeyId) -> Result<(), TPE> { + match &mut self.state { + SenderState::Initializing(ref mut info) | + SenderState::Finalizing(ref mut info) | + SenderState::SingleRoundMessageReady(ref mut info) | + SenderState::CollectingSingleSignature(ref mut info) => { + if let Some(ref mut v) = info.recipient_data { + v.recipient_sender_offset_key_id = key_id; + } + }, + SenderState::FinalizedTransaction(_) | SenderState::Failed(_) => return Err(TPE::InvalidStateError), + } + + Ok(()) + } + /// This function will return the value of the fee of this transaction pub fn get_fee_amount(&self) -> Result { match &self.state { @@ -472,7 +487,7 @@ impl SenderTransactionProtocol { Ok((public_nonce, public_excess)) } - /// Add partial signatures, add the the recipient info to sender state and move to the Finalizing state + /// Add partial signatures, add the recipient info to sender state and move to the Finalizing state pub async fn add_single_recipient_info( &mut self, mut rec: RecipientSignedMessage, @@ -724,10 +739,8 @@ impl SenderTransactionProtocol { /// transaction was valid or not. If the result is false, the transaction will be in a Failed state. Calling /// finalize while in any other state will result in an error. /// - /// First we validate against internal sanity checks, then try build the transaction, and then - /// formally validate the transaction terms (no inflation, signature matches etc). If any step fails, - /// the transaction protocol moves to Failed state and we are done; you can't rescue the situation. The function - /// returns `Ok(false)` in this instance. + /// First we validate against internal sanity checks, then try build the transaction. If any step fails, + /// the transaction protocol moves to Failed state and we are done; you can't rescue the situation. pub async fn finalize(&mut self, key_manager: &KM) -> Result<(), TPE> { match &self.state { SenderState::Finalizing(info) => { @@ -776,16 +789,6 @@ impl fmt::Display for SenderTransactionProtocol { } } -pub fn calculate_tx_id(pub_nonce: &PublicKey, index: usize) -> TxId { - let hash = CalculateTxIdTransactionProtocolHasherBlake256::new() - .chain(pub_nonce.as_bytes()) - .chain(index.to_le_bytes()) - .finalize(); - let mut bytes: [u8; 8] = [0u8; 8]; - bytes.copy_from_slice(&hash.as_ref()[..8]); - u64::from_le_bytes(bytes).into() -} - //---------------------------------------- Sender State ----------------------------------------------------// /// This enum contains all the states of the Sender state machine @@ -883,6 +886,7 @@ mod test { tari_amount::*, test_helpers::{create_test_input, create_wallet_output_with_data, TestParams}, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, OutputFeatures, TransactionOutput, @@ -906,7 +910,7 @@ mod test { #[tokio::test] async fn test_errors() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let stp = SenderTransactionProtocol { state: SenderState::Failed(TransactionProtocolError::InvalidStateError), }; @@ -951,15 +955,15 @@ mod test { #[tokio::test] async fn test_metadata_signature_finalize() { // Defaults - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); // Sender data - let (ephemeral_pubkey_id, ephemeral_pubkey) = key_manager + let ephemeral_key = key_manager .get_next_key(TransactionKeyManagerBranch::Nonce.get_branch_key()) .await .unwrap(); let value = 1000u64; - let (sender_offset_key_id, sender_offset_public_key) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); @@ -970,21 +974,21 @@ mod test { let output_features = Default::default(); // Receiver data - let (spending_key_id, _, _script_key_id, _) = key_manager.get_next_spend_and_script_key_ids().await.unwrap(); + let (commitment_mask_key, _) = key_manager.get_next_commitment_mask_and_script_key().await.unwrap(); let commitment = key_manager - .get_commitment(&spending_key_id, &PrivateKey::from(value)) + .get_commitment(&commitment_mask_key.key_id, &PrivateKey::from(value)) .await .unwrap(); let minimum_value_promise = MicroMinotari::zero(); let proof = key_manager - .construct_range_proof(&spending_key_id, value, minimum_value_promise.into()) + .construct_range_proof(&commitment_mask_key.key_id, value, minimum_value_promise.into()) .await .unwrap(); let covenant = Covenant::default(); // Encrypted value let encrypted_data = key_manager - .encrypt_data_for_recovery(&spending_key_id, None, value) + .encrypt_data_for_recovery(&commitment_mask_key.key_id, None, value, PaymentId::Empty) .await .unwrap(); @@ -998,10 +1002,10 @@ mod test { ); let partial_metadata_signature = key_manager .get_receiver_partial_metadata_signature( - &spending_key_id, + &commitment_mask_key.key_id, &value.into(), - &sender_offset_public_key, - &ephemeral_pubkey, + &sender_offset.pub_key, + &ephemeral_key.pub_key, &txo_version, &metadata_message, output_features.range_proof_type, @@ -1014,7 +1018,7 @@ mod test { commitment, Some(proof), script.clone(), - sender_offset_public_key, + sender_offset.pub_key, partial_metadata_signature.clone(), covenant.clone(), encrypted_data, @@ -1025,8 +1029,8 @@ mod test { // Sender finalize transaction output let partial_sender_metadata_signature = key_manager .get_sender_partial_metadata_signature( - &ephemeral_pubkey_id, - &sender_offset_key_id, + &ephemeral_key.key_id, + &sender_offset.key_id, &output.commitment, partial_metadata_signature.ephemeral_commitment(), &txo_version, @@ -1040,7 +1044,7 @@ mod test { #[tokio::test] async fn zero_recipients() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p1 = TestParams::new(&key_manager).await; let p2 = TestParams::new(&key_manager).await; let input = create_test_input(MicroMinotari(1200), 0, &key_manager, vec![]).await; @@ -1055,7 +1059,7 @@ mod test { TariScript::default(), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_input(input) @@ -1102,7 +1106,7 @@ mod test { let rules = create_consensus_rules(); let factories = CryptoFactories::default(); // Alice's parameters - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let a_change_key = TestParams::new(&key_manager).await; // Bob's parameters let bob_key = TestParams::new(&key_manager).await; @@ -1132,7 +1136,7 @@ mod test { script.clone(), ExecutionStack::default(), a_change_key.script_key_id, - a_change_key.spend_key_id, + a_change_key.commitment_mask_key_id, Covenant::default(), ); let mut alice = builder.build().await.unwrap(); @@ -1143,7 +1147,7 @@ mod test { let bob_public_key = msg.sender_offset_public_key.clone(); let mut bob_output = WalletOutput::new_current_version( MicroMinotari(1200) - fee - MicroMinotari(10), - bob_key.spend_key_id, + bob_key.commitment_mask_key_id, OutputFeatures::default(), script.clone(), ExecutionStack::default(), @@ -1154,6 +1158,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -1207,7 +1212,7 @@ mod test { #[tokio::test] async fn single_recipient_with_change() { let rules = create_consensus_rules(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let factories = CryptoFactories::default(); // Alice's parameters let alice_key = TestParams::new(&key_manager).await; @@ -1234,7 +1239,7 @@ mod test { script.clone(), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_input(input) @@ -1265,7 +1270,7 @@ mod test { let bob_public_key = msg.sender_offset_public_key.clone(); let mut bob_output = WalletOutput::new_current_version( MicroMinotari(5000), - bob_key.spend_key_id, + bob_key.commitment_mask_key_id, OutputFeatures::default(), script.clone(), ExecutionStack::default(), @@ -1276,6 +1281,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -1320,7 +1326,7 @@ mod test { #[tokio::test] async fn single_recipient_multiple_inputs_with_change() { let rules = create_consensus_rules(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let factories = CryptoFactories::default(); // Bob's parameters let bob_key = TestParams::new(&key_manager).await; @@ -1338,7 +1344,7 @@ mod test { script.clone(), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_input(input) @@ -1375,7 +1381,7 @@ mod test { let bob_public_key = msg.sender_offset_public_key.clone(); let mut bob_output = WalletOutput::new_current_version( MicroMinotari(5000), - bob_key.spend_key_id, + bob_key.commitment_mask_key_id, OutputFeatures::default(), script.clone(), ExecutionStack::default(), @@ -1386,6 +1392,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -1429,7 +1436,7 @@ mod test { #[tokio::test] async fn disallow_fee_larger_than_amount() { // Alice's parameters - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (utxo_amount, fee_per_gram, amount) = (MicroMinotari(2500), MicroMinotari(10), MicroMinotari(500)); let input = create_test_input(utxo_amount, 0, &key_manager, vec![]).await; let script = script!(Nop); @@ -1442,7 +1449,7 @@ mod test { script.clone(), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_input(input) @@ -1467,7 +1474,7 @@ mod test { #[tokio::test] async fn allow_fee_larger_than_amount() { // Alice's parameters - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (utxo_amount, fee_per_gram, amount) = (MicroMinotari(2500), MicroMinotari(10), MicroMinotari(500)); let input = create_test_input(utxo_amount, 0, &key_manager, vec![]).await; let script = script!(Nop); @@ -1480,7 +1487,7 @@ mod test { script.clone(), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_input(input) @@ -1506,8 +1513,8 @@ mod test { #[tokio::test] async fn single_recipient_with_rewindable_change_and_receiver_outputs_bulletproofs() { // Alice's parameters - let key_manager_alice = create_memory_db_key_manager(); - let key_manager_bob = create_memory_db_key_manager(); + let key_manager_alice = create_memory_db_key_manager().unwrap(); + let key_manager_bob = create_memory_db_key_manager().unwrap(); // Bob's parameters let bob_test_params = TestParams::new(&key_manager_bob).await; let alice_value = MicroMinotari(25000); @@ -1525,7 +1532,7 @@ mod test { script!(PushInt(1) Drop Nop), inputs!(change_params.script_key_pk), change_params.script_key_id.clone(), - change_params.spend_key_id.clone(), + change_params.commitment_mask_key_id.clone(), Covenant::default(), ) .with_input(input) @@ -1561,7 +1568,7 @@ mod test { let bob_public_key = msg.sender_offset_public_key.clone(); let bob_output = WalletOutput::new_current_version( MicroMinotari(5000), - bob_test_params.spend_key_id, + bob_test_params.commitment_mask_key_id, OutputFeatures::default(), script.clone(), ExecutionStack::default(), @@ -1572,6 +1579,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager_bob, ) .await @@ -1601,7 +1609,7 @@ mod test { let output = tx.body.outputs().iter().find(|o| o.script.size() > 1).unwrap(); - let (key, _value) = key_manager_alice.try_output_key_recovery(output, None).await.unwrap(); - assert_eq!(key, change_params.spend_key_id); + let (key, _value, _) = key_manager_alice.try_output_key_recovery(output, None).await.unwrap(); + assert_eq!(key, change_params.commitment_mask_key_id); } } diff --git a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs index 8fb6feee3f..883cff1f0d 100644 --- a/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs +++ b/base_layer/core/src/transactions/transaction_protocol/single_receiver.rs @@ -38,6 +38,7 @@ use crate::{ /// * Checks the input for validity /// * Constructs his output, range proof and partial signature /// * Constructs the reply +/// /// If any step fails, an error is returned. pub struct SingleReceiverTransactionProtocol {} @@ -51,7 +52,7 @@ impl SingleReceiverTransactionProtocol { SingleReceiverTransactionProtocol::validate_sender_data(sender_info, consensus_constants)?; let transaction_output = output.to_transaction_output(key_manager).await?; - let (nonce_id, public_nonce) = key_manager + let public_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await?; let tx_meta = if output.is_burned() { @@ -62,7 +63,7 @@ impl SingleReceiverTransactionProtocol { sender_info.metadata.clone() }; let public_excess = key_manager - .get_txo_kernel_signature_excess_with_offset(&output.spending_key_id, &nonce_id) + .get_txo_kernel_signature_excess_with_offset(&output.spending_key_id, &public_nonce.key_id) .await?; let kernel_message = TransactionKernel::build_kernel_signature_message( @@ -75,8 +76,8 @@ impl SingleReceiverTransactionProtocol { let signature = key_manager .get_partial_txo_kernel_signature( &output.spending_key_id, - &nonce_id, - &(&sender_info.public_nonce + &public_nonce), + &public_nonce.key_id, + &(&sender_info.public_nonce + &public_nonce.pub_key), &(&sender_info.public_excess + &public_excess), &sender_info.kernel_version, &kernel_message, @@ -85,7 +86,7 @@ impl SingleReceiverTransactionProtocol { ) .await?; let offset = key_manager - .get_txo_private_kernel_offset(&output.spending_key_id, &nonce_id) + .get_txo_private_kernel_offset(&output.spending_key_id, &public_nonce.key_id) .await?; let data = RecipientSignedMessage { @@ -153,6 +154,7 @@ mod test { tari_amount::*, test_helpers::TestParams, transaction_components::{ + encrypted_data::PaymentId, EncryptedData, OutputFeatures, TransactionKernel, @@ -172,13 +174,13 @@ mod test { #[tokio::test] async fn zero_amount_fails() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let consensus_constants = create_consensus_constants(0); let info = SingleRoundSenderData::default(); let bob_output = WalletOutput::new_current_version( MicroMinotari(5000), - test_params.spend_key_id, + test_params.commitment_mask_key_id, OutputFeatures::default(), script!(Nop), ExecutionStack::default(), @@ -189,6 +191,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -204,7 +207,7 @@ mod test { #[tokio::test] async fn invalid_version_fails() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let consensus_constants = create_consensus_constants(0); @@ -218,7 +221,7 @@ mod test { let bob_output = WalletOutput::new_current_version( MicroMinotari(5000), - test_params.spend_key_id, + test_params.commitment_mask_key_id, OutputFeatures::default(), script!(Nop), ExecutionStack::default(), @@ -229,6 +232,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -250,7 +254,7 @@ mod test { tari_key_manager::key_manager_service::storage::sqlite_db::KeyManagerSqliteDatabase< tari_common_sqlite::connection::DbConnection, >, - > = create_memory_db_key_manager(); + > = create_memory_db_key_manager().unwrap(); let consensus_constants = create_consensus_constants(0); let m = TransactionMetadata::new(MicroMinotari(100), 0); let test_params = TestParams::new(&key_manager).await; @@ -265,7 +269,7 @@ mod test { .await .unwrap(); let pub_xs = key_manager - .get_public_key_at_key_id(&test_params.spend_key_id) + .get_public_key_at_key_id(&test_params.commitment_mask_key_id) .await .unwrap(); let pub_rs = key_manager @@ -294,7 +298,7 @@ mod test { .unwrap(); let mut bob_output = WalletOutput::new_current_version( MicroMinotari(1500), - test_params2.spend_key_id.clone(), + test_params2.commitment_mask_key_id.clone(), OutputFeatures::default(), script.clone(), ExecutionStack::default(), @@ -305,6 +309,7 @@ mod test { Covenant::default(), EncryptedData::default(), 0.into(), + PaymentId::Empty, &key_manager, ) .await @@ -330,7 +335,7 @@ mod test { // Check the signature let pubkey = key_manager - .get_public_key_at_key_id(&test_params2.spend_key_id) + .get_public_key_at_key_id(&test_params2.commitment_mask_key_id) .await .unwrap(); let offset = prot.offset.clone(); 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 a96691dd75..85fdea1e0d 100644 --- a/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs +++ b/base_layer/core/src/transactions/transaction_protocol/transaction_initializer.rs @@ -40,6 +40,7 @@ use crate::{ key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, tari_amount::*, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, TransactionOutput, TransactionOutputVersion, @@ -48,7 +49,7 @@ use crate::{ MAX_TRANSACTION_OUTPUTS, }, transaction_protocol::{ - sender::{calculate_tx_id, OutputPair, RawTransactionInfo, SenderState, SenderTransactionProtocol}, + sender::{OutputPair, RawTransactionInfo, SenderState, SenderTransactionProtocol}, KernelFeatures, TransactionMetadata, }, @@ -59,7 +60,7 @@ pub const LOG_TARGET: &str = "c::tx::tx_protocol::tx_initializer"; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub(super) struct ChangeDetails { - change_spending_key_id: TariKeyId, + change_commitment_mask_key_id: TariKeyId, change_script: TariScript, change_input_data: ExecutionStack, change_script_key_id: TariKeyId, @@ -151,21 +152,21 @@ where KM: TransactionKeyManagerInterface recipient_minimum_value_promise: MicroMinotari, amount: MicroMinotari, ) -> Result<&mut Self, KeyManagerServiceError> { - let (recipient_ephemeral_public_key_nonce, _) = self + let recipient_ephemeral_public_key_nonce = self .key_manager - .get_next_key(TransactionKeyManagerBranch::Nonce.get_branch_key()) + .get_next_key(TransactionKeyManagerBranch::MetadataEphemeralNonce.get_branch_key()) .await?; - let (recipient_sender_offset_key_id, _) = self + let recipient_sender_offset = self .key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await?; let recipient_details = RecipientDetails { recipient_output_features, recipient_script, - recipient_sender_offset_key_id, + recipient_sender_offset_key_id: recipient_sender_offset.key_id, recipient_covenant, recipient_minimum_value_promise, - recipient_ephemeral_public_key_nonce, + recipient_ephemeral_public_key_nonce: recipient_ephemeral_public_key_nonce.key_id, amount, }; self.recipient = Some(recipient_details); @@ -180,32 +181,32 @@ where KM: TransactionKeyManagerInterface /// Adds an input to the transaction. pub async fn with_input(&mut self, input: WalletOutput) -> Result<&mut Self, KeyManagerServiceError> { - let (nonce_id, _) = self + let nonce = self .key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await?; let pair = OutputPair { output: input, - kernel_nonce: nonce_id, + kernel_nonce: nonce.key_id, sender_offset_key_id: None, }; self.inputs.push(pair); Ok(self) } - /// As the Sender adds an output to the transaction. + /// As the Sender add an output to the transaction. pub async fn with_output( &mut self, output: WalletOutput, sender_offset_key_id: TariKeyId, ) -> Result<&mut Self, KeyManagerServiceError> { - let (nonce_id, _) = self + let nonce = self .key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await?; let pair = OutputPair { output, - kernel_nonce: nonce_id, + kernel_nonce: nonce.key_id, sender_offset_key_id: Some(sender_offset_key_id), }; self.sender_custom_outputs.push(pair); @@ -219,11 +220,11 @@ where KM: TransactionKeyManagerInterface change_script: TariScript, change_input_data: ExecutionStack, change_script_key_id: TariKeyId, - change_spending_key_id: TariKeyId, + change_commitment_mask_key_id: TariKeyId, change_covenant: Covenant, ) -> &mut Self { let details = ChangeDetails { - change_spending_key_id, + change_commitment_mask_key_id, change_script, change_input_data, change_script_key_id, @@ -367,10 +368,10 @@ where KM: TransactionKeyManagerInterface let change_data = self.change.as_ref().ok_or("Change data was not provided")?; let change_script = change_data.change_script.clone(); let change_script_key_id = change_data.change_script_key_id.clone(); - let change_key_id = change_data.change_spending_key_id.clone(); - let (sender_offset_key_id, sender_offset_public_key) = self + let change_key_id = change_data.change_commitment_mask_key_id.clone(); + let sender_offset_public = self .key_manager - .get_next_key(&TransactionKeyManagerBranch::SenderOffset.get_branch_key()) + .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .map_err(|e| e.to_string())?; let input_data = change_data.change_input_data.clone(); @@ -384,7 +385,7 @@ where KM: TransactionKeyManagerInterface let encrypted_data = self .key_manager - .encrypt_data_for_recovery(&change_key_id, None, v.as_u64()) + .encrypt_data_for_recovery(&change_key_id, None, v.as_u64(), PaymentId::Empty) .await .map_err(|e| e.to_string())?; @@ -407,7 +408,7 @@ where KM: TransactionKeyManagerInterface .get_metadata_signature( &change_key_id, &v.into(), - &sender_offset_key_id, + &sender_offset_public.key_id, &output_version, &metadata_message, features.range_proof_type, @@ -422,12 +423,13 @@ where KM: TransactionKeyManagerInterface change_script, input_data, change_script_key_id, - sender_offset_public_key.clone(), + sender_offset_public.pub_key.clone(), metadata_sig, 0, covenant, encrypted_data, minimum_value_promise, + PaymentId::Empty, &self.key_manager, ) .await @@ -435,7 +437,7 @@ where KM: TransactionKeyManagerInterface Ok(( fee_without_change + change_fee, v, - Some((change_wallet_output, sender_offset_key_id)), + Some((change_wallet_output, sender_offset_public.key_id)), )) }, } @@ -507,7 +509,7 @@ where KM: TransactionKeyManagerInterface if self.sender_custom_outputs.len() >= MAX_TRANSACTION_OUTPUTS { return self.build_err("Too many outputs in transaction"); } - let (nonce_id, _) = match self + let nonce = match self .key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await @@ -517,25 +519,17 @@ where KM: TransactionKeyManagerInterface }; Some(OutputPair { output, - kernel_nonce: nonce_id, + kernel_nonce: nonce.key_id, sender_offset_key_id: Some(sender_offset_key_id), }) }, None => None, }; - let spending_key = match self - .key_manager - .get_public_key_at_key_id(&self.inputs[0].output.spending_key_id) - .await - { - Ok(key) => key, - Err(e) => return self.build_err(&e.to_string()), - }; // we need some random data here, the public excess of the commitment is random. let tx_id = match self.tx_id { Some(id) => id, - None => calculate_tx_id(&spending_key, 0), + None => TxId::new_random(), }; // The fee should be less than the amount being sent. This isn't a protocol requirement, but it's what you want @@ -612,7 +606,7 @@ mod test { #[tokio::test] async fn no_receivers() -> std::io::Result<()> { // Create some inputs - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p = TestParams::new(&key_manager).await; // Start the builder let builder = SenderTransactionInitializer::new(&create_consensus_constants(0), key_manager.clone()); @@ -667,7 +661,7 @@ mod test { script!(Nop), Default::default(), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ); let result = builder.build().await.unwrap(); @@ -689,7 +683,7 @@ mod test { #[tokio::test] async fn no_change_or_receivers() { // Create some inputs - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p = TestParams::new(&key_manager).await; let input = create_test_input(MicroMinotari(5000), 0, &key_manager, vec![]).await; let constants = create_consensus_constants(0); @@ -740,7 +734,7 @@ mod test { #[allow(clippy::identity_op)] async fn change_edge_case() { // Create some inputs - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p = TestParams::new(&key_manager).await; let constants = create_consensus_constants(0); let weighting = constants.transaction_weight_params(); @@ -795,7 +789,7 @@ mod test { #[tokio::test] async fn too_many_inputs() { // Create some inputs - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p = TestParams::new(&key_manager).await; let output = create_wallet_output_with_data( @@ -827,7 +821,7 @@ mod test { #[tokio::test] async fn fee_too_low() { // Create some inputs - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p = TestParams::new(&key_manager).await; let tx_fee = p.fee().calculate( MicroMinotari(1), @@ -852,7 +846,7 @@ mod test { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_fee_per_gram(MicroMinotari(1)) @@ -872,7 +866,7 @@ mod test { #[tokio::test] async fn not_enough_funds() { // Create some inputs - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p = TestParams::new(&key_manager).await; let input = create_test_input(MicroMinotari(400), 0, &key_manager, vec![]).await; let script = script!(Nop); @@ -901,7 +895,7 @@ mod test { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_fee_per_gram(MicroMinotari(1)) @@ -924,7 +918,7 @@ mod test { #[tokio::test] async fn single_recipient() { // Create some inputs - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let p = TestParams::new(&key_manager).await; let input1 = create_test_input(MicroMinotari(2000), 0, &key_manager, vec![]).await; let input2 = create_test_input(MicroMinotari(3000), 0, &key_manager, vec![]).await; @@ -967,7 +961,7 @@ mod test { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_fee_per_gram(fee_per_gram) diff --git a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs index 1ffdb5be0b..840f3f0994 100644 --- a/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs +++ b/base_layer/core/src/validation/aggregate_body/aggregate_body_chain_validator.rs @@ -34,7 +34,12 @@ use crate::{ transaction_components::{TransactionError, TransactionInput}, }, validation::{ - helpers::{check_input_is_utxo, check_not_duplicate_txo, check_tari_script_byte_size}, + helpers::{ + check_input_is_utxo, + check_not_duplicate_txo, + check_tari_encrypted_data_byte_size, + check_tari_script_byte_size, + }, ValidationError, }, }; @@ -222,8 +227,10 @@ pub fn check_outputs( body: &AggregateBody, ) -> Result<(), ValidationError> { let max_script_size = constants.max_script_byte_size(); + let max_encrypted_data_size = constants.max_extra_encrypted_data_byte_size(); for output in body.outputs() { check_tari_script_byte_size(&output.script, max_script_size)?; + check_tari_encrypted_data_byte_size(&output.encrypted_data, max_encrypted_data_size)?; check_not_duplicate_txo(db, output)?; } Ok(()) 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 6437cd5efb..496f782a78 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 @@ -52,6 +52,7 @@ use crate::{ check_covenant_length, check_permitted_output_types, check_permitted_range_proof_types, + check_tari_encrypted_data_byte_size, check_tari_script_byte_size, is_all_unique_and_sorted, validate_input_version, @@ -113,6 +114,7 @@ impl AggregateBodyInternalConsistencyValidator { for output in body.outputs() { check_permitted_output_types(constants, output)?; check_script_size(output, constants.max_script_byte_size())?; + check_encrypted_data_byte_size(output, constants.max_extra_encrypted_data_byte_size())?; check_covenant_length(&output.covenant, constants.max_covenant_length())?; check_permitted_range_proof_types(constants, output)?; check_validator_node_registration_utxo(constants, output)?; @@ -225,6 +227,20 @@ fn check_script_size(output: &TransactionOutput, max_script_size: usize) -> Resu }) } +/// Verify that the TariScript is not larger than the max size +fn check_encrypted_data_byte_size( + output: &TransactionOutput, + max_encrypted_data_size: usize, +) -> Result<(), ValidationError> { + check_tari_encrypted_data_byte_size(output.encrypted_data(), max_encrypted_data_size).map_err(|e| { + warn!( + target: LOG_TARGET, + "output ({}) script size exceeded max size {:?}.", output, e + ); + e + }) +} + /// This function checks for duplicate inputs and outputs. There should be no duplicate inputs or outputs in a /// aggregated body fn check_sorting_and_duplicates(body: &AggregateBody) -> Result<(), ValidationError> { @@ -574,7 +590,7 @@ mod test { let mut kernel1 = test_helpers::create_test_kernel(0.into(), 0, KernelFeatures::create_burn()); let mut kernel2 = test_helpers::create_test_kernel(0.into(), 0, KernelFeatures::create_burn()); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (output1, _, _) = test_helpers::create_utxo( 100.into(), &key_manager, @@ -636,7 +652,7 @@ mod test { // Sort the kernels, we'll check that the outputs fail the sorting check kernels.sort(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut outputs = futures::stream::unfold((), |_| async { let (o, _, _) = test_helpers::create_utxo( 100.into(), diff --git a/base_layer/core/src/validation/block_body/test.rs b/base_layer/core/src/validation/block_body/test.rs index dd64bc7b04..85a9712b27 100644 --- a/base_layer/core/src/validation/block_body/test.rs +++ b/base_layer/core/src/validation/block_body/test.rs @@ -24,7 +24,7 @@ use std::sync::Arc; use tari_common::configuration::Network; use tari_common_types::tari_address::TariAddress; use tari_key_manager::key_manager_service::KeyId; -use tari_script::{one_sided_payment_script, script}; +use tari_script::{push_pubkey_script, script}; use tari_test_utils::unpack_enum; use tokio::time::Instant; @@ -40,14 +40,18 @@ use crate::{ key_manager::{TariKeyId, TransactionKeyManagerBranch}, tari_amount::{uT, T}, test_helpers::schema_to_transaction, - transaction_components::{RangeProofType, TransactionError}, + transaction_components::{ + encrypted_data::{PaymentId, STATIC_ENCRYPTED_DATA_SIZE_TOTAL}, + EncryptedData, + RangeProofType, + TransactionError, + }, CoinbaseBuilder, CryptoFactories, }, txn_schema, validation::{BlockBodyValidator, ValidationError}, }; - async fn setup_with_rules(rules: ConsensusManager, check_rangeproof: bool) -> (TestBlockchain, BlockBodyFullValidator) { let blockchain = TestBlockchain::create(rules.clone()).await; let validator = BlockBodyFullValidator::new(rules, check_rangeproof); @@ -234,21 +238,25 @@ async fn it_allows_multiple_coinbases() { let (blockchain, validator) = setup(true).await; let (mut block, coinbase) = blockchain.create_unmined_block(block_spec!("A1", parent: "GB")).await; - let spend_key_id = KeyId::Managed { - branch: TransactionKeyManagerBranch::Coinbase.get_branch_key(), + let commitment_mask_key = KeyId::Managed { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), index: 42, }; let wallet_payment_address = TariAddress::default(); let (_, coinbase_output) = CoinbaseBuilder::new(blockchain.km.clone()) .with_block_height(1) .with_fees(0.into()) - .with_spend_key_id(spend_key_id.clone()) + .with_commitment_mask_id(commitment_mask_key.clone()) .with_encryption_key_id(TariKeyId::default()) .with_sender_offset_key_id(TariKeyId::default()) .with_script_key_id(TariKeyId::default()) - .with_script(one_sided_payment_script(wallet_payment_address.public_key())) + .with_script(push_pubkey_script(wallet_payment_address.public_spend_key())) .with_range_proof_type(RangeProofType::RevealedValue) - .build_with_reward(blockchain.rules().consensus_constants(1), coinbase.value) + .build_with_reward( + blockchain.rules().consensus_constants(1), + coinbase.value, + PaymentId::Empty, + ) .await .unwrap(); @@ -413,6 +421,35 @@ async fn it_limits_the_script_byte_size() { assert!(matches!(err, ValidationError::TariScriptExceedsMaxSize { .. })); } +#[tokio::test] +async fn it_limits_the_encrypted_data_byte_size() { + let rules = ConsensusManager::builder(Network::LocalNet) + .add_consensus_constants( + ConsensusConstantsBuilder::new(Network::LocalNet) + .with_coinbase_lockheight(0) + .build(), + ) + .build() + .unwrap(); + let (mut blockchain, validator) = setup_with_rules(rules, true).await; + + let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).await.unwrap(); + + let mut schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T, 12 * T]); + schema1.script = script!(Nop Nop Nop); + let (txs, _) = schema_to_transaction(&[schema1], &blockchain.km).await; + let mut txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::>(); + let mut outputs = txs[0].body.outputs().clone(); + outputs[0].encrypted_data = EncryptedData::from_vec_unsafe(vec![0; STATIC_ENCRYPTED_DATA_SIZE_TOTAL + 257]); + txs[0].body = AggregateBody::new(txs[0].body.inputs().clone(), outputs, txs[0].body.kernels().clone()); + let (block, _) = blockchain.create_next_tip(block_spec!("B", transactions: txs)).await; + + let txn = blockchain.db().db_read_access().unwrap(); + let smt = blockchain.db().smt(); + let err = validator.validate_body(&*txn, block.block(), smt).unwrap_err(); + assert!(matches!(err, ValidationError::EncryptedDataExceedsMaxSize { .. })); +} + #[tokio::test] async fn it_rejects_invalid_input_metadata() { let rules = ConsensusManager::builder(Network::LocalNet) @@ -471,7 +508,6 @@ async fn it_rejects_zero_conf_double_spends() { mod body_only { use super::*; - use crate::validation::block_body::BlockBodyFullValidator; #[tokio::test] async fn it_rejects_invalid_input_metadata() { @@ -510,7 +546,7 @@ mod body_only { mod orphan_validator { use super::*; use crate::{ - transactions::transaction_components::{OutputType, RangeProofType}, + transactions::transaction_components::OutputType, txn_schema, validation::block_body::BlockBodyInternalConsistencyValidator, }; diff --git a/base_layer/core/src/validation/error.rs b/base_layer/core/src/validation/error.rs index 452c267bba..9a734ee20f 100644 --- a/base_layer/core/src/validation/error.rs +++ b/base_layer/core/src/validation/error.rs @@ -95,6 +95,14 @@ pub enum ValidationError { max_script_size: usize, actual_script_size: usize, }, + #[error( + "Encrypted data exceeded maximum encrytped data size, expected less than {max_encrypted_data_size} but was \ + {actual_encrypted_data_size}" + )] + EncryptedDataExceedsMaxSize { + max_encrypted_data_size: usize, + actual_encrypted_data_size: usize, + }, #[error("Consensus Error: {0}")] ConsensusError(String), #[error("Duplicate kernel Error: {0}")] @@ -185,6 +193,7 @@ impl ValidationError { err @ ValidationError::IncorrectPreviousHash { .. } | err @ ValidationError::BadBlockFound { .. } | err @ ValidationError::TariScriptExceedsMaxSize { .. } | + err @ ValidationError::EncryptedDataExceedsMaxSize { .. } | err @ ValidationError::ConsensusError(_) | err @ ValidationError::DuplicateKernelError(_) | err @ ValidationError::CovenantError(_) | diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 4ceac26a70..160706defe 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -42,7 +42,13 @@ use crate::{ PowAlgorithm, PowError, }, - transactions::transaction_components::{TransactionInput, TransactionKernel, TransactionOutput}, + transactions::transaction_components::{ + encrypted_data::STATIC_ENCRYPTED_DATA_SIZE_TOTAL, + EncryptedData, + TransactionInput, + TransactionKernel, + TransactionOutput, + }, validation::ValidationError, }; @@ -223,6 +229,21 @@ pub fn check_tari_script_byte_size(script: &TariScript, max_script_size: usize) Ok(()) } +/// Checks the byte size of TariScript is less than or equal to the given size, otherwise returns an error. +pub fn check_tari_encrypted_data_byte_size( + encrypted_data: &EncryptedData, + max_encrypted_data_size: usize, +) -> Result<(), ValidationError> { + let encrypted_data_size = encrypted_data.as_bytes().len(); + if encrypted_data_size > max_encrypted_data_size + STATIC_ENCRYPTED_DATA_SIZE_TOTAL { + return Err(ValidationError::EncryptedDataExceedsMaxSize { + max_encrypted_data_size: max_encrypted_data_size + STATIC_ENCRYPTED_DATA_SIZE_TOTAL, + actual_encrypted_data_size: encrypted_data_size, + }); + } + Ok(()) +} + /// This function checks that the outputs do not already exist in the TxO set. pub fn check_not_duplicate_txo( db: &B, @@ -552,10 +573,10 @@ mod test { #[tokio::test] async fn it_succeeds_for_valid_coinbase() { let height = 1; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let rules = test_helpers::create_consensus_manager(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let coinbase = block_on(test_helpers::create_coinbase_wallet_output( &test_params, height, @@ -576,7 +597,7 @@ mod test { #[tokio::test] async fn it_returns_error_for_invalid_coinbase_maturity() { let height = 1; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let rules = test_helpers::create_consensus_manager(); let mut coinbase = @@ -600,7 +621,7 @@ mod test { #[tokio::test] async fn it_returns_error_for_invalid_coinbase_reward() { let height = 1; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let test_params = TestParams::new(&key_manager).await; let rules = test_helpers::create_consensus_manager(); let mut coinbase = test_helpers::create_coinbase_wallet_output( diff --git a/base_layer/core/src/validation/test.rs b/base_layer/core/src/validation/test.rs index 5c2c2bd036..65659b8b30 100644 --- a/base_layer/core/src/validation/test.rs +++ b/base_layer/core/src/validation/test.rs @@ -172,7 +172,7 @@ async fn chain_balance_validation() { let consensus_manager = ConsensusManagerBuilder::new(Network::Esmeralda).build().unwrap(); let genesis = consensus_manager.get_genesis_block(); let faucet_value = 5000 * uT; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (faucet_utxo, faucet_key_id, _) = create_utxo( faucet_value, &key_manager, @@ -355,7 +355,7 @@ async fn chain_balance_validation_burned() { let consensus_manager = ConsensusManagerBuilder::new(Network::Esmeralda).build().unwrap(); let genesis = consensus_manager.get_genesis_block(); let faucet_value = 5000 * uT; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (faucet_utxo, faucet_key_id, _) = create_utxo( faucet_value, &key_manager, @@ -513,7 +513,7 @@ mod transaction_validator { #[tokio::test] async fn it_rejects_coinbase_outputs() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_manager = ConsensusManagerBuilder::new(Network::LocalNet).build().unwrap(); let db = create_store_with_consensus(consensus_manager.clone()); let factories = CryptoFactories::default(); @@ -533,7 +533,7 @@ mod transaction_validator { #[tokio::test] async fn coinbase_extra_must_be_empty() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_manager = ConsensusManagerBuilder::new(Network::LocalNet).build().unwrap(); let db = create_store_with_consensus(consensus_manager.clone()); let factories = CryptoFactories::default(); diff --git a/base_layer/core/tests/chain_storage_tests/chain_backend.rs b/base_layer/core/tests/chain_storage_tests/chain_backend.rs index abb92a8df6..590219de03 100644 --- a/base_layer/core/tests/chain_storage_tests/chain_backend.rs +++ b/base_layer/core/tests/chain_storage_tests/chain_backend.rs @@ -179,7 +179,7 @@ fn test_utxo_order() { let read_utxos = db.fetch_outputs_in_block_with_spend_state(&block_hash).unwrap(); assert_eq!(utxos.len(), read_utxos.len()); for i in 0..2000 { - assert_eq!(&utxos[i], read_utxos[i].as_transaction_output().unwrap()); + assert_eq!(&utxos[i], read_utxos[i].to_transaction_output().unwrap()); } } diff --git a/base_layer/core/tests/helpers/block_builders.rs b/base_layer/core/tests/helpers/block_builders.rs index 6196f37fc0..8aa974419c 100644 --- a/base_layer/core/tests/helpers/block_builders.rs +++ b/base_layer/core/tests/helpers/block_builders.rs @@ -65,8 +65,11 @@ pub async fn create_coinbase( key_manager: &MemoryDbKeyManager, ) -> (TransactionOutput, TransactionKernel, WalletOutput) { let p = TestParams::new(key_manager).await; - let public_exess = key_manager.get_public_key_at_key_id(&p.spend_key_id).await.unwrap(); - let (nonce, public_nonce) = key_manager + let public_exess = key_manager + .get_public_key_at_key_id(&p.commitment_mask_key_id) + .await + .unwrap(); + let nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); @@ -83,9 +86,9 @@ pub async fn create_coinbase( let sig = key_manager .get_partial_txo_kernel_signature( - &p.spend_key_id, - &nonce, - &public_nonce, + &p.commitment_mask_key_id, + &nonce.key_id, + &nonce.pub_key, &public_exess, &TransactionKernelVersion::get_current_version(), &kernel_message, diff --git a/base_layer/core/tests/helpers/nodes.rs b/base_layer/core/tests/helpers/nodes.rs index c22e65ac18..7b5bcfd0f7 100644 --- a/base_layer/core/tests/helpers/nodes.rs +++ b/base_layer/core/tests/helpers/nodes.rs @@ -78,6 +78,7 @@ use tari_shutdown::Shutdown; use crate::helpers::mock_state_machine::MockBaseNodeStateMachine; /// The NodeInterfaces is used as a container for providing access to all the services and interfaces of a single node. +#[allow(dead_code)] pub struct NodeInterfaces { pub node_identity: Arc, pub outbound_nci: OutboundNodeCommsInterface, @@ -357,6 +358,7 @@ async fn setup_base_node_services( let handles = StackBuilder::new(shutdown.to_signal()) .add_initializer(RegisterHandle::new(dht)) .add_initializer(RegisterHandle::new(comms.connectivity())) + .add_initializer(RegisterHandle::new(comms.peer_manager())) .add_initializer(LivenessInitializer::new( liveness_service_config, Arc::clone(&subscription_factory), diff --git a/base_layer/core/tests/helpers/sample_blockchains.rs b/base_layer/core/tests/helpers/sample_blockchains.rs index 0270c7d600..14badc5953 100644 --- a/base_layer/core/tests/helpers/sample_blockchains.rs +++ b/base_layer/core/tests/helpers/sample_blockchains.rs @@ -190,7 +190,7 @@ pub async fn create_new_blockchain( ConsensusManager, MemoryDbKeyManager, ) { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = consensus_constants(network).build(); let (block0, output) = create_genesis_block(&consensus_constants, &key_manager).await; let consensus_manager = ConsensusManagerBuilder::new(network) @@ -219,7 +219,7 @@ pub async fn create_new_blockchain_with_constants( ConsensusManager, MemoryDbKeyManager, ) { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (block0, output) = create_genesis_block(&constants, &key_manager).await; let consensus_manager = ConsensusManagerBuilder::new(network) .add_consensus_constants(constants) @@ -248,7 +248,7 @@ pub async fn create_new_blockchain_lmdb( ConsensusManager, MemoryDbKeyManager, ) { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = consensus_constants(network).build(); let (block0, output) = create_genesis_block(&consensus_constants, &key_manager).await; let consensus_manager = ConsensusManagerBuilder::new(network) diff --git a/base_layer/core/tests/helpers/sync.rs b/base_layer/core/tests/helpers/sync.rs index 8dace2f6d9..40099ce612 100644 --- a/base_layer/core/tests/helpers/sync.rs +++ b/base_layer/core/tests/helpers/sync.rs @@ -154,7 +154,7 @@ pub async fn create_network_with_multiple_nodes( } let network = Network::LocalNet; let temp_dir = tempdir().unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = sample_blockchains::consensus_constants(network).build(); let (initial_block, coinbase_wallet_output) = create_genesis_block(&consensus_constants, &key_manager).await; let consensus_manager = ConsensusManagerBuilder::new(network) diff --git a/base_layer/core/tests/tests/base_node_rpc.rs b/base_layer/core/tests/tests/base_node_rpc.rs index bc8f0c39fb..b78ae3014b 100644 --- a/base_layer/core/tests/tests/base_node_rpc.rs +++ b/base_layer/core/tests/tests/base_node_rpc.rs @@ -85,7 +85,7 @@ async fn setup() -> ( let consensus_constants = ConsensusConstantsBuilder::new(Network::LocalNet) .with_coinbase_lockheight(1) .build(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let temp_dir = tempdir().unwrap(); let (block0, utxo0) = create_genesis_block_with_coinbase_value(100_000_000.into(), &consensus_constants, &key_manager).await; diff --git a/base_layer/core/tests/tests/block_validation.rs b/base_layer/core/tests/tests/block_validation.rs index c0e4d50c44..1cdec8cfde 100644 --- a/base_layer/core/tests/tests/block_validation.rs +++ b/base_layer/core/tests/tests/block_validation.rs @@ -102,7 +102,7 @@ async fn test_monero_blocks() { let seed1 = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97"; let seed2 = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad98"; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let network = Network::Esmeralda; let cc = ConsensusConstantsBuilder::new(network) .with_max_randomx_seed_height(1) @@ -283,7 +283,7 @@ async fn inputs_are_not_malleable() { let mut malicious_test_params = TestParams::new(&blockchain.key_manager).await; // Oh noes - they've managed to get hold of the private script and spend keys - malicious_test_params.spend_key_id = spent_output.spending_key_id; + malicious_test_params.commitment_mask_key_id = spent_output.spending_key_id; let modified_so = blockchain .key_manager .get_script_offset(&vec![spent_output.script_key_id.clone()], &vec![malicious_test_params @@ -339,7 +339,7 @@ async fn inputs_are_not_malleable() { #[allow(clippy::too_many_lines)] async fn test_orphan_validator() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let network = Network::Igor; let consensus_constants = ConsensusConstantsBuilder::new(network) .with_max_block_transaction_weight(325) @@ -489,7 +489,7 @@ async fn test_orphan_body_validation() { .clear_proof_of_work() .add_proof_of_work(PowAlgorithm::Sha3x, sha3x_constants) .build(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (genesis, outputs) = create_genesis_block_with_utxos(&[T, T, T], &consensus_constants, &key_manager).await; let network = Network::LocalNet; let rules = ConsensusManager::builder(network) @@ -700,7 +700,7 @@ OutputFeatures::default()), #[allow(clippy::too_many_lines)] async fn test_header_validation() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let network = Network::Igor; // we dont want localnet's 1 difficulty or the full mined difficulty of weather wax but we want some. let sha3x_constants = PowAlgorithmConstants { @@ -827,7 +827,7 @@ async fn test_block_sync_body_validator() { let consensus_constants = ConsensusConstantsBuilder::new(network) .with_max_block_transaction_weight(400) .build(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (genesis, outputs) = create_genesis_block_with_utxos(&[T, T, T], &consensus_constants, &key_manager).await; let network = Network::LocalNet; let rules = ConsensusManager::builder(network) @@ -1100,7 +1100,7 @@ async fn add_block_with_large_block() { let factories = CryptoFactories::default(); let network = Network::LocalNet; let consensus_constants = ConsensusConstantsBuilder::new(network).build(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (genesis, outputs) = create_genesis_block_with_utxos( &[ 5 * T, @@ -1179,7 +1179,7 @@ async fn add_block_with_large_many_output_block() { let consensus_constants = ConsensusConstantsBuilder::new(network) .with_max_block_transaction_weight(127_795) .build(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (genesis, outputs) = create_genesis_block_with_utxos(&[501 * T], &consensus_constants, &key_manager).await; let network = Network::LocalNet; let rules = ConsensusManager::builder(network) diff --git a/base_layer/core/tests/tests/mempool.rs b/base_layer/core/tests/tests/mempool.rs index 9feb7c180f..083f3fec15 100644 --- a/base_layer/core/tests/tests/mempool.rs +++ b/base_layer/core/tests/tests/mempool.rs @@ -1045,7 +1045,7 @@ async fn receive_and_propagate_transaction() { let consensus_constants = crate::helpers::sample_blockchains::consensus_constants(network) .with_coinbase_lockheight(100) .build(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (block0, utxo) = create_genesis_block(&consensus_constants, &key_manager).await; let consensus_manager = ConsensusManager::builder(network) .add_consensus_constants(consensus_constants) @@ -1219,17 +1219,17 @@ async fn consensus_validation_large_tx() { let amount_per_output = (amount - fee) / output_count as u64; let amount_for_last_output = (amount - fee) - amount_per_output * (output_count as u64 - 1); let mut wallet_outputs = Vec::with_capacity(output_count); - let (input_kernel_nonce, mut pub_nonce) = key_manager + let input_kernel_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); let mut pub_excess = PublicKey::default() - key_manager - .get_txo_kernel_signature_excess_with_offset(&input.spending_key_id, &input_kernel_nonce) + .get_txo_kernel_signature_excess_with_offset(&input.spending_key_id, &input_kernel_nonce.key_id) .await .unwrap(); let mut sender_offsets = Vec::new(); - + let mut pub_nonce = input_kernel_nonce.pub_key.clone(); for i in 0..output_count { let test_params = TestParams::new(&key_manager).await; let output_amount = if i < output_count - 1 { @@ -1294,13 +1294,13 @@ async fn consensus_validation_large_tx() { offset = &offset - &key_manager - .get_txo_private_kernel_offset(&input.spending_key_id, &input_kernel_nonce) + .get_txo_private_kernel_offset(&input.spending_key_id, &input_kernel_nonce.key_id) .await .unwrap(); let sig = key_manager .get_partial_txo_kernel_signature( &input.spending_key_id, - &input_kernel_nonce, + &input_kernel_nonce.key_id, &pub_nonce, &pub_excess, &kernel_version, @@ -1387,13 +1387,13 @@ async fn validation_reject_min_fee() { let fee = 0.into(); - let (input_kernel_nonce, mut pub_nonce) = key_manager + let input_kernel_nonce = key_manager .get_next_key(TransactionKeyManagerBranch::KernelNonce.get_branch_key()) .await .unwrap(); let mut pub_excess = PublicKey::default() - key_manager - .get_txo_kernel_signature_excess_with_offset(&input.spending_key_id, &input_kernel_nonce) + .get_txo_kernel_signature_excess_with_offset(&input.spending_key_id, &input_kernel_nonce.key_id) .await .unwrap(); let mut sender_offsets = Vec::new(); @@ -1416,7 +1416,7 @@ async fn validation_reject_min_fee() { ) .await .unwrap(); - pub_nonce = pub_nonce + test_params.kernel_nonce_key_pk; + let pub_nonce = input_kernel_nonce.pub_key + test_params.kernel_nonce_key_pk; sender_offsets.push(test_params.sender_offset_key_id.clone()); let mut agg_sig = Signature::default(); @@ -1454,13 +1454,13 @@ async fn validation_reject_min_fee() { offset = &offset - &key_manager - .get_txo_private_kernel_offset(&input.spending_key_id, &input_kernel_nonce) + .get_txo_private_kernel_offset(&input.spending_key_id, &input_kernel_nonce.key_id) .await .unwrap(); let sig = key_manager .get_partial_txo_kernel_signature( &input.spending_key_id, - &input_kernel_nonce, + &input_kernel_nonce.key_id, &pub_nonce, &pub_excess, &kernel_version, @@ -1711,7 +1711,7 @@ async fn block_event_and_reorg_event_handling() { // When block B2A is submitted, then both nodes have TX2A and TX3A in their reorg pools // When block B2B is submitted with TX2B, TX3B, then TX2A, TX3A are discarded (Not Stored) let network = Network::LocalNet; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = ConsensusConstantsBuilder::new(Network::LocalNet) .with_coinbase_lockheight(1) .build(); diff --git a/base_layer/core/tests/tests/node_comms_interface.rs b/base_layer/core/tests/tests/node_comms_interface.rs index 576702189a..cd6c80d6a6 100644 --- a/base_layer/core/tests/tests/node_comms_interface.rs +++ b/base_layer/core/tests/tests/node_comms_interface.rs @@ -46,10 +46,12 @@ use tari_core::{ MemoryDbKeyManager, TransactionKeyManagerBranch, TransactionKeyManagerInterface, + TransactionKeyManagerLabel, }, tari_amount::MicroMinotari, test_helpers::{create_utxo, TestParams, TransactionSchema}, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, TransactionOutput, TransactionOutputVersion, @@ -63,7 +65,7 @@ use tari_core::{ validation::{mocks::MockValidator, transaction::TransactionChainLinkedValidator}, OutputSmt, }; -use tari_key_manager::key_manager_service::KeyManagerInterface; +use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface}; use tari_script::{inputs, script, ExecutionStack}; use tari_service_framework::reply_channel; use tokio::sync::{broadcast, mpsc}; @@ -204,7 +206,7 @@ async fn inbound_fetch_utxos() { let utxo_1 = block0.body.outputs()[0].clone(); let hash_1 = utxo_1.hash(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (utxo_2, _, _) = create_utxo( MicroMinotari(10_000), &key_manager, @@ -287,7 +289,7 @@ async fn initialize_sender_transaction_protocol_for_overflow_test( script!(PushPubKey(Box::new(script_public_key))), ExecutionStack::default(), change.script_key_id, - change.spend_key_id, + change.commitment_mask_key_id, Covenant::default(), ); @@ -295,18 +297,21 @@ async fn initialize_sender_transaction_protocol_for_overflow_test( stx_builder.with_input(tx_input.clone()).await.unwrap(); } for tx_output in txn_schema.to { - let (spending_key, _) = key_manager + let commitment_mask_key = key_manager .get_next_key(TransactionKeyManagerBranch::CommitmentMask.get_branch_key()) .await .unwrap(); - let (sender_offset_key_id, sender_offset_public_key) = key_manager + let sender_offset = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); - let (script_key_id, _) = key_manager - .get_next_key(TransactionKeyManagerBranch::ScriptKey.get_branch_key()) - .await - .unwrap(); + + let script_key_id = KeyId::Derived { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), + label: TransactionKeyManagerLabel::ScriptKey.get_branch_key(), + index: commitment_mask_key.key_id.managed_index().unwrap(), + }; + let script_public_key = key_manager.get_public_key_at_key_id(&script_key_id).await.unwrap(); let input_data = match &txn_schema.input_data { Some(data) => data.clone(), @@ -316,28 +321,28 @@ async fn initialize_sender_transaction_protocol_for_overflow_test( Some(data) => data, None => TransactionOutputVersion::get_current_version(), }; - let output = WalletOutputBuilder::new(tx_output, spending_key) + let output = WalletOutputBuilder::new(tx_output, commitment_mask_key.key_id) .with_features(txn_schema.features.clone()) .with_script(txn_schema.script.clone()) - .encrypt_data_for_recovery(key_manager, None) + .encrypt_data_for_recovery(key_manager, None, PaymentId::Empty) .await .unwrap() .with_input_data(input_data) .with_covenant(txn_schema.covenant.clone()) .with_version(version) - .with_sender_offset_public_key(sender_offset_public_key) + .with_sender_offset_public_key(sender_offset.pub_key) .with_script_key(script_key_id.clone()) - .sign_as_sender_and_receiver(key_manager, &sender_offset_key_id) + .sign_as_sender_and_receiver(key_manager, &sender_offset.key_id) .await .unwrap() .try_build(key_manager) .await .unwrap(); - stx_builder.with_output(output, sender_offset_key_id).await.unwrap(); + stx_builder.with_output(output, sender_offset.key_id).await.unwrap(); } for mut utxo in txn_schema.to_outputs { - let (sender_offset_key_id, _) = key_manager + let sender_offset_key = key_manager .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await .unwrap(); @@ -346,7 +351,7 @@ async fn initialize_sender_transaction_protocol_for_overflow_test( .get_metadata_signature( &utxo.spending_key_id, &utxo.value.into(), - &sender_offset_key_id, + &sender_offset_key.key_id, &utxo.version, &metadata_message, utxo.features.range_proof_type, @@ -354,7 +359,7 @@ async fn initialize_sender_transaction_protocol_for_overflow_test( .await .unwrap(); - stx_builder.with_output(utxo, sender_offset_key_id).await.unwrap(); + stx_builder.with_output(utxo, sender_offset_key.key_id).await.unwrap(); } stx_builder @@ -362,7 +367,7 @@ async fn initialize_sender_transaction_protocol_for_overflow_test( #[tokio::test] async fn test_sender_transaction_protocol_for_overflow() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let script = script!(Nop); let amount = MicroMinotari(u64::MAX); // This is the adversary's attack! let output_features = OutputFeatures::default(); @@ -395,6 +400,7 @@ async fn test_sender_transaction_protocol_for_overflow() { utxo.encrypted_data, utxo.minimum_value_promise, utxo.proof, + PaymentId::Empty, ); // Test overflow in inputs @@ -433,7 +439,7 @@ async fn test_sender_transaction_protocol_for_overflow() { async fn inbound_fetch_blocks_before_horizon_height() { let consensus_manager = ConsensusManager::builder(Network::LocalNet).build().unwrap(); let block0 = consensus_manager.get_genesis_block(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let validators = Validators::new( MockValidator::new(true), MockValidator::new(true), diff --git a/base_layer/core/tests/tests/node_service.rs b/base_layer/core/tests/tests/node_service.rs index cc36517d53..8f3abcf78e 100644 --- a/base_layer/core/tests/tests/node_service.rs +++ b/base_layer/core/tests/tests/node_service.rs @@ -73,7 +73,7 @@ use crate::{ #[tokio::test(flavor = "multi_thread", worker_threads = 1)] async fn propagate_and_forward_many_valid_blocks() { let temp_dir = tempdir().unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); // Alice will propagate a number of block hashes to bob, bob will receive it, request the full block, verify and // then propagate the hash to carol and dan. Dan and Carol will also try to propagate the block hashes to each // other, but the block should not be re-requested. These duplicate blocks will be discarded and wont be @@ -233,7 +233,7 @@ async fn propagate_and_forward_invalid_block_hash() { let bob_node_identity = random_node_identity(); let carol_node_identity = random_node_identity(); let network = Network::LocalNet; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = crate::helpers::sample_blockchains::consensus_constants(network).build(); let (block0, genesis_coinbase) = create_genesis_block(&consensus_constants, &key_manager).await; let rules = ConsensusManager::builder(network) @@ -363,7 +363,7 @@ async fn propagate_and_forward_invalid_block() { let bob_node_identity = random_node_identity(); let carol_node_identity = random_node_identity(); let dan_node_identity = random_node_identity(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let network = Network::LocalNet; let consensus_constants = crate::helpers::sample_blockchains::consensus_constants(network).build(); let (block0, _) = create_genesis_block(&consensus_constants, &key_manager).await; @@ -512,7 +512,7 @@ async fn propagate_and_forward_invalid_block() { async fn local_get_metadata() { let temp_dir = tempdir().unwrap(); let network = Network::LocalNet; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (mut node, consensus_manager) = BaseNodeBuilder::new(network.into()) .start(temp_dir.path().to_str().unwrap(), BlockchainDatabaseConfig::default()) .await; @@ -536,7 +536,7 @@ async fn local_get_metadata() { async fn local_get_new_block_template_and_get_new_block() { let temp_dir = tempdir().unwrap(); let network = Network::LocalNet; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = NetworkConsensus::from(network).create_consensus_constants(); let (block0, outputs) = create_genesis_block_with_utxos(&[T, T], &consensus_constants[0], &key_manager).await; let rules = ConsensusManager::builder(network) @@ -579,7 +579,7 @@ async fn local_get_new_block_with_zero_conf() { let factories = CryptoFactories::default(); let temp_dir = tempdir().unwrap(); let network = Network::LocalNet; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = NetworkConsensus::from(network).create_consensus_constants(); let (block0, outputs) = create_genesis_block_with_utxos(&[T, T], &consensus_constants[0], &key_manager).await; let rules = ConsensusManagerBuilder::new(network) @@ -665,7 +665,7 @@ async fn local_get_new_block_with_combined_transaction() { let factories = CryptoFactories::default(); let temp_dir = tempdir().unwrap(); let network = Network::LocalNet; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = NetworkConsensus::from(network).create_consensus_constants(); let (block0, outputs) = create_genesis_block_with_utxos(&[T, T], &consensus_constants[0], &key_manager).await; let rules = ConsensusManagerBuilder::new(network) @@ -745,7 +745,7 @@ async fn local_get_new_block_with_combined_transaction() { async fn local_submit_block() { let temp_dir = tempdir().unwrap(); let network = Network::LocalNet; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let (mut node, consensus_manager) = BaseNodeBuilder::new(network.into()) .start(temp_dir.path().to_str().unwrap(), BlockchainDatabaseConfig::default()) .await; diff --git a/base_layer/core/tests/tests/node_state_machine.rs b/base_layer/core/tests/tests/node_state_machine.rs index 72757744b8..124626e5ce 100644 --- a/base_layer/core/tests/tests/node_state_machine.rs +++ b/base_layer/core/tests/tests/node_state_machine.rs @@ -69,7 +69,7 @@ use crate::helpers::{ async fn test_listening_lagging() { let network = Network::LocalNet; let temp_dir = tempdir().unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = crate::helpers::sample_blockchains::consensus_constants(network).build(); let (prev_block, _) = create_genesis_block(&consensus_constants, &key_manager).await; let consensus_manager = ConsensusManagerBuilder::new(network) @@ -153,7 +153,7 @@ async fn test_listening_lagging() { async fn test_listening_initial_fallen_behind() { let network = Network::LocalNet; let temp_dir = tempdir().unwrap(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = crate::helpers::sample_blockchains::consensus_constants(network).build(); let (gen_block, _) = create_genesis_block(&consensus_constants, &key_manager).await; let consensus_manager = ConsensusManagerBuilder::new(network) diff --git a/base_layer/key_manager/src/cipher_seed.rs b/base_layer/key_manager/src/cipher_seed.rs index acae2f54c1..469b765417 100644 --- a/base_layer/key_manager/src/cipher_seed.rs +++ b/base_layer/key_manager/src/cipher_seed.rs @@ -20,9 +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 std::{convert::TryFrom, mem::size_of, str::FromStr}; +use std::{mem::size_of, str::FromStr}; -use argon2; use blake2::Blake2b; use chacha20::{ cipher::{NewCipher, StreamCipher}, @@ -35,26 +34,29 @@ use digest::consts::U32; use rand::{rngs::OsRng, RngCore}; use serde::{Deserialize, Serialize}; use subtle::ConstantTimeEq; +use tari_crypto::hashing::DomainSeparatedHasher; use tari_utilities::{hidden::Hidden, safe_array::SafeArray, SafePassword}; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; use crate::{ error::KeyManagerError, - mac_domain_hasher, mnemonic::{from_bytes, to_bytes, to_bytes_with_language, Mnemonic, MnemonicLanguage}, CipherSeedEncryptionKey, CipherSeedMacKey, + KeyManagerDomain, SeedWords, - LABEL_ARGON_ENCODING, - LABEL_CHACHA20_ENCODING, - LABEL_MAC_GENERATION, + HASHER_LABEL_CIPHER_SEED_ENCRYPTION_NONCE, + HASHER_LABEL_CIPHER_SEED_MAC, + HASHER_LABEL_CIPHER_SEED_PBKDF_SALT, }; // The version should be incremented for any breaking change to the format +// NOTE: Only the most recent version is supported! // History: // 0: initial version // 1: fixed incorrect key derivation and birthday genesis -const CIPHER_SEED_VERSION: u8 = 1u8; +// 2: updated hasher domain labels and MAC input ordering +const CIPHER_SEED_VERSION: u8 = 2u8; pub const BIRTHDAY_GENESIS_FROM_UNIX_EPOCH: u64 = 1640995200; // seconds to 2022-01-01 00:00:00 UTC pub const DEFAULT_CIPHER_SEED_PASSPHRASE: &str = "TARI_CIPHER_SEED"; // the default passphrase if none is supplied @@ -116,13 +118,12 @@ pub const CIPHER_SEED_CHECKSUM_BYTES: usize = 4; /// only have to scan the blocks in the chain since that day for full recovery, rather than scanning the entire /// blockchain. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize)] -#[zeroize(drop)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)] pub struct CipherSeed { version: u8, birthday: u16, entropy: Box<[u8; CIPHER_SEED_ENTROPY_BYTES]>, - salt: Box<[u8; CIPHER_SEED_MAIN_SALT_BYTES]>, + salt: [u8; CIPHER_SEED_MAIN_SALT_BYTES], } // This is a separate type to make the linter happy @@ -137,10 +138,10 @@ impl CipherSeed { let birthday_genesis_date = UNIX_EPOCH + Duration::from_secs(BIRTHDAY_GENESIS_FROM_UNIX_EPOCH); let days = SystemTime::now() .duration_since(birthday_genesis_date) - .unwrap() + .unwrap_or_default() // default to the epoch on error .as_secs() / SECONDS_PER_DAY; - let birthday = u16::try_from(days).unwrap_or(0u16); + let birthday = u16::try_from(days).unwrap_or(0u16); // default to the epoch on error CipherSeed::new_with_birthday(birthday) } @@ -150,7 +151,7 @@ impl CipherSeed { const MILLISECONDS_PER_DAY: u64 = 24 * 60 * 60 * 1000; let millis = js_sys::Date::now() as u64; let days = millis / MILLISECONDS_PER_DAY; - let birthday = u16::try_from(days).unwrap_or(0u16); + let birthday = u16::try_from(days).unwrap_or(0u16); // default to the epoch on error CipherSeed::new_with_birthday(birthday) } @@ -158,7 +159,7 @@ impl CipherSeed { fn new_with_birthday(birthday: u16) -> Self { let mut entropy = Box::new([0u8; CIPHER_SEED_ENTROPY_BYTES]); OsRng.fill_bytes(entropy.as_mut()); - let mut salt = Box::new([0u8; CIPHER_SEED_MAIN_SALT_BYTES]); + let mut salt = [0u8; CIPHER_SEED_MAIN_SALT_BYTES]; OsRng.fill_bytes(salt.as_mut()); Self { @@ -180,9 +181,9 @@ impl CipherSeed { // Generate the MAC let mac = Self::generate_mac( + CIPHER_SEED_VERSION, &self.birthday.to_le_bytes(), self.entropy.as_ref(), - CIPHER_SEED_VERSION, self.salt.as_ref(), &mac_key, )?; @@ -256,9 +257,8 @@ impl CipherSeed { SafePassword::from_str(DEFAULT_CIPHER_SEED_PASSPHRASE) .expect("Failed to parse default cipher seed passphrase to SafePassword") }); - let salt: Box<[u8; CIPHER_SEED_MAIN_SALT_BYTES]> = encrypted_seed + let salt: [u8; CIPHER_SEED_MAIN_SALT_BYTES] = encrypted_seed .split_off(1 + CIPHER_SEED_BIRTHDAY_BYTES + CIPHER_SEED_ENTROPY_BYTES + CIPHER_SEED_MAC_BYTES) - .into_boxed_slice() .try_into() .map_err(|_| KeyManagerError::InvalidData)?; let (encryption_key, mac_key) = Self::derive_keys(&passphrase, salt.as_ref())?; @@ -280,7 +280,7 @@ impl CipherSeed { let birthday = u16::from_le_bytes(birthday_bytes); // Generate the MAC - let expected_mac = Self::generate_mac(&birthday_bytes, entropy.reveal(), version, salt.as_ref(), &mac_key)?; + let expected_mac = Self::generate_mac(version, &birthday_bytes, entropy.reveal(), salt.as_ref(), &mac_key)?; // Verify the MAC in constant time to avoid leaking data if mac.ct_eq(&expected_mac).into() { @@ -302,9 +302,11 @@ impl CipherSeed { salt: &[u8], ) -> Result<(), KeyManagerError> { // The ChaCha20 nonce is derived from the main salt - let encryption_nonce = mac_domain_hasher::>(LABEL_CHACHA20_ENCODING) - .chain(salt) - .finalize(); + let encryption_nonce = DomainSeparatedHasher::, KeyManagerDomain>::new_with_label( + HASHER_LABEL_CIPHER_SEED_ENCRYPTION_NONCE, + ) + .chain(salt) + .finalize(); let encryption_nonce = &encryption_nonce.as_ref()[..size_of::()]; // Encrypt/decrypt the data @@ -312,7 +314,9 @@ impl CipherSeed { Key::from_slice(encryption_key.reveal()), Nonce::from_slice(encryption_nonce), ); - cipher.apply_keystream(data); + cipher + .try_apply_keystream(data) + .map_err(|_| KeyManagerError::CryptographicError("Unable to apply stream cipher".to_string()))?; Ok(()) } @@ -329,9 +333,9 @@ impl CipherSeed { /// Generate a MAC using Blake2b fn generate_mac( + version: u8, birthday: &[u8], entropy: &[u8], - cipher_seed_version: u8, salt: &[u8], mac_key: &CipherSeedMacKey, ) -> Result, KeyManagerError> { @@ -346,23 +350,27 @@ impl CipherSeed { return Err(KeyManagerError::InvalidData); } - Ok(mac_domain_hasher::>(LABEL_MAC_GENERATION) - .chain(birthday) - .chain(entropy) - .chain([cipher_seed_version]) - .chain(salt) - .chain(mac_key.reveal()) - .finalize() - .as_ref()[..CIPHER_SEED_MAC_BYTES] - .to_vec()) + Ok( + DomainSeparatedHasher::, KeyManagerDomain>::new_with_label(HASHER_LABEL_CIPHER_SEED_MAC) + .chain([version]) + .chain(birthday) + .chain(entropy) + .chain(salt) + .chain(mac_key.reveal()) + .finalize() + .as_ref()[..CIPHER_SEED_MAC_BYTES] + .to_vec(), + ) } /// Use Argon2 to derive encryption and MAC keys from a passphrase and main salt fn derive_keys(passphrase: &SafePassword, salt: &[u8]) -> DerivedCipherSeedKeys { // The Argon2 salt is derived from the main salt - let argon2_salt = mac_domain_hasher::>(LABEL_ARGON_ENCODING) - .chain(salt) - .finalize(); + let argon2_salt = DomainSeparatedHasher::, KeyManagerDomain>::new_with_label( + HASHER_LABEL_CIPHER_SEED_PBKDF_SALT, + ) + .chain(salt) + .finalize(); let argon2_salt = &argon2_salt.as_ref()[..ARGON2_SALT_BYTES]; // Run Argon2 with enough output to accommodate both keys, so we only run it once diff --git a/base_layer/key_manager/src/key_manager.rs b/base_layer/key_manager/src/key_manager.rs index 0bac8d5995..58c27d6838 100644 --- a/base_layer/key_manager/src/key_manager.rs +++ b/base_layer/key_manager/src/key_manager.rs @@ -26,13 +26,13 @@ use derivative::Derivative; use digest::{consts::U64, typenum::IsEqual, Digest}; use serde::{Deserialize, Serialize}; use tari_crypto::{ - hashing::LengthExtensionAttackResistant, + hashing::{DomainSeparatedHasher, LengthExtensionAttackResistant}, keys::{PublicKey, SecretKey}, tari_utilities::byte_array::ByteArrayError, }; use zeroize::Zeroize; -use crate::{cipher_seed::CipherSeed, mac_domain_hasher, LABEL_DERIVE_KEY}; +use crate::{cipher_seed::CipherSeed, KeyManagerDomain, HASHER_LABEL_DERIVE_KEY}; #[derive(Clone, Derivative, Serialize, Deserialize, Zeroize)] #[derivative(Debug)] @@ -102,7 +102,7 @@ where // apply domain separation to generate derive key. Under the hood, the hashing api prepends the length of each // piece of data for concatenation, reducing the risk of collisions due to redundancy of variable length // input - let derive_key = mac_domain_hasher::(LABEL_DERIVE_KEY) + let derive_key = DomainSeparatedHasher::::new_with_label(HASHER_LABEL_DERIVE_KEY) .chain(self.seed.entropy()) .chain(self.branch_seed.as_bytes()) .chain(key_index.to_le_bytes()) @@ -174,7 +174,6 @@ where #[cfg(test)] mod test { use blake2::Blake2b; - use digest::consts::U64; use tari_crypto::ristretto::RistrettoPublicKey; use crate::key_manager::*; diff --git a/base_layer/key_manager/src/key_manager_service/error.rs b/base_layer/key_manager/src/key_manager_service/error.rs index 307114d802..ef632355cc 100644 --- a/base_layer/key_manager/src/key_manager_service/error.rs +++ b/base_layer/key_manager/src/key_manager_service/error.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use diesel::result::Error as DieselError; -use tari_common_sqlite::error::SqliteStorageError; +use tari_common_sqlite::error::{SqliteStorageError, StorageError}; use tari_crypto::errors::RangeProofError; use tari_utilities::{hex::HexError, ByteArrayError}; @@ -49,6 +49,18 @@ pub enum KeyManagerServiceError { TariKeyManagerError(#[from] KMError), #[error("Schnorr signature error: `{0}`")] SchnorrSignatureError(String), + #[error("Unknown error: `{0}`")] + UnknownError(String), + #[error("Ledger error: `{0}`")] + LedgerError(String), + #[error("The Ledger private key cannot be accessed or read")] + LedgerPrivateKeyInaccessible, + #[error("The Ledger view key cannot be accessed or read")] + LedgerViewKeyInaccessible, + #[error("Tari Key Manager storage error: `{0}`")] + StorageError(#[from] StorageError), + #[error("The imported private key cannot be accessed or read")] + ImportedPrivateKeyInaccessible, } impl From for KeyManagerServiceError { diff --git a/base_layer/key_manager/src/key_manager_service/handle.rs b/base_layer/key_manager/src/key_manager_service/handle.rs index b10ee8ccec..6b9f44186e 100644 --- a/base_layer/key_manager/src/key_manager_service/handle.rs +++ b/base_layer/key_manager/src/key_manager_service/handle.rs @@ -29,6 +29,7 @@ use crate::{ cipher_seed::CipherSeed, key_manager_service::{ error::KeyManagerServiceError, + interface::KeyAndId, storage::database::{KeyManagerBackend, KeyManagerDatabase}, AddResult, KeyId, @@ -76,7 +77,7 @@ where .add_key_manager_branch(&branch.into()) } - async fn get_next_key + Send>(&self, branch: T) -> Result<(KeyId, PK), KeyManagerServiceError> { + async fn get_next_key + Send>(&self, branch: T) -> Result, KeyManagerServiceError> { (*self.key_manager_inner) .read() .await @@ -84,6 +85,11 @@ where .await } + /// Gets a randomly generated key, which the key manager will manage + async fn get_random_key(&self) -> Result, KeyManagerServiceError> { + (*self.key_manager_inner).read().await.get_random_key().await + } + async fn get_static_key + Send>(&self, branch: T) -> Result, KeyManagerServiceError> { (*self.key_manager_inner) .read() diff --git a/base_layer/key_manager/src/key_manager_service/interface.rs b/base_layer/key_manager/src/key_manager_service/interface.rs index 80acc85864..f8ba62078f 100644 --- a/base_layer/key_manager/src/key_manager_service/interface.rs +++ b/base_layer/key_manager/src/key_manager_service/interface.rs @@ -25,11 +25,29 @@ use std::{fmt, str::FromStr}; use serde::{Deserialize, Serialize}; +use strum_macros::EnumIter; +use tari_common_types::WALLET_COMMS_AND_SPEND_KEY_BRANCH; use tari_crypto::keys::{PublicKey, SecretKey}; use tari_utilities::{hex::Hex, ByteArray}; use crate::key_manager_service::error::KeyManagerServiceError; +#[repr(u8)] +#[derive(Clone, Copy, EnumIter)] +pub enum KeyManagerBranch { + Comms, +} + +impl KeyManagerBranch { + /// Warning: Changing these strings will affect the backwards compatibility of the wallet with older databases or + /// recovery. + pub fn get_branch_key(self) -> String { + match self { + KeyManagerBranch::Comms => WALLET_COMMS_AND_SPEND_KEY_BRANCH.to_string(), + } + } +} + /// The value returned from [add_new_branch]. `AlreadyExists` is returned if the branch was previously created, /// otherwise `NewEntry` is returned. #[derive(Debug, PartialEq)] @@ -38,12 +56,23 @@ pub enum AddResult { AlreadyExists, } +#[derive(Debug, Eq, PartialEq)] +pub struct KeyAndId { + pub pub_key: PK, + pub key_id: KeyId, +} + #[derive(Default, Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub enum KeyId { Managed { branch: String, index: u64, }, + Derived { + branch: String, + label: String, + index: u64, + }, Imported { key: PK, }, @@ -57,6 +86,7 @@ where PK: Clone pub fn managed_index(&self) -> Option { match self { KeyId::Managed { index, .. } => Some(*index), + KeyId::Derived { index, .. } => Some(*index), KeyId::Imported { .. } => None, KeyId::Zero => None, } @@ -65,6 +95,7 @@ where PK: Clone pub fn managed_branch(&self) -> Option { match self { KeyId::Managed { branch, .. } => Some(branch.clone()), + KeyId::Derived { branch, .. } => Some(branch.clone()), KeyId::Imported { .. } => None, KeyId::Zero => None, } @@ -73,6 +104,7 @@ where PK: Clone pub fn imported(&self) -> Option { match self { KeyId::Managed { .. } => None, + KeyId::Derived { .. } => None, KeyId::Imported { key } => Some(key.clone()), KeyId::Zero => None, } @@ -80,6 +112,7 @@ where PK: Clone } pub const MANAGED_KEY_BRANCH: &str = "managed"; +pub const DERIVED_KEY_BRANCH: &str = "derived"; pub const IMPORTED_KEY_BRANCH: &str = "imported"; pub const ZERO_KEY_BRANCH: &str = "zero"; @@ -90,6 +123,11 @@ where PK: ByteArray fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { KeyId::Managed { branch: b, index: i } => write!(f, "{}.{}.{}", MANAGED_KEY_BRANCH, b, i), + KeyId::Derived { + branch: b, + label: l, + index: i, + } => write!(f, "{}.{}.{}.{}", DERIVED_KEY_BRANCH, b, l, i), KeyId::Imported { key: public_key } => write!(f, "{}.{}", IMPORTED_KEY_BRANCH, public_key.to_hex()), KeyId::Zero => write!(f, "{}", ZERO_KEY_BRANCH), } @@ -126,6 +164,19 @@ where PK: ByteArray Ok(KeyId::Imported { key }) }, ZERO_KEY_BRANCH => Ok(KeyId::Zero), + DERIVED_KEY_BRANCH => { + if parts.len() != 4 { + return Err("Wrong format".to_string()); + } + let index = parts[3] + .parse() + .map_err(|_| "Index for default, invalid u64".to_string())?; + Ok(KeyId::Derived { + branch: parts[1].into(), + label: parts[2].into(), + index, + }) + }, _ => Err("Wrong format".to_string()), }, } @@ -147,7 +198,10 @@ where async fn add_new_branch + Send>(&self, branch: T) -> Result; /// Gets the next key id from the branch. This will auto-increment the branch key index by 1 - async fn get_next_key + Send>(&self, branch: T) -> Result<(KeyId, PK), KeyManagerServiceError>; + async fn get_next_key + Send>(&self, branch: T) -> Result, KeyManagerServiceError>; + + /// Gets a randomly generated key, which the key manager will manage + async fn get_random_key(&self) -> Result, KeyManagerServiceError>; /// Gets the fixed key id from the branch. This will use the branch key with index 0 async fn get_static_key + Send>(&self, branch: T) -> Result, KeyManagerServiceError>; diff --git a/base_layer/key_manager/src/key_manager_service/mod.rs b/base_layer/key_manager/src/key_manager_service/mod.rs index d5670676c4..d7eb072ec1 100644 --- a/base_layer/key_manager/src/key_manager_service/mod.rs +++ b/base_layer/key_manager/src/key_manager_service/mod.rs @@ -51,5 +51,4 @@ pub use service::KeyManagerInner; mod interface; pub use interface::KeyId; pub mod storage; - -pub use interface::{AddResult, KeyManagerInterface}; +pub use interface::{AddResult, KeyAndId, KeyManagerBranch, KeyManagerInterface}; diff --git a/base_layer/key_manager/src/key_manager_service/service.rs b/base_layer/key_manager/src/key_manager_service/service.rs index acd914b16b..681edc5bb4 100644 --- a/base_layer/key_manager/src/key_manager_service/service.rs +++ b/base_layer/key_manager/src/key_manager_service/service.rs @@ -21,16 +21,24 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::collections::HashMap; +use argon2::password_hash::rand_core::OsRng; +use blake2::Blake2b; +use digest::consts::U64; use futures::lock::Mutex; use log::*; -use tari_crypto::keys::PublicKey; -use tari_utilities::hex::Hex; +use tari_crypto::{ + hash_domain, + hashing::DomainSeparatedHasher, + keys::{PublicKey, SecretKey}, +}; +use tari_utilities::ByteArray; use crate::{ cipher_seed::CipherSeed, key_manager::KeyManager, key_manager_service::{ error::KeyManagerServiceError, + interface::KeyAndId, storage::database::{KeyManagerBackend, KeyManagerDatabase, KeyManagerState}, AddResult, KeyDigest, @@ -41,6 +49,8 @@ use crate::{ const LOG_TARGET: &str = "key_manager::key_manager_service"; const KEY_MANAGER_MAX_SEARCH_DEPTH: u64 = 1_000_000; +hash_domain!(KeyManagerHashingDomain, "com.tari.base_layer.key_manager", 1); + pub struct KeyManagerInner { key_managers: HashMap>>, db: KeyManagerDatabase, @@ -88,7 +98,7 @@ where Ok(result) } - pub async fn get_next_key(&self, branch: &str) -> Result<(KeyId, PK), KeyManagerServiceError> { + pub async fn get_next_key(&self, branch: &str) -> Result, KeyManagerServiceError> { let mut km = self .key_managers .get(branch) @@ -98,13 +108,24 @@ where self.db.increment_key_index(branch)?; let index = km.increment_key_index(1); let key = km.derive_public_key(index)?.key; - Ok(( - KeyId::Managed { + + Ok(KeyAndId { + key_id: KeyId::Managed { branch: branch.to_string(), index, }, - key, - )) + pub_key: key, + }) + } + + pub async fn get_random_key(&self) -> Result, KeyManagerServiceError> { + let random_private_key = PK::K::random(&mut OsRng); + let key_id = self.import_key(random_private_key).await?; + let public_key = self.get_public_key_at_key_id(&key_id).await?; + Ok(KeyAndId { + key_id, + pub_key: public_key, + }) } pub async fn get_static_key(&self, branch: &str) -> Result, KeyManagerServiceError> { @@ -128,6 +149,29 @@ where .await; Ok(km.derive_public_key(*index)?.key) }, + KeyId::Derived { branch, index, .. } => { + let km = self + .key_managers + .get(branch) + .ok_or(KeyManagerServiceError::UnknownKeyBranch)? + .lock() + .await; + let branch_key = km.get_private_key(*index)?; + + let public_key = { + let hasher = DomainSeparatedHasher::, KeyManagerHashingDomain>::new_with_label( + "Key manager derived key", + ); + let hasher = hasher.chain(branch_key.as_bytes()).finalize(); + let private_key = PK::K::from_uniform_bytes(hasher.as_ref()).map_err(|_| { + KeyManagerServiceError::UnknownError( + "Invalid private key for Key manager derived key".to_string(), + ) + })?; + PK::from_secret_key(&private_key) + }; + Ok(public_key) + }, KeyId::Imported { key } => Ok(key.clone()), KeyId::Zero => Ok(PK::default()), } @@ -178,9 +222,7 @@ where pub async fn import_key(&self, private_key: PK::K) -> Result, KeyManagerServiceError> { let public_key = PK::from_secret_key(&private_key); - let hex_key = public_key.to_hex(); self.db.insert_imported_key(public_key.clone(), private_key)?; - trace!(target: LOG_TARGET, "Imported key {}", hex_key); let key_id = KeyId::Imported { key: public_key }; Ok(key_id) } diff --git a/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/imported_keys.rs b/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/imported_keys.rs index 066bec86b8..2a2ad7fa03 100644 --- a/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/imported_keys.rs +++ b/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/imported_keys.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use chacha20poly1305::XChaCha20Poly1305; use chrono::{NaiveDateTime, Utc}; -use diesel::{prelude::*, SqliteConnection}; +use diesel::prelude::*; use tari_common_types::encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce}; use tari_crypto::keys::PublicKey; use tari_utilities::{hex::Hex, ByteArray, Hidden}; diff --git a/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/key_manager_state.rs b/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/key_manager_state.rs index be569018c5..c6c64894df 100644 --- a/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/key_manager_state.rs +++ b/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/key_manager_state.rs @@ -20,11 +20,9 @@ // 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; - use chacha20poly1305::XChaCha20Poly1305; use chrono::{NaiveDateTime, Utc}; -use diesel::{prelude::*, SqliteConnection}; +use diesel::prelude::*; use tari_common_sqlite::util::diesel_ext::ExpectedRowsExtension; use tari_common_types::encryption::{decrypt_bytes_integral_nonce, encrypt_bytes_integral_nonce}; use tari_utilities::{ByteArray, Hidden}; diff --git a/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/mod.rs b/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/mod.rs index 4da82737fc..1e92eb5d42 100644 --- a/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/mod.rs +++ b/base_layer/key_manager/src/key_manager_service/storage/sqlite_db/mod.rs @@ -20,10 +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. -use std::{ - convert::TryFrom, - sync::{Arc, RwLock}, -}; +use std::sync::{Arc, RwLock}; use chacha20poly1305::XChaCha20Poly1305; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; @@ -61,7 +58,7 @@ impl + C { /// Creates a new sql backend from provided wallet db connection /// * `cipher` is used to encrypt the sensitive fields in the database, a cipher is derived - /// from a provided password, which we enforce for class instantiation + /// * from a provided password, which we enforce for class instantiation fn new(database_connection: TKeyManagerDbConnection, cipher: XChaCha20Poly1305) -> Self { Self { database_connection: Arc::new(database_connection), @@ -246,7 +243,7 @@ where if start.elapsed().as_millis() > 0 { trace!( target: LOG_TARGET, - "sqlite profile - insert_imported_key: lock {} + db_op {} = {} ms", + "sqlite profile - get_imported_key: lock {} + db_op {} = {} ms", acquire_lock.as_millis(), (start.elapsed() - acquire_lock).as_millis(), start.elapsed().as_millis() @@ -259,14 +256,10 @@ where #[cfg(test)] mod test { - use std::convert::TryFrom; - use diesel::{sql_query, Connection, RunQueryDsl, SqliteConnection}; - use diesel_migrations::{EmbeddedMigrations, MigrationHarness}; use tempfile::tempdir; use super::*; - use crate::key_manager_service::storage::sqlite_db::{KeyManagerState, KeyManagerStateSql, NewKeyManagerStateSql}; #[test] fn test_key_manager_crud() { diff --git a/base_layer/key_manager/src/lib.rs b/base_layer/key_manager/src/lib.rs index 6118226c6e..33f6a1f411 100644 --- a/base_layer/key_manager/src/lib.rs +++ b/base_layer/key_manager/src/lib.rs @@ -4,11 +4,7 @@ use std::str::FromStr; use cipher_seed::BIRTHDAY_GENESIS_FROM_UNIX_EPOCH; -use digest::Digest; -use tari_crypto::{ - hash_domain, - hashing::{DomainSeparatedHasher, LengthExtensionAttackResistant}, -}; +use tari_crypto::hash_domain; use tari_utilities::{hidden::Hidden, hidden_type, safe_array::SafeArray}; use zeroize::Zeroize; @@ -30,16 +26,10 @@ pub mod schema; hash_domain!(KeyManagerDomain, "com.tari.base_layer.key_manager", 1); -const LABEL_ARGON_ENCODING: &str = "argon2_encoding"; -const LABEL_CHACHA20_ENCODING: &str = "chacha20_encoding"; -const LABEL_MAC_GENERATION: &str = "mac_generation"; -const LABEL_DERIVE_KEY: &str = "derive_key"; - -pub(crate) fn mac_domain_hasher( - label: &'static str, -) -> DomainSeparatedHasher { - DomainSeparatedHasher::::new_with_label(label) -} +const HASHER_LABEL_CIPHER_SEED_PBKDF_SALT: &str = "cipher_seed_pbkdf_salt"; +const HASHER_LABEL_CIPHER_SEED_ENCRYPTION_NONCE: &str = "cipher_seed_encryption_nonce"; +const HASHER_LABEL_CIPHER_SEED_MAC: &str = "cipher_seed_mac"; +const HASHER_LABEL_DERIVE_KEY: &str = "derive_key"; hidden_type!(CipherSeedEncryptionKey, SafeArray); hidden_type!(CipherSeedMacKey, SafeArray< u8, CIPHER_SEED_MAC_KEY_BYTES>); diff --git a/base_layer/key_manager/src/mnemonic.rs b/base_layer/key_manager/src/mnemonic.rs index eb99b57a1b..9a783bf5aa 100644 --- a/base_layer/key_manager/src/mnemonic.rs +++ b/base_layer/key_manager/src/mnemonic.rs @@ -282,7 +282,7 @@ pub trait Mnemonic { mod test { use std::str::FromStr; - use rand::{self, rngs::OsRng}; + use rand::rngs::OsRng; use tari_crypto::{keys::SecretKey, ristretto::RistrettoSecretKey, tari_utilities::byte_array::ByteArray}; use super::*; diff --git a/base_layer/mmr/src/backend.rs b/base_layer/mmr/src/backend.rs index 69235daf01..fe5943420b 100644 --- a/base_layer/mmr/src/backend.rs +++ b/base_layer/mmr/src/backend.rs @@ -41,7 +41,7 @@ pub trait ArrayLike { /// Return the item at the given index fn get(&self, index: usize) -> Result, Self::Error>; - /// Remove all stored items from the the backend. + /// Remove all stored items from the backend. fn clear(&mut self) -> Result<(), Self::Error>; /// Finds the index of the specified stored item, it will return None if the object could not be found. diff --git a/base_layer/mmr/src/common.rs b/base_layer/mmr/src/common.rs index 80358703dd..b2a74668f1 100644 --- a/base_layer/mmr/src/common.rs +++ b/base_layer/mmr/src/common.rs @@ -30,7 +30,7 @@ use tari_common::DomainDigest; use crate::{error::MerkleMountainRangeError, Hash}; -const ALL_ONES: usize = std::usize::MAX; +const ALL_ONES: usize = usize::MAX; #[derive(Copy, Clone)] pub struct LeafIndex(pub usize); diff --git a/base_layer/mmr/src/lib.rs b/base_layer/mmr/src/lib.rs index db0054bf33..158464e875 100644 --- a/base_layer/mmr/src/lib.rs +++ b/base_layer/mmr/src/lib.rs @@ -112,7 +112,7 @@ //! //! * _All_ indices are numbered starting from zero. //! * MMR nodes refer to all the nodes in the Merkle Mountain Range and are ordered in the canonical mmr ordering -//! described above. +//! * described above. //! * Leaf nodes are numbered counting from zero and increment by one each time a leaf is added. //! //! To illustrate, consider this MMR: diff --git a/base_layer/mmr/src/merkle_mountain_range.rs b/base_layer/mmr/src/merkle_mountain_range.rs index 0eef7fee65..8aa3338ea1 100644 --- a/base_layer/mmr/src/merkle_mountain_range.rs +++ b/base_layer/mmr/src/merkle_mountain_range.rs @@ -23,7 +23,6 @@ use std::{ cmp::{max, min}, convert::TryInto, - iter::IntoIterator, marker::PhantomData, }; diff --git a/base_layer/mmr/src/sparse_merkle_tree/bit_utils.rs b/base_layer/mmr/src/sparse_merkle_tree/bit_utils.rs index d5b8bbc5ff..0aa5ed472c 100644 --- a/base_layer/mmr/src/sparse_merkle_tree/bit_utils.rs +++ b/base_layer/mmr/src/sparse_merkle_tree/bit_utils.rs @@ -99,7 +99,6 @@ pub fn traverse_direction( #[cfg(test)] mod test { use super::*; - use crate::sparse_merkle_tree::{bit_utils::count_common_prefix, NodeKey}; #[test] fn test_common_prefix() { diff --git a/base_layer/mmr/src/sparse_merkle_tree/mod.rs b/base_layer/mmr/src/sparse_merkle_tree/mod.rs index 28a367c492..a6ff68c533 100644 --- a/base_layer/mmr/src/sparse_merkle_tree/mod.rs +++ b/base_layer/mmr/src/sparse_merkle_tree/mod.rs @@ -22,8 +22,8 @@ //! * B: 01011111 (95 in decimal) //! * C: 11100000 (224 in decimal) //! * D: 11110000 (240 in decimal) -//! you will notice that they the first two diverge at the first bit, while the first and last pairs differ at the -//! fourth bit. This results in a SMT that looks like this: +//! * you will notice that they the first two diverge at the first bit, while the first and last pairs differ at the +//! * fourth bit. This results in a SMT that looks like this: //! //! r###" ┌──────┐ "### //! r###" ┌─────┤ root ├─────┐ "### diff --git a/base_layer/mmr/src/sparse_merkle_tree/node.rs b/base_layer/mmr/src/sparse_merkle_tree/node.rs index 6ac8be86c8..6d17a1426f 100644 --- a/base_layer/mmr/src/sparse_merkle_tree/node.rs +++ b/base_layer/mmr/src/sparse_merkle_tree/node.rs @@ -510,8 +510,7 @@ impl> BranchNode { #[cfg(test)] mod test { use blake2::Blake2b; - use digest::consts::U32; - use rand::{self, RngCore}; + use rand::RngCore; use super::*; use crate::sparse_merkle_tree::bit_utils::TraverseDirection::{Left, Right}; diff --git a/base_layer/mmr/src/sparse_merkle_tree/proofs.rs b/base_layer/mmr/src/sparse_merkle_tree/proofs.rs index cf7b3405fe..1d3203fa04 100644 --- a/base_layer/mmr/src/sparse_merkle_tree/proofs.rs +++ b/base_layer/mmr/src/sparse_merkle_tree/proofs.rs @@ -98,7 +98,7 @@ pub struct InclusionProof { /// ``` pub struct ExclusionProof { siblings: Vec, - // The terminal node of the tree proof, or `None` if the the node is `Empty`. + // The terminal node of the tree proof, or `None` if the node is `Empty`. leaf: Option>, phantom: std::marker::PhantomData, } @@ -227,7 +227,6 @@ impl> MerkleProofDigest for ExclusionProof { #[cfg(test)] mod test { use blake2::Blake2b; - use digest::consts::U32; use super::*; diff --git a/base_layer/mmr/tests/tests/with_blake512_hash.rs b/base_layer/mmr/tests/tests/with_blake512_hash.rs index 48488cf080..393ee30544 100644 --- a/base_layer/mmr/tests/tests/with_blake512_hash.rs +++ b/base_layer/mmr/tests/tests/with_blake512_hash.rs @@ -20,8 +20,6 @@ // 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::string::ToString; - use blake2::Blake2b; use digest::consts::U64; use tari_crypto::{hash_domain, hashing::DomainSeparatedHasher}; diff --git a/base_layer/p2p/examples/gen_node_identity.rs b/base_layer/p2p/examples/gen_node_identity.rs index 52e65086bf..df12b8dce7 100644 --- a/base_layer/p2p/examples/gen_node_identity.rs +++ b/base_layer/p2p/examples/gen_node_identity.rs @@ -40,7 +40,7 @@ use tari_comms::{ use tari_utilities::message_format::MessageFormat; fn random_address() -> Multiaddr { - let port = OsRng.gen_range(9000..std::u16::MAX); + let port = OsRng.gen_range(9000..u16::MAX); let socket_addr: SocketAddr = (Ipv4Addr::LOCALHOST, port).into(); socketaddr_to_multiaddr(&socket_addr) } diff --git a/base_layer/p2p/src/auto_update/mod.rs b/base_layer/p2p/src/auto_update/mod.rs index 97c28a4acb..dc9e52614e 100644 --- a/base_layer/p2p/src/auto_update/mod.rs +++ b/base_layer/p2p/src/auto_update/mod.rs @@ -213,7 +213,6 @@ fn maintainers() -> impl Iterator { #[cfg(test)] mod test { - use config; use tari_common::DefaultConfigLoader; use super::*; diff --git a/base_layer/p2p/src/comms_connector/inbound_connector.rs b/base_layer/p2p/src/comms_connector/inbound_connector.rs index bc31992d93..e56367c499 100644 --- a/base_layer/p2p/src/comms_connector/inbound_connector.rs +++ b/base_layer/p2p/src/comms_connector/inbound_connector.rs @@ -110,8 +110,6 @@ impl InboundDomainConnector { mod test { use futures::executor::block_on; use tari_comms::{message::MessageExt, wrap_in_envelope_body}; - use tari_comms_dht::domain_message::MessageHeader; - use tokio::sync::mpsc; use tower::ServiceExt; use super::*; diff --git a/base_layer/p2p/src/config.rs b/base_layer/p2p/src/config.rs index 734ac7632e..601a71ec5f 100644 --- a/base_layer/p2p/src/config.rs +++ b/base_layer/p2p/src/config.rs @@ -112,11 +112,9 @@ pub struct P2pConfig { pub listener_liveness_max_sessions: usize, /// If Some, enables periodic socket-level liveness checks #[serde(with = "serializers::optional_seconds")] - pub listener_liveness_check_interval: Option, + pub listener_self_liveness_check_interval: Option, /// CIDR for addresses allowed to enter into liveness check mode on the listener. pub listener_liveness_allowlist_cidrs: StringList, - /// User agent string for this node - pub user_agent: String, /// The address to bind on using the TCP transport _in addition to_ the primary transport. This is typically useful /// for direct comms between a wallet and base node. If this is set to None, no listener will be bound. /// Default: None @@ -146,9 +144,8 @@ impl Default for P2pConfig { }, allow_test_addresses: false, listener_liveness_max_sessions: 0, - listener_liveness_check_interval: None, + listener_self_liveness_check_interval: None, listener_liveness_allowlist_cidrs: StringList::default(), - user_agent: String::new(), auxiliary_tcp_listener_address: None, rpc_max_simultaneous_sessions: 100, rpc_max_sessions_per_peer: 10, diff --git a/base_layer/p2p/src/dns/client.rs b/base_layer/p2p/src/dns/client.rs index cd889fd034..50e4a61829 100644 --- a/base_layer/p2p/src/dns/client.rs +++ b/base_layer/p2p/src/dns/client.rs @@ -204,13 +204,7 @@ fn default_client_config() -> Arc { #[cfg(test)] mod mock { - use std::sync::Arc; - - use tari_shutdown::Shutdown; - use trust_dns_client::proto::error::ProtoError; - use super::*; - use crate::dns::mock::{DefaultOnSend, MockClientHandle}; impl Client> { pub async fn connect_mock(messages: Vec>) -> Result { diff --git a/base_layer/p2p/src/dns/mock.rs b/base_layer/p2p/src/dns/mock.rs index e4ee81a4a7..953f10e653 100644 --- a/base_layer/p2p/src/dns/mock.rs +++ b/base_layer/p2p/src/dns/mock.rs @@ -87,6 +87,7 @@ pub fn message(query: Query, answers: Vec, name_servers: Vec, ad } pub trait OnSend: Clone + Send + Sync + 'static { + #[allow(dead_code)] fn on_send( &mut self, response: Result, diff --git a/base_layer/p2p/src/domain_message.rs b/base_layer/p2p/src/domain_message.rs index 4217fb5de9..60a7b151a0 100644 --- a/base_layer/p2p/src/domain_message.rs +++ b/base_layer/p2p/src/domain_message.rs @@ -20,7 +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. -use std::convert::{From, TryFrom}; +use std::convert::TryFrom; use tari_comms::{peer_manager::Peer, types::CommsPublicKey}; use tari_comms_dht::envelope::DhtMessageHeader; diff --git a/base_layer/p2p/src/initialization.rs b/base_layer/p2p/src/initialization.rs index d4cf62ca97..75465d69d6 100644 --- a/base_layer/p2p/src/initialization.rs +++ b/base_layer/p2p/src/initialization.rs @@ -445,6 +445,7 @@ pub async fn add_seed_peers( pub struct P2pInitializer { config: P2pConfig, + user_agent: String, seed_config: PeerSeedsConfig, network: Network, node_identity: Arc, @@ -454,6 +455,7 @@ pub struct P2pInitializer { impl P2pInitializer { pub fn new( config: P2pConfig, + user_agent: String, seed_config: PeerSeedsConfig, network: Network, node_identity: Arc, @@ -461,6 +463,7 @@ impl P2pInitializer { ) -> Self { Self { config, + user_agent, seed_config, network, node_identity, @@ -557,9 +560,14 @@ impl ServiceInitializer for P2pInitializer { major_version: MAJOR_NETWORK_VERSION, minor_version: MINOR_NETWORK_VERSION, network_byte: self.network.as_byte(), - user_agent: config.user_agent.clone(), + user_agent: self.user_agent.clone(), }) - .set_liveness_check(config.listener_liveness_check_interval); + .with_minimize_connections(if self.config.dht.minimize_connections { + Some(self.config.dht.num_neighbouring_nodes + self.config.dht.num_random_nodes) + } else { + None + }) + .set_self_liveness_check(config.listener_self_liveness_check_interval); if config.allow_test_addresses || config.dht.peer_validator_config.allow_test_addresses { // The default is false, so ensure that both settings are true in this case diff --git a/base_layer/p2p/src/peer_seeds.rs b/base_layer/p2p/src/peer_seeds.rs index b55feeac2f..0e1311e431 100644 --- a/base_layer/p2p/src/peer_seeds.rs +++ b/base_layer/p2p/src/peer_seeds.rs @@ -160,8 +160,6 @@ impl From for Peer { #[cfg(test)] mod test { - use tari_utilities::hex::Hex; - use super::*; const TEST_NAME: &str = "test.local."; diff --git a/base_layer/p2p/src/services/liveness/error.rs b/base_layer/p2p/src/services/liveness/error.rs index 4b6973ef14..07e7a99547 100644 --- a/base_layer/p2p/src/services/liveness/error.rs +++ b/base_layer/p2p/src/services/liveness/error.rs @@ -20,7 +20,12 @@ // 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_comms::{connectivity::ConnectivityError, message::MessageError, PeerConnectionError}; +use tari_comms::{ + connectivity::ConnectivityError, + message::MessageError, + peer_manager::PeerManagerError, + PeerConnectionError, +}; use tari_comms_dht::{outbound::DhtOutboundError, DhtActorError}; use tari_service_framework::reply_channel::TransportChannelError; use thiserror::Error; @@ -53,4 +58,6 @@ pub enum LivenessError { NodeIdDoesNotExist, #[error("PingPongDecodeError: {0}")] PingPongDecodeError(#[from] prost::DecodeError), + #[error("Peer not found: `{0}`")] + PeerNotFoundError(#[from] PeerManagerError), } diff --git a/base_layer/p2p/src/services/liveness/mod.rs b/base_layer/p2p/src/services/liveness/mod.rs index 4faa04e9c2..53031bbe7a 100644 --- a/base_layer/p2p/src/services/liveness/mod.rs +++ b/base_layer/p2p/src/services/liveness/mod.rs @@ -63,7 +63,7 @@ use std::sync::Arc; use futures::{Stream, StreamExt}; use log::*; -use tari_comms::connectivity::ConnectivityRequester; +use tari_comms::{connectivity::ConnectivityRequester, PeerManager}; use tari_comms_dht::Dht; use tari_service_framework::{ async_trait, @@ -136,6 +136,7 @@ impl ServiceInitializer for LivenessInitializer { let dht = handles.expect_handle::(); let connectivity = handles.expect_handle::(); let outbound_messages = dht.outbound_requester(); + let peer_manager = handles.expect_handle::>(); let service = LivenessService::new( config, @@ -146,6 +147,7 @@ impl ServiceInitializer for LivenessInitializer { outbound_messages, publisher, handles.get_shutdown_signal(), + peer_manager, ); service.run().await; debug!(target: LOG_TARGET, "Liveness service has shut down"); diff --git a/base_layer/p2p/src/services/liveness/service.rs b/base_layer/p2p/src/services/liveness/service.rs index 29724b946a..b3e02e82c6 100644 --- a/base_layer/p2p/src/services/liveness/service.rs +++ b/base_layer/p2p/src/services/liveness/service.rs @@ -28,6 +28,8 @@ use tari_comms::{ connectivity::{ConnectivityRequester, ConnectivitySelection}, peer_manager::NodeId, types::CommsPublicKey, + Minimized, + PeerManager, }; use tari_comms_dht::{ domain_message::OutboundDomainMessage, @@ -64,6 +66,7 @@ pub struct LivenessService { event_publisher: LivenessEventSender, shutdown_signal: ShutdownSignal, monitored_peers: Arc>>, + peer_manager: Arc, } impl LivenessService @@ -80,6 +83,7 @@ where outbound_messaging: OutboundMessageRequester, event_publisher: LivenessEventSender, shutdown_signal: ShutdownSignal, + peer_manager: Arc, ) -> Self { Self { request_rx: Some(request_rx), @@ -91,6 +95,7 @@ where shutdown_signal, config: config.clone(), monitored_peers: Arc::new(RwLock::new(config.monitored_peers)), + peer_manager, } } @@ -157,8 +162,8 @@ where inner: ping_pong_msg, .. } = msg; - let node_id = source_peer.node_id; - let public_key = source_peer.public_key; + let node_id = source_peer.node_id.clone(); + let public_key = source_peer.public_key.clone(); let message_tag = dht_header.message_tag; let ping_pong_msg = match ping_pong_msg { Ok(p) => p, @@ -214,8 +219,14 @@ where message_tag, ); - let pong_event = PingPongEvent::new(node_id, maybe_latency, ping_pong_msg.metadata.into()); + let pong_event = PingPongEvent::new(node_id.clone(), maybe_latency, ping_pong_msg.metadata.into()); self.publish_event(LivenessEvent::ReceivedPong(Box::new(pong_event))); + + if let Some(address) = source_peer.last_address_used() { + self.peer_manager + .update_peer_address_latency_and_last_seen(&public_key, &address, maybe_latency) + .await?; + } }, } Ok(()) @@ -350,7 +361,7 @@ where target: LOG_TARGET, "Disconnecting peer {} that failed {} rounds of pings", node_id, max_allowed_ping_failures ); - conn.disconnect().await?; + conn.disconnect(Minimized::No).await?; } } self.state.clear_failed_pings(); @@ -384,8 +395,9 @@ mod test { use tari_comms::{ message::MessageTag, net_address::MultiaddressesWithStats, - peer_manager::{NodeId, Peer, PeerFeatures, PeerFlags}, + peer_manager::{Peer, PeerFeatures, PeerFlags}, test_utils::mocks::create_connectivity_mock, + types::CommsDatabase, }; use tari_comms_dht::{ envelope::{DhtMessageHeader, DhtMessageType}, @@ -395,6 +407,8 @@ mod test { use tari_crypto::keys::PublicKey; use tari_service_framework::reply_channel; use tari_shutdown::Shutdown; + use tari_storage::lmdb_store::{LMDBBuilder, LMDBConfig}; + use tari_test_utils::{paths::create_temporary_data_path, random}; use tokio::{ sync::{broadcast, mpsc, oneshot}, task, @@ -406,6 +420,24 @@ mod test { services::liveness::{handle::LivenessHandle, state::Metadata}, }; + pub fn build_peer_manager() -> Arc { + let database_name = random::string(8); + let path = create_temporary_data_path(); + let datastore = LMDBBuilder::new() + .set_path(path.to_str().unwrap()) + .set_env_config(LMDBConfig::default()) + .set_max_number_of_databases(1) + .add_database(&database_name, lmdb_zero::db::CREATE) + .build() + .unwrap(); + + let peer_database = datastore.get_handle(&database_name).unwrap(); + + PeerManager::new(CommsDatabase::new(Arc::new(peer_database)), None) + .map(Arc::new) + .unwrap() + } + #[tokio::test] async fn get_ping_pong_count() { let mut state = LivenessState::new(); @@ -436,6 +468,7 @@ mod test { outbound_messaging, publisher, shutdown.to_signal(), + build_peer_manager(), ); // Run the service @@ -471,6 +504,7 @@ mod test { outbound_messaging, publisher, shutdown.to_signal(), + build_peer_manager(), ); // Run the LivenessService @@ -544,6 +578,7 @@ mod test { let (publisher, _) = broadcast::channel(200); let shutdown = Shutdown::new(); + let service = LivenessService::new( Default::default(), stream::empty(), @@ -553,6 +588,7 @@ mod test { outbound_messaging, publisher, shutdown.to_signal(), + build_peer_manager(), ); task::spawn(service.run()); @@ -595,6 +631,7 @@ mod test { outbound_messaging, publisher.clone(), shutdown.to_signal(), + build_peer_manager(), ); task::spawn(service.run()); diff --git a/base_layer/p2p/src/services/liveness/state.rs b/base_layer/p2p/src/services/liveness/state.rs index 0c2282811b..0c29d20d31 100644 --- a/base_layer/p2p/src/services/liveness/state.rs +++ b/base_layer/p2p/src/services/liveness/state.rs @@ -162,7 +162,7 @@ impl LivenessState { /// Returns true if the nonce is inflight, otherwise false pub fn is_inflight(&self, nonce: u64) -> bool { - self.inflight_pings.get(&nonce).is_some() + self.inflight_pings.contains_key(&nonce) } /// Records a pong. Specifically, the pong counter is incremented and @@ -345,8 +345,8 @@ mod test { state.add_inflight_ping(2, peer2.clone()); state.add_inflight_ping(3, peer2.clone()); - assert!(state.failed_pings.get(&peer1).is_none()); - assert!(state.failed_pings.get(&peer2).is_none()); + assert!(!state.failed_pings.contains_key(&peer1)); + assert!(!state.failed_pings.contains_key(&peer2)); // MAX_INFLIGHT_TTL passes for n in [1, 2, 3] { @@ -363,6 +363,6 @@ mod test { assert!(state.record_pong(2, &peer2).is_none()); let n = state.failed_pings.get(&peer1).unwrap(); assert_eq!(*n, 1); - assert!(state.failed_pings.get(&peer2).is_none()); + assert!(!state.failed_pings.contains_key(&peer2)); } } diff --git a/base_layer/p2p/src/transport.rs b/base_layer/p2p/src/transport.rs index a220fa9d0e..939a96329e 100644 --- a/base_layer/p2p/src/transport.rs +++ b/base_layer/p2p/src/transport.rs @@ -147,7 +147,7 @@ pub struct TorTransportConfig { /// When set to true, outbound TCP connections bypass the tor proxy. Defaults to false for better privacy, setting /// to true may improve network performance for TCP nodes. pub proxy_bypass_for_outbound_tcp: bool, - /// If set, instructs tor to forward traffic the the provided address. Otherwise, an OS-assigned port on 127.0.0.1 + /// If set, instructs tor to forward traffic the provided address. Otherwise, an OS-assigned port on 127.0.0.1 /// is used. pub forward_address: Option, /// If set, the listener will bind to this address instead of the forward_address. diff --git a/base_layer/p2p/tests/services/liveness.rs b/base_layer/p2p/tests/services/liveness.rs index 7a88eca350..432b3ae68b 100644 --- a/base_layer/p2p/tests/services/liveness.rs +++ b/base_layer/p2p/tests/services/liveness.rs @@ -54,6 +54,7 @@ pub async fn setup_liveness_service( let handles = StackBuilder::new(comms.shutdown_signal()) .add_initializer(RegisterHandle::new(dht.clone())) .add_initializer(RegisterHandle::new(comms.connectivity())) + .add_initializer(RegisterHandle::new(comms.peer_manager())) .add_initializer(LivenessInitializer::new( Default::default(), Arc::clone(&subscription_factory), diff --git a/base_layer/service_framework/src/context/handles.rs b/base_layer/service_framework/src/context/handles.rs index 2042c8f4e8..1eb4bfce10 100644 --- a/base_layer/service_framework/src/context/handles.rs +++ b/base_layer/service_framework/src/context/handles.rs @@ -208,8 +208,6 @@ impl ServiceHandles { #[cfg(test)] mod test { - use tari_shutdown::Shutdown; - use super::*; #[test] diff --git a/base_layer/service_framework/src/context/lazy_service.rs b/base_layer/service_framework/src/context/lazy_service.rs index 313558f94d..c018ea1305 100644 --- a/base_layer/service_framework/src/context/lazy_service.rs +++ b/base_layer/service_framework/src/context/lazy_service.rs @@ -95,12 +95,9 @@ where #[cfg(test)] mod test { - use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - task::Poll, + use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, }; use futures::future::{self, poll_fn}; diff --git a/base_layer/service_framework/src/stack.rs b/base_layer/service_framework/src/stack.rs index 34bb819a38..83eaf20a76 100644 --- a/base_layer/service_framework/src/stack.rs +++ b/base_layer/service_framework/src/stack.rs @@ -97,12 +97,11 @@ mod test { }; use async_trait::async_trait; - use futures::{executor::block_on, future}; + use futures::executor::block_on; use tari_shutdown::Shutdown; use tower::service_fn; use super::*; - use crate::{initializer::ServiceInitializer, ServiceInitializerContext}; #[tokio::test] async fn service_defn_simple() { diff --git a/base_layer/service_framework/src/tower/service_ext.rs b/base_layer/service_framework/src/tower/service_ext.rs index 6c0a506792..cd52890f5f 100644 --- a/base_layer/service_framework/src/tower/service_ext.rs +++ b/base_layer/service_framework/src/tower/service_ext.rs @@ -93,7 +93,7 @@ mod test { Arc, }; - use futures::{future, FutureExt}; + use futures::future; use futures_test::task::panic_context; use tower::service_fn; diff --git a/base_layer/tari_mining_helper_ffi/Cargo.toml b/base_layer/tari_mining_helper_ffi/Cargo.toml index 3b6a72401f..d465deb265 100644 --- a/base_layer/tari_mining_helper_ffi/Cargo.toml +++ b/base_layer/tari_mining_helper_ffi/Cargo.toml @@ -30,3 +30,6 @@ tari_common = { workspace = true, features = ["build"] } [lib] crate-type = ["cdylib"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tari_target_network_mainnet)', 'cfg(tari_target_network_nextnet)', 'cfg(tari_target_network_testnet)'] } diff --git a/base_layer/tari_mining_helper_ffi/src/error.rs b/base_layer/tari_mining_helper_ffi/src/error.rs index e9fea91c8d..81a80f2f21 100644 --- a/base_layer/tari_mining_helper_ffi/src/error.rs +++ b/base_layer/tari_mining_helper_ffi/src/error.rs @@ -44,11 +44,14 @@ pub enum InterfaceError { InvalidAddress(String), #[error("An invalid network was passed in: `{0}`")] InvalidNetwork(String), + #[error("KeyManager encountered an error: `{0}`")] + KeyManager(String), } /// This struct is meant to hold an error for use by Miningcore. The error has an integer code and string /// message #[derive(Debug, Clone)] +#[allow(dead_code)] pub struct MiningHelperError { pub code: i32, pub message: String, @@ -97,6 +100,10 @@ impl From for MiningHelperError { code: 10, message: format!("{:?}", v), }, + InterfaceError::KeyManager(_) => Self { + code: 11, + message: format!("{:?}", v), + }, } } } diff --git a/base_layer/tari_mining_helper_ffi/src/lib.rs b/base_layer/tari_mining_helper_ffi/src/lib.rs index a3f6c2cc2e..9b2ba3d7dd 100644 --- a/base_layer/tari_mining_helper_ffi/src/lib.rs +++ b/base_layer/tari_mining_helper_ffi/src/lib.rs @@ -43,7 +43,7 @@ use tari_core::{ transactions::{ generate_coinbase, key_manager::create_memory_db_key_manager, - transaction_components::RangeProofType, + transaction_components::{encrypted_data::PaymentId, RangeProofType}, }, }; use tari_crypto::tari_utilities::hex::Hex; @@ -339,7 +339,14 @@ pub unsafe extern "C" fn inject_coinbase( return; }, }; - let key_manager = create_memory_db_key_manager(); + let key_manager = match create_memory_db_key_manager() { + Ok(v) => v, + Err(e) => { + error = MiningHelperError::from(InterfaceError::KeyManager(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return; + }, + }; let consensus_manager = match ConsensusManager::builder(network).build() { Ok(v) => v, @@ -376,6 +383,7 @@ pub unsafe extern "C" fn inject_coinbase( stealth_payment, consensus_manager.consensus_constants(height), range_proof_type, + PaymentId::Empty, ) .await }) { @@ -562,8 +570,6 @@ pub unsafe extern "C" fn share_validate( #[cfg(test)] mod tests { - use libc::c_int; - use tari_common::configuration::Network; use tari_core::{ blocks::{genesis_block::get_genesis_block, Block}, proof_of_work::Difficulty, @@ -571,7 +577,6 @@ mod tests { }; use super::*; - use crate::{inject_nonce, public_key_hex_validate, share_difficulty, share_validate}; fn min_difficulty() -> Difficulty { Difficulty::from_u64(1000).expect("Failed to create difficulty") diff --git a/base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql new file mode 100644 index 0000000000..291a97c5ce --- /dev/null +++ b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql new file mode 100644 index 0000000000..9ce50b940d --- /dev/null +++ b/base_layer/wallet/migrations/2024-05-13-101400_payment_id/up.sql @@ -0,0 +1,7 @@ +-- Any old 'outputs' will not be valid due to the removal of 'coinbase_block_height' and removal of default value for +-- 'spending_priority', so we drop and recreate the table. +ALTER TABLE outputs + ADD payment_id BLOB NULL; + +ALTER TABLE completed_transactions + ADD payment_id BLOB NULL; \ No newline at end of file diff --git a/base_layer/wallet/src/base_node_service/service.rs b/base_layer/wallet/src/base_node_service/service.rs index beb52c877c..d2cdb26cec 100644 --- a/base_layer/wallet/src/base_node_service/service.rs +++ b/base_layer/wallet/src/base_node_service/service.rs @@ -112,9 +112,8 @@ where T: WalletBackend + 'static error!(target: LOG_TARGET, "Error handling request: {:?}", e); e }); - let _result = reply_tx.send(response).map_err(|e| { + let _result = reply_tx.send(response).inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send reply"); - e }); } diff --git a/base_layer/wallet/src/config.rs b/base_layer/wallet/src/config.rs index 3d804cb5f5..64207cbd20 100644 --- a/base_layer/wallet/src/config.rs +++ b/base_layer/wallet/src/config.rs @@ -32,7 +32,7 @@ use tari_common::{ configuration::{serializers, Network, StringList}, SubConfigPath, }; -use tari_common_types::{grpc_authentication::GrpcAuthentication, wallet_types::WalletType}; +use tari_common_types::grpc_authentication::GrpcAuthentication; use tari_comms::multiaddr::Multiaddr; use tari_p2p::P2pConfig; use tari_utilities::SafePassword; @@ -43,8 +43,6 @@ use crate::{ transaction_service::config::TransactionServiceConfig, }; -pub const KEY_MANAGER_COMMS_SECRET_KEY_BRANCH_KEY: &str = "comms"; - fn deserialize_safe_password_option<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de> { let password: Option = Deserialize::deserialize(deserializer)?; @@ -118,8 +116,6 @@ pub struct WalletConfig { pub use_libtor: bool, /// A path to the file that stores the base node identity and secret key pub identity_file: Option, - /// The type of wallet software, or specific type of hardware - pub wallet_type: Option, /// The cool down period between balance enquiry checks in seconds; requests faster than this will be ignored. /// For specialized wallets processing many batch transactions this setting could be increased to 60 s to retain /// responsiveness of the wallet with slightly delayed balance updates @@ -131,7 +127,7 @@ impl Default for WalletConfig { fn default() -> Self { let p2p = P2pConfig { datastore_path: PathBuf::from("peer_db/wallet"), - listener_liveness_check_interval: None, + listener_self_liveness_check_interval: None, ..Default::default() }; Self { @@ -163,7 +159,6 @@ impl Default for WalletConfig { num_required_confirmations: 3, use_libtor: true, identity_file: None, - wallet_type: None, balance_enquiry_cooldown_period: Duration::from_secs(5), } } diff --git a/base_layer/wallet/src/connectivity_service/service.rs b/base_layer/wallet/src/connectivity_service/service.rs index f7e380113f..f880739bfb 100644 --- a/base_layer/wallet/src/connectivity_service/service.rs +++ b/base_layer/wallet/src/connectivity_service/service.rs @@ -27,6 +27,7 @@ use tari_comms::{ connectivity::{ConnectivityError, ConnectivityRequester}, peer_manager::{NodeId, Peer}, protocol::rpc::{RpcClientLease, RpcClientPool}, + Minimized, PeerConnection, }; use tari_core::base_node::{rpc::BaseNodeWalletRpcClient, sync::rpc::BaseNodeSyncRpcClient}; @@ -43,6 +44,7 @@ use crate::{ }; const LOG_TARGET: &str = "wallet::connectivity"; +const CONNECTIVITY_WAIT: u64 = 5; /// Connection status of the Base Node #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -224,7 +226,7 @@ impl WalletConnectivityService { async fn disconnect_base_node(&mut self, node_id: NodeId) { if let Ok(Some(mut connection)) = self.connectivity.get_connection(node_id.clone()).await { - match connection.disconnect().await { + match connection.disconnect(Minimized::No).await { Ok(_) => debug!(target: LOG_TARGET, "Disconnected base node peer {}", node_id), Err(e) => error!(target: LOG_TARGET, "Failed to disconnect base node: {}", e), } @@ -270,7 +272,7 @@ impl WalletConnectivityService { self.config.base_node_monitor_max_refresh_interval.as_secs() ); self.set_online_status(OnlineStatus::Offline); - time::sleep(self.config.base_node_monitor_max_refresh_interval).await; + time::sleep(Duration::from_secs(CONNECTIVITY_WAIT)).await; continue; }, Err(e) => { @@ -278,7 +280,7 @@ impl WalletConnectivityService { if self.current_base_node().as_ref() == Some(&node_id) { self.disconnect_base_node(node_id).await; self.set_online_status(OnlineStatus::Offline); - time::sleep(self.config.base_node_monitor_max_refresh_interval).await; + time::sleep(Duration::from_secs(CONNECTIVITY_WAIT)).await; } continue; }, diff --git a/base_layer/wallet/src/connectivity_service/test.rs b/base_layer/wallet/src/connectivity_service/test.rs index ed4c021835..a0da32e40d 100644 --- a/base_layer/wallet/src/connectivity_service/test.rs +++ b/base_layer/wallet/src/connectivity_service/test.rs @@ -34,6 +34,7 @@ use tari_comms::{ mocks::{create_connectivity_mock, ConnectivityManagerMockState}, node_identity::build_node_identity, }, + Minimized, }; use tari_shutdown::Shutdown; use tari_test_utils::runtime::spawn_until_shutdown; @@ -177,7 +178,7 @@ async fn it_gracefully_handles_connect_fail_reconnect() { mock_state.add_active_connection(conn.clone()).await; // Empty out all the calls let _result = mock_state.take_calls().await; - conn.disconnect().await.unwrap(); + conn.disconnect(Minimized::No).await.unwrap(); let barrier = Arc::new(Barrier::new(2)); let pending_request = task::spawn({ diff --git a/base_layer/wallet/src/lib.rs b/base_layer/wallet/src/lib.rs index c29e951158..0cbc58b4cf 100644 --- a/base_layer/wallet/src/lib.rs +++ b/base_layer/wallet/src/lib.rs @@ -17,7 +17,6 @@ pub mod output_manager_service; pub mod storage; pub mod test_utils; pub mod transaction_service; -pub mod types; pub use tari_common_types::types::WalletHasher; pub mod util; diff --git a/base_layer/wallet/src/output_manager_service/error.rs b/base_layer/wallet/src/output_manager_service/error.rs index c7ae684627..411741340a 100644 --- a/base_layer/wallet/src/output_manager_service/error.rs +++ b/base_layer/wallet/src/output_manager_service/error.rs @@ -150,6 +150,8 @@ pub enum OutputManagerError { RangeProofError(String), #[error("Transaction is over sized: `{0}`")] TooManyInputsToFulfillTransaction(String), + #[error("Std I/O error: {0}")] + StdIoError(#[from] std::io::Error), } impl From for OutputManagerError { diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 56347a9013..50cb8ec246 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -20,9 +20,10 @@ // 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::{fmt, fmt::Formatter, sync::Arc}; +use std::{collections::HashMap, fmt, fmt::Formatter, sync::Arc}; use tari_common_types::{ + tari_address::TariAddress, transaction::TxId, types::{Commitment, FixedHash, HashOutput, PublicKey}, }; @@ -36,7 +37,8 @@ use tari_core::{ SenderTransactionProtocol, }, }; -use tari_script::TariScript; +use tari_crypto::ristretto::pedersen::PedersenCommitment; +use tari_script::{CheckSigSchnorrSignature, TariScript}; use tari_service_framework::reply_channel::SenderService; use tari_utilities::hex::Hex; use tokio::sync::broadcast; @@ -59,6 +61,18 @@ pub enum OutputManagerRequest { UpdateOutputMetadataSignature(Box), GetRecipientTransaction(TransactionSenderMessage), ConfirmPendingTransaction(TxId), + EncumberAggregateUtxo { + tx_id: TxId, + fee_per_gram: MicroMinotari, + output_hash: HashOutput, + expected_commitment: PedersenCommitment, + script_input_shares: HashMap, + script_signature_public_nonces: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + }, PrepareToSendTransaction { tx_id: TxId, amount: MicroMinotari, @@ -141,6 +155,18 @@ impl fmt::Display for OutputManagerRequest { v.metadata_signature.u_y().to_hex(), v.metadata_signature.u_a().to_hex(), ), + EncumberAggregateUtxo { + tx_id, + output_hash, + expected_commitment, + .. + } => write!( + f, + "Encumber aggregate utxo with tx_id: {} and output: ({},{})", + tx_id, + expected_commitment.to_hex(), + output_hash + ), GetRecipientTransaction(_) => write!(f, "GetRecipientTransaction"), ConfirmPendingTransaction(v) => write!(f, "ConfirmPendingTransaction ({})", v), PrepareToSendTransaction { message, .. } => write!(f, "PrepareToSendTransaction ({})", message), @@ -214,6 +240,16 @@ pub enum OutputManagerResponse { ConvertedToTransactionOutput(Box), OutputMetadataSignatureUpdated, RecipientTransactionGenerated(ReceiverTransactionProtocol), + EncumberAggregateUtxo( + ( + Transaction, + MicroMinotari, + MicroMinotari, + PublicKey, + PublicKey, + PublicKey, + ), + ), OutputConfirmed, PendingTransactionConfirmed, PayToSelfTransaction((MicroMinotari, Transaction, HashOutput)), @@ -232,8 +268,13 @@ pub enum OutputManagerResponse { RewoundOutputs(Vec), ScanOutputs(Vec), AddKnownOneSidedPaymentScript, - CreateOutputWithFeatures { output: Box }, - CreatePayToSelfWithOutputs { transaction: Box, tx_id: TxId }, + CreateOutputWithFeatures { + output: Box, + }, + CreatePayToSelfWithOutputs { + transaction: Box, + tx_id: TxId, + }, ReinstatedCancelledInboundTx, ClaimHtlcTransaction((TxId, MicroMinotari, MicroMinotari, Transaction)), OutputInfoByTxId(OutputInfoByTxId), @@ -717,6 +758,65 @@ impl OutputManagerHandle { } } + #[allow(clippy::mutable_key_type)] + pub async fn encumber_aggregate_utxo( + &mut self, + tx_id: TxId, + fee_per_gram: MicroMinotari, + output_hash: HashOutput, + expected_commitment: PedersenCommitment, + script_input_shares: HashMap, + script_signature_public_nonces: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + ) -> Result< + ( + Transaction, + MicroMinotari, + MicroMinotari, + PublicKey, + PublicKey, + PublicKey, + ), + OutputManagerError, + > { + match self + .handle + .call(OutputManagerRequest::EncumberAggregateUtxo { + tx_id, + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + }) + .await?? + { + OutputManagerResponse::EncumberAggregateUtxo(( + transaction, + amount, + fee, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) => Ok(( + transaction, + amount, + fee, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )), + _ => Err(OutputManagerError::UnexpectedApiResponse), + } + } + pub async fn create_pay_to_self_transaction( &mut self, tx_id: TxId, diff --git a/base_layer/wallet/src/output_manager_service/mod.rs b/base_layer/wallet/src/output_manager_service/mod.rs index ef26e0d5a2..c748236356 100644 --- a/base_layer/wallet/src/output_manager_service/mod.rs +++ b/base_layer/wallet/src/output_manager_service/mod.rs @@ -39,7 +39,10 @@ use futures::future; use log::*; use tari_core::{ consensus::NetworkConsensus, - transactions::{key_manager::TransactionKeyManagerInterface, CryptoFactories}, + transactions::{ + key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerInterface}, + CryptoFactories, + }, }; use tari_service_framework::{ async_trait, @@ -59,7 +62,6 @@ use crate::{ service::OutputManagerService, storage::database::{OutputManagerBackend, OutputManagerDatabase}, }, - util::wallet_identity::WalletIdentity, }; /// The maximum number of transaction inputs that can be created in a single transaction, slightly less than the maximum @@ -74,7 +76,6 @@ where T: OutputManagerBackend backend: Option, factories: CryptoFactories, network: NetworkConsensus, - wallet_identity: WalletIdentity, phantom: PhantomData, } @@ -86,14 +87,12 @@ where T: OutputManagerBackend + 'static backend: T, factories: CryptoFactories, network: NetworkConsensus, - wallet_identity: WalletIdentity, ) -> Self { Self { config, backend: Some(backend), factories, network, - wallet_identity, phantom: PhantomData, } } @@ -103,7 +102,7 @@ where T: OutputManagerBackend + 'static impl ServiceInitializer for OutputManagerServiceInitializer where T: OutputManagerBackend + 'static, - TKeyManagerInterface: TransactionKeyManagerInterface, + TKeyManagerInterface: TransactionKeyManagerInterface + SecretTransactionKeyManagerInterface, { async fn initialize(&mut self, context: ServiceInitializerContext) -> Result<(), ServiceInitializationError> { let (sender, receiver) = reply_channel::unbounded(); @@ -120,7 +119,6 @@ where let factories = self.factories.clone(); let config = self.config.clone(); let constants = self.network.create_consensus_constants().pop().unwrap(); - let wallet_identity = self.wallet_identity.clone(); context.spawn_when_ready(move |handles| async move { let base_node_service_handle = handles.expect_handle::(); let connectivity = handles.expect_handle::(); @@ -136,7 +134,6 @@ where handles.get_shutdown_signal(), base_node_service_handle, connectivity, - wallet_identity, key_manager, ) .await diff --git a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs index 949b255d06..5134dd2287 100644 --- a/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs +++ b/base_layer/wallet/src/output_manager_service/recovery/standard_outputs_recoverer.rs @@ -23,12 +23,23 @@ use std::time::Instant; use log::*; -use tari_common_types::{transaction::TxId, types::FixedHash}; +use tari_common_types::{ + transaction::TxId, + types::{FixedHash, PrivateKey}, +}; use tari_core::transactions::{ - key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, + key_manager::{TariKeyId, TransactionKeyManagerBranch, TransactionKeyManagerInterface, TransactionKeyManagerLabel}, tari_amount::MicroMinotari, - transaction_components::{OutputType, TransactionError, TransactionOutput, WalletOutput}, + transaction_components::{ + encrypted_data::PaymentId, + OutputType, + TransactionError, + TransactionOutput, + WalletOutput, + }, }; +use tari_crypto::keys::SecretKey; +use tari_key_manager::key_manager_service::KeyId; use tari_script::{inputs, script, ExecutionStack, Opcode, TariScript}; use tari_utilities::hex::Hex; @@ -80,7 +91,7 @@ where continue; } - let (spending_key, committed_value) = match self.attempt_output_recovery(&output).await? { + let (spending_key, committed_value, payment_id) = match self.attempt_output_recovery(&output).await? { Some(recovered) => recovered, None => continue, }; @@ -108,6 +119,7 @@ where output.encrypted_data, output.minimum_value_promise, output.proof.clone(), + payment_id, ); rewound_outputs.push((uo, known_script_index.is_some(), hash)); @@ -116,7 +128,7 @@ where let rewind_time = start.elapsed(); trace!( target: LOG_TARGET, - "bulletproof rewind profile - rewound {} outputs in {} ms", + "UTXO recovery - checked {} outputs in {} ms", outputs_length, rewind_time.as_millis(), ); @@ -148,8 +160,6 @@ where tx_id, hash: *hash, }); - self.update_outputs_script_private_key_and_update_key_manager_index(output) - .await?; trace!( target: LOG_TARGET, "Output {} with value {} with {} recovered", @@ -192,11 +202,18 @@ where known_scripts: &[KnownOneSidedPaymentScript], ) -> Result, OutputManagerError> { let (input_data, script_key) = if script == &script!(Nop) { - // This is a nop, so we can just create a new key an create the input stack. - let (key, public_key) = self - .master_key_manager - .get_next_key(TransactionKeyManagerBranch::ScriptKey.get_branch_key()) - .await?; + // This is a nop, so we can just create a new key for the input stack. + let key = if let Some(index) = spending_key.managed_index() { + KeyId::Derived { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), + label: TransactionKeyManagerLabel::ScriptKey.get_branch_key(), + index, + } + } else { + let private_key = PrivateKey::random(&mut rand::thread_rng()); + self.master_key_manager.import_key(private_key).await? + }; + let public_key = self.master_key_manager.get_public_key_at_key_id(&key).await?; (inputs!(public_key), key) } else { // This is a known script so lets fill in the details @@ -210,7 +227,7 @@ where if let Some(Opcode::PushPubKey(public_key)) = script.opcode(0) { let result = self .master_key_manager - .find_script_key_id_from_spend_key_id(spending_key, Some(public_key)) + .find_script_key_id_from_commitment_mask_key_id(spending_key, Some(public_key)) .await?; if let Some(script_key_id) = result { (ExecutionStack::default(), script_key_id) @@ -231,7 +248,7 @@ where async fn attempt_output_recovery( &self, output: &TransactionOutput, - ) -> Result, OutputManagerError> { + ) -> Result, OutputManagerError> { // lets first check if the output exists in the db, if it does we dont have to try recovery as we already know // about the output. match self.db.fetch_by_commitment(output.commitment().clone()) { @@ -239,57 +256,14 @@ where Err(OutputManagerStorageError::ValueNotFound) => {}, Err(e) => return Err(e.into()), }; - let (key, committed_value) = match self.master_key_manager.try_output_key_recovery(output, None).await { - Ok(value) => value, - // Key manager errors here are actual errors and should not be suppressed. - Err(TransactionError::KeyManagerError(e)) => return Err(TransactionError::KeyManagerError(e).into()), - Err(_) => return Ok(None), - }; - - Ok(Some((key, committed_value))) - } - - /// Find the key manager index that corresponds to the spending key in the rewound output, if found then modify - /// output to contain correct associated script private key and update the key manager to the highest index it has - /// seen so far. - async fn update_outputs_script_private_key_and_update_key_manager_index( - &mut self, - output: &mut WalletOutput, - ) -> Result<(), OutputManagerError> { - let public_key = self - .master_key_manager - .get_public_key_at_key_id(&output.spending_key_id) - .await?; - let script_key = { - let found_index = self - .master_key_manager - .find_key_index( - TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), - &public_key, - ) - .await?; - - self.master_key_manager - .update_current_key_index_if_higher( - TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), - found_index, - ) - .await?; - self.master_key_manager - .update_current_key_index_if_higher( - TransactionKeyManagerBranch::ScriptKey.get_branch_key(), - found_index, - ) - .await?; + let (key, committed_value, payment_id) = + match self.master_key_manager.try_output_key_recovery(output, None).await { + Ok(value) => value, + // Key manager errors here are actual errors and should not be suppressed. + Err(TransactionError::KeyManagerError(e)) => return Err(TransactionError::KeyManagerError(e).into()), + Err(_) => return Ok(None), + }; - TariKeyId::Managed { - branch: TransactionKeyManagerBranch::ScriptKey.get_branch_key(), - index: found_index, - } - }; - let public_script_key = self.master_key_manager.get_public_key_at_key_id(&script_key).await?; - output.input_data = inputs!(public_script_key); - output.script_key_id = script_key; - Ok(()) + Ok(Some((key, committed_value, payment_id))) } } diff --git a/base_layer/wallet/src/output_manager_service/resources.rs b/base_layer/wallet/src/output_manager_service/resources.rs index 104588f742..8f55251e9e 100644 --- a/base_layer/wallet/src/output_manager_service/resources.rs +++ b/base_layer/wallet/src/output_manager_service/resources.rs @@ -23,13 +23,10 @@ use tari_core::{consensus::ConsensusConstants, transactions::CryptoFactories}; use tari_shutdown::ShutdownSignal; -use crate::{ - output_manager_service::{ - config::OutputManagerServiceConfig, - handle::OutputManagerEventSender, - storage::database::OutputManagerDatabase, - }, - util::wallet_identity::WalletIdentity, +use crate::output_manager_service::{ + config::OutputManagerServiceConfig, + handle::OutputManagerEventSender, + storage::database::OutputManagerDatabase, }; /// This struct is a collection of the common resources that a async task in the service requires. @@ -43,5 +40,4 @@ pub(crate) struct OutputManagerResources Result { let resources = OutputManagerResources { @@ -140,7 +155,6 @@ where key_manager, consensus_constants, shutdown_signal, - wallet_identity, }; Ok(Self { @@ -153,13 +167,6 @@ where } pub async fn start(mut self) -> Result<(), OutputManagerError> { - // we need to ensure the wallet identity secret key is stored in the key manager - let _key_id = self - .resources - .key_manager - .import_key(self.resources.wallet_identity.node_identity.secret_key().clone()) - .await?; - let request_stream = self .request_stream .take() @@ -189,9 +196,8 @@ where warn!(target: LOG_TARGET, "Error handling request: {:?}", e); e }); - let _result = reply_tx.send(response).map_err(|e| { + let _result = reply_tx.send(response).inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send reply"); - e }); }, _ = shutdown.wait() => { @@ -220,6 +226,36 @@ where .add_output(Some(tx_id), *uo, spend_priority) .await .map(|_| OutputManagerResponse::OutputAdded), + OutputManagerRequest::EncumberAggregateUtxo { + tx_id, + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + } => self + .encumber_aggregate_utxo( + tx_id, + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + PaymentId::Empty, + 0, + RangeProofType::BulletProofPlus, + 0.into(), + ) + .await + .map(OutputManagerResponse::EncumberAggregateUtxo), OutputManagerRequest::AddUnvalidatedOutput((tx_id, uo, spend_priority)) => self .add_unvalidated_output(tx_id, *uo, spend_priority) .await @@ -443,7 +479,7 @@ where fee_per_gram: MicroMinotari, ) -> Result { let output = self - .fetch_outputs_from_node(vec![output_hash]) + .fetch_unspent_outputs_from_node(vec![output_hash]) .await? .pop() .ok_or_else(|| OutputManagerError::ServiceError("Output not found".to_string()))?; @@ -618,7 +654,7 @@ where ) -> Result<(), OutputManagerError> { debug!( target: LOG_TARGET, - "Add unvalidated output of value {} to Output Manager", output.value + "Add unvalidated output of value {} to Output Manager with TxId {}", output.value, tx_id ); let output = DbWalletOutput::from_wallet_output( output, @@ -629,6 +665,7 @@ where None, ) .await?; + trace!(target: LOG_TARGET, "TxId: {}, {:?}", tx_id, output); self.resources.db.add_unvalidated_output(tx_id, output)?; // Because we added new outputs, let try to trigger a validation for them @@ -647,16 +684,19 @@ where value: MicroMinotari, features: OutputFeatures, ) -> Result { - let (spending_key_id, _spending_key_id, script_key_id, _script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (commitment_mask_key, script_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; let input_data = ExecutionStack::default(); let script = TariScript::default(); - Ok(WalletOutputBuilder::new(value, spending_key_id) + Ok(WalletOutputBuilder::new(value, commitment_mask_key.key_id) .with_features(features) .with_script(script) .with_input_data(input_data) - .with_script_key(script_key_id)) + .with_script_key(script_key.key_id)) } fn get_balance(&self, current_tip_for_time_lock_calculation: Option) -> Result { @@ -695,15 +735,18 @@ where return Err(OutputManagerError::InvalidKernelFeatures); } - let (spending_key_id, _, script_key_id, script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (spending_key, script_public_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; // Confirm script hash is for the expected script, at the moment assuming Nop or Push_pubkey // if the script is Push_pubkey(default_key) we know we have to fill it in. let script = if single_round_sender_data.script == script!(Nop) { single_round_sender_data.script.clone() } else if single_round_sender_data.script == script!(PushPubKey(Box::default())) { - script!(PushPubKey(Box::new(script_public_key.clone()))) + script!(PushPubKey(Box::new(script_public_key.pub_key.clone()))) } else { return Err(OutputManagerError::InvalidScriptHash); }; @@ -711,7 +754,12 @@ where let encrypted_data = self .resources .key_manager - .encrypt_data_for_recovery(&spending_key_id, None, single_round_sender_data.amount.as_u64()) + .encrypt_data_for_recovery( + &spending_key.key_id, + None, + single_round_sender_data.amount.as_u64(), + PaymentId::Empty, + ) .await .unwrap(); let minimum_value_promise = single_round_sender_data.minimum_value_promise; @@ -728,7 +776,7 @@ where .resources .key_manager .get_receiver_partial_metadata_signature( - &spending_key_id, + &spending_key.key_id, &single_round_sender_data.amount.into(), &single_round_sender_data.sender_offset_public_key, &single_round_sender_data.ephemeral_public_nonce, @@ -740,11 +788,11 @@ where let key_kanager_output = WalletOutput::new_current_version( single_round_sender_data.amount, - spending_key_id.clone(), + spending_key.key_id.clone(), single_round_sender_data.features.clone(), script, ExecutionStack::default(), - script_key_id, + script_public_key.key_id, single_round_sender_data.sender_offset_public_key.clone(), // Note: The signature at this time is only partially built metadata_signature, @@ -752,6 +800,7 @@ where single_round_sender_data.covenant.clone(), encrypted_data, minimum_value_promise, + PaymentId::Empty, &self.resources.key_manager, ) .await?; @@ -938,13 +987,16 @@ where input_selection.num_selected() ); - let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (change_commitment_mask_key, change_script_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key.clone()))), + script!(PushPubKey(Box::new(change_script_key.pub_key.clone()))), ExecutionStack::default(), - change_script_key_id, - change_spending_key_id, + change_script_key.key_id, + change_commitment_mask_key.key_id, Covenant::default(), ); @@ -1042,31 +1094,34 @@ where } if input_selection.requires_change_output() { - let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (change_commitment_mask_key, change_script_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key))), + script!(PushPubKey(Box::new(change_script_key.pub_key))), ExecutionStack::default(), - change_script_key_id, - change_spending_key_id, + change_script_key.key_id, + change_commitment_mask_key.key_id, Covenant::default(), ); } let mut db_outputs = vec![]; for mut wallet_output in outputs { - let (sender_offset_key_id, _) = self + let sender_offset_key = self .resources .key_manager - .get_next_key(&TransactionKeyManagerBranch::SenderOffset.get_branch_key()) + .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await?; wallet_output = wallet_output - .sign_as_sender_and_receiver(&self.resources.key_manager, &sender_offset_key_id) + .sign_as_sender_and_receiver(&self.resources.key_manager, &sender_offset_key.key_id) .await?; let ub = wallet_output.try_build(&self.resources.key_manager).await?; builder - .with_output(ub.clone(), sender_offset_key_id.clone()) + .with_output(ub.clone(), sender_offset_key.key_id.clone()) .await .map_err(|e| OutputManagerError::BuildError(e.to_string()))?; db_outputs.push( @@ -1109,6 +1164,331 @@ where Ok((tx_id, stp.into_transaction()?)) } + /// Create a partial transaction in order to prepare output + #[allow(clippy::too_many_lines)] + #[allow(clippy::mutable_key_type)] + pub async fn encumber_aggregate_utxo( + &mut self, + tx_id: TxId, + fee_per_gram: MicroMinotari, + output_hash: HashOutput, + expected_commitment: PedersenCommitment, + mut script_input_shares: HashMap, + script_signature_public_nonces: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + payment_id: PaymentId, + maturity: u64, + range_proof_type: RangeProofType, + minimum_value_promise: MicroMinotari, + ) -> Result< + ( + Transaction, + MicroMinotari, + MicroMinotari, + PublicKey, + PublicKey, + PublicKey, + ), + OutputManagerError, + > { + // Fetch the output from the blockchain + let output = self + .fetch_unspent_outputs_from_node(vec![output_hash]) + .await? + .pop() + .ok_or_else(|| { + OutputManagerError::ServiceError(format!( + "Output with hash {} not found in blockchain (TxId: {})", + output_hash, tx_id + )) + })?; + if output.commitment != expected_commitment { + return Err(OutputManagerError::ServiceError(format!( + "Output commitment does not match expected commitment (TxId: {})", + tx_id + ))); + } + // Retrieve the list of n public keys from the script + let public_keys = + if let [Opcode::CheckMultiSigVerifyAggregatePubKey(_n, _m, keys, _msg)] = output.script.as_slice() { + keys.clone() + } else { + return Err(OutputManagerError::ServiceError(format!( + "Invalid script (TxId: {})", + tx_id + ))); + }; + // Create a deterministic encryption key from the sum of the public keys + let sum_public_keys = public_keys + .iter() + .fold(tari_common_types::types::PublicKey::default(), |acc, x| acc + x); + let encryption_private_key = public_key_to_output_encryption_key(&sum_public_keys)?; + let mut aggregated_script_public_key_shares = PublicKey::default(); + // Decrypt the output secrets and create a new input as WalletOutput (unblinded) + let input = if let Ok((amount, spending_key, payment_id)) = + EncryptedData::decrypt_data(&encryption_private_key, &output.commitment, &output.encrypted_data) + { + if output.verify_mask(&self.resources.factories.range_proof, &spending_key, amount.as_u64())? { + let mut script_signatures = Vec::new(); + // lets add our own signature to the list + let self_signature = self + .resources + .key_manager + .sign_script_message( + &self.resources.key_manager.get_spend_key().await?.key_id, + output.commitment.as_bytes(), + ) + .await?; + script_input_shares.insert( + self.resources.key_manager.get_spend_key().await?.pub_key, + self_signature, + ); + + // the order here is important, we need to add the signatures in the same order as public keys where + // added to the script originally + for key in public_keys { + if let Some(signature) = script_input_shares.get(&key) { + script_signatures.push(StackItem::Signature(signature.clone())); + // our own key should not be added yet, it will be added with the script signing + if key != self.resources.key_manager.get_spend_key().await?.pub_key { + aggregated_script_public_key_shares = aggregated_script_public_key_shares + key; + } + } + } + let spending_key_id = self.resources.key_manager.import_key(spending_key).await?; + WalletOutput::new_with_rangeproof( + output.version, + amount, + spending_key_id, + output.features, + output.script, + ExecutionStack::new(script_signatures), + self.resources.key_manager.get_spend_key().await?.key_id, // Only of the master wallet + output.sender_offset_public_key, + output.metadata_signature, + 0, + output.covenant, + output.encrypted_data, + output.minimum_value_promise, + output.proof, + payment_id, + ) + } else { + return Err(OutputManagerError::ServiceError(format!( + "Could not verify mask (TxId: {})", + tx_id + ))); + } + } else { + return Err(OutputManagerError::ServiceError(format!( + "Could not decrypt output (TxId: {})", + tx_id + ))); + }; + + // The entire input will be spent to a single recipient with no change + let output_features = OutputFeatures { + maturity, + range_proof_type, + ..Default::default() + }; + let script = script!(PushPubKey(Box::new(recipient_address.public_spend_key().clone()))); + let metadata_byte_size = self + .resources + .consensus_constants + .transaction_weight_params() + .round_up_features_and_scripts_size( + output_features.get_serialized_size()? + + script.get_serialized_size()? + + Covenant::default().get_serialized_size()?, + ); + let fee = self.get_fee_calc(); + let fee = fee.calculate(fee_per_gram, 1, 1, 1, metadata_byte_size); + let amount = input.value - fee; + + // Create sender transaction protocol builder with recipient data and no change + let mut builder = SenderTransactionProtocol::builder( + self.resources.consensus_constants.clone(), + self.resources.key_manager.clone(), + ); + builder + .with_lock_height(0) + .with_fee_per_gram(fee_per_gram) + .with_kernel_features(KernelFeatures::empty()) + .with_prevent_fee_gt_amount(self.resources.config.prevent_fee_gt_amount) + .with_input(input.clone()) + .await? + .with_recipient_data( + push_pubkey_script(recipient_address.public_spend_key()), + output_features, + Covenant::default(), + minimum_value_promise, + amount, + ) + .await? + .with_change_data( + script!(PushPubKey(Box::default())), + ExecutionStack::default(), + TariKeyId::default(), + TariKeyId::default(), + Covenant::default(), + ); + let mut stp = builder + .build() + .await + .map_err(|e| OutputManagerError::BuildError(e.message))?; + + // This call is needed to advance the state from `SingleRoundMessageReady` to `SingleRoundMessageReady`, + // but the returned value is not used + let _single_round_sender_data = stp.build_single_round_message(&self.resources.key_manager).await?; + + self.confirm_encumberance(tx_id)?; + + // Prepare receiver part of the transaction + + // Diffie-Hellman shared secret `k_Ob * K_Sb = K_Ob * k_Sb` results in a public key, which is fed into + // KDFs to produce the spending and encryption keys. All player's shares are added together to produce the + // shared secret. + let sender_offset_private_key_id_self = + stp.get_recipient_sender_offset_private_key()? + .ok_or(OutputManagerError::ServiceError(format!( + "Missing sender offset private key ID (TxId: {})", + tx_id + )))?; + + let shared_secret = { + let mut key_sum = PublicKey::default(); + for key in &dh_shared_secret_shares { + key_sum = key_sum + key; + } + let shared_secret_self = self + .resources + .key_manager + .get_diffie_hellman_shared_secret( + &sender_offset_private_key_id_self, + recipient_address + .public_view_key() + .ok_or(OutputManagerError::ServiceError(format!( + "Missing public view key (TxId: {})", + tx_id + )))?, + ) + .await?; + key_sum = key_sum + &PublicKey::from_vec(&shared_secret_self.as_bytes().to_vec())?; + CommsDHKE::from_canonical_bytes(key_sum.as_bytes())? + }; + + let spending_key = shared_secret_to_output_spending_key(&shared_secret)?; + let spending_key_id = self.resources.key_manager.import_key(spending_key).await?; + + let encryption_private_key = shared_secret_to_output_encryption_key(&shared_secret)?; + let encryption_key_id = self.resources.key_manager.import_key(encryption_private_key).await?; + + let sender_offset_public_key_self = self + .resources + .key_manager + .get_public_key_at_key_id(&sender_offset_private_key_id_self) + .await?; + let aggregated_sender_offset_public_key_shares = sender_offset_public_key_shares + .iter() + .fold(PublicKey::default(), |acc, x| acc + x); + let sender_offset_public_key = &aggregated_sender_offset_public_key_shares + sender_offset_public_key_self; + + let sender_message = TransactionSenderMessage::new_single_round_message( + stp.get_single_round_message(&self.resources.key_manager) + .await + .map_err(|e| service_error_with_id(tx_id, e.to_string(), true))?, + ); + + let aggregated_metadata_ephemeral_public_key_shares = metadata_ephemeral_public_key_shares + .iter() + .fold(PublicKey::default(), |acc, x| acc + x); + // Create the output with a partially signed metadata signature + let output = WalletOutputBuilder::new(amount, spending_key_id) + .with_features( + sender_message + .single() + .ok_or( + OutputManagerError::InvalidSenderMessage)? + .features + .clone(), + ) + .with_script(script) + .encrypt_data_for_recovery( + &self.resources.key_manager, + Some(&encryption_key_id), + payment_id.clone(), + ) + .await? + .with_input_data(ExecutionStack::default()) // Just a placeholder in the wallet + .with_sender_offset_public_key(sender_offset_public_key) + .with_script_key(self.resources.key_manager.get_spend_key().await?.key_id) + .with_minimum_value_promise(minimum_value_promise) + .sign_partial_as_sender_and_receiver( + &self.resources.key_manager, + &sender_offset_private_key_id_self, + &aggregated_sender_offset_public_key_shares, + &aggregated_metadata_ephemeral_public_key_shares, + ) + .await + .map_err(|e|service_error_with_id(tx_id, e.to_string(), true))? + .try_build(&self.resources.key_manager) + .await + .map_err(|e|service_error_with_id(tx_id, e.to_string(), true))?; + let total_metadata_ephemeral_public_key = + aggregated_metadata_ephemeral_public_key_shares + output.metadata_signature.ephemeral_pubkey(); + + // Finalize the partial transaction - it will not be valid at this stage as the metadata and script + // signatures are not yet complete. + let rtp = ReceiverTransactionProtocol::new( + sender_message, + output, + &self.resources.key_manager, + &self.resources.consensus_constants.clone(), + ) + .await; + let recipient_reply = rtp.get_signed_data()?.clone(); + stp.add_presigned_recipient_info(recipient_reply)?; + stp.finalize(&self.resources.key_manager) + .await + .map_err(|e| service_error_with_id(tx_id, e.to_string(), true))?; + info!(target: LOG_TARGET, "Finalized partial one-side transaction TxId: {}", tx_id); + + let aggregated_script_signature_public_nonces = script_signature_public_nonces + .iter() + .fold(PublicKey::default(), |acc, x| acc + x); + + // Update the input's script signature + let (updated_input, total_script_public_key) = input + .to_transaction_input_with_multi_party_script_signature( + &aggregated_script_signature_public_nonces, + &aggregated_script_public_key_shares, + &self.resources.key_manager, + ) + .await?; + + let total_script_nonce = + aggregated_script_signature_public_nonces + updated_input.script_signature.ephemeral_pubkey(); + let mut tx = stp.get_transaction()?.clone(); + let mut tx_body = tx.body; + tx_body.update_script_signature(updated_input.commitment()?, updated_input.script_signature.clone())?; + tx.body = tx_body; + + let fee = stp.get_fee_amount()?; + + Ok(( + tx, + amount, + fee, + total_script_public_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) + } + async fn create_pay_to_self_transaction( &mut self, tx_id: TxId, @@ -1172,13 +1552,16 @@ where let mut outputs = vec![output]; - let (change_spending_key_id, _spend_public_key, change_script_key_id, change_script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (change_commitment_mask_key_id, change_script_public_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key.clone()))), + script!(PushPubKey(Box::new(change_script_public_key.pub_key.clone()))), ExecutionStack::default(), - change_script_key_id.clone(), - change_spending_key_id, + change_script_public_key.key_id.clone(), + change_commitment_mask_key_id.key_id, Covenant::default(), ); @@ -1294,7 +1677,7 @@ where let uo_len = uo.len(); trace!( target: LOG_TARGET, - "select_utxos profile - fetch_unspent_outputs_for_spending: {} outputs, {} ms (at {})", + "select_utxos profile - fetch_unspent_outputs_for_spending: {} outputs, {} ms (at {} ms)", uo_len, start_new.elapsed().as_millis(), start.elapsed().as_millis(), @@ -1363,7 +1746,7 @@ where let enough_spendable = utxos_total_value > amount + fee_with_change; trace!( target: LOG_TARGET, - "select_utxos profile - final_selection: {} outputs from {}, {} ms (at {})", + "select_utxos profile - final_selection: {} outputs from {}, {} ms (at {} ms)", utxos.len(), uo_len, start_new.elapsed().as_millis(), @@ -1803,13 +2186,16 @@ where // extending transaction if there is some `change` left over if has_leftover_change { - let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (change_mask, change_script) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; tx_builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key))), + script!(PushPubKey(Box::new(change_script.pub_key))), ExecutionStack::default(), - change_script_key_id, - change_spending_key_id, + change_script.key_id, + change_mask.key_id, Covenant::default(), ); } @@ -1882,14 +2268,17 @@ where amount: MicroMinotari, covenant: Covenant, ) -> Result<(DbWalletOutput, TariKeyId), OutputManagerError> { - let (spending_key_id, _, script_key_id, script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; - let script = script!(PushPubKey(Box::new(script_public_key.clone()))); + let (commitment_mask_key, script_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; + let script = script!(PushPubKey(Box::new(script_key.pub_key.clone()))); let encrypted_data = self .resources .key_manager - .encrypt_data_for_recovery(&spending_key_id, None, amount.as_u64()) + .encrypt_data_for_recovery(&commitment_mask_key.key_id, None, amount.as_u64(), PaymentId::Empty) .await?; let minimum_value_promise = MicroMinotari::zero(); let metadata_message = TransactionOutput::metadata_signature_message_from_parts( @@ -1900,18 +2289,18 @@ where &encrypted_data, &minimum_value_promise, ); - let (sender_offset_key_id, sender_offset_public_key) = self + let sender_offset = self .resources .key_manager - .get_next_key(&TransactionKeyManagerBranch::SenderOffset.get_branch_key()) + .get_next_key(TransactionKeyManagerBranch::SenderOffset.get_branch_key()) .await?; let metadata_signature = self .resources .key_manager .get_metadata_signature( - &spending_key_id, + &commitment_mask_key.key_id, &PrivateKey::from(amount), - &sender_offset_key_id, + &sender_offset.key_id, &TransactionOutputVersion::get_current_version(), &metadata_message, output_features.range_proof_type, @@ -1921,17 +2310,18 @@ where let output = DbWalletOutput::from_wallet_output( WalletOutput::new_current_version( amount, - spending_key_id, + commitment_mask_key.key_id, output_features, script, ExecutionStack::default(), - script_key_id, - sender_offset_public_key, + script_key.key_id, + sender_offset.pub_key, metadata_signature, 0, covenant, encrypted_data, minimum_value_promise, + PaymentId::Empty, &self.resources.key_manager, ) .await?, @@ -1943,7 +2333,7 @@ where ) .await?; - Ok((output, sender_offset_key_id)) + Ok((output, sender_offset.key_id)) } #[allow(clippy::too_many_lines)] @@ -2048,7 +2438,7 @@ where Ok((tx_id, stp.into_transaction()?, accumulated_amount + fee)) } - async fn fetch_outputs_from_node( + async fn fetch_unspent_outputs_from_node( &mut self, hashes: Vec, ) -> Result, OutputManagerError> { @@ -2087,24 +2477,24 @@ where .resources .key_manager .get_diffie_hellman_shared_secret( - &self.resources.wallet_identity.wallet_node_key_id, + &self.resources.key_manager.get_view_key().await?.key_id, &output.sender_offset_public_key, ) .await?; let encryption_key = shared_secret_to_output_encryption_key(&shared_secret)?; - if let Ok((amount, spending_key)) = + if let Ok((amount, spending_key, payment_id)) = EncryptedData::decrypt_data(&encryption_key, &output.commitment, &output.encrypted_data) { if output.verify_mask(&self.resources.factories.range_proof, &spending_key, amount.as_u64())? { let spending_key_id = self.resources.key_manager.import_key(spending_key).await?; - let rewound_output = WalletOutput::new( + let rewound_output = WalletOutput::new_with_rangeproof( output.version, amount, spending_key_id, output.features, output.script, inputs!(pre_image), - self.resources.wallet_identity.wallet_node_key_id.clone(), + self.resources.key_manager.get_spend_key().await?.key_id, output.sender_offset_public_key, output.metadata_signature, // Although the technically the script does have a script lock higher than 0, this does not apply @@ -2113,9 +2503,9 @@ where output.covenant, output.encrypted_data, output.minimum_value_promise, - &self.resources.key_manager, - ) - .await?; + output.proof, + payment_id, + ); let message = "SHA-XTR atomic swap".to_string(); @@ -2135,13 +2525,16 @@ where let mut outputs = Vec::new(); - let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (change_commitment_mask_key, change_script_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key.clone()))), + script!(PushPubKey(Box::new(change_script_key.pub_key.clone()))), ExecutionStack::default(), - change_script_key_id, - change_spending_key_id, + change_script_key.key_id, + change_commitment_mask_key.key_id, Covenant::default(), ); @@ -2216,13 +2609,16 @@ where let mut outputs = Vec::new(); - let (change_spending_key_id, _, change_script_key_id, change_script_public_key) = - self.resources.key_manager.get_next_spend_and_script_key_ids().await?; + let (change_commitment_mask_key, change_script_key) = self + .resources + .key_manager + .get_next_commitment_mask_and_script_key() + .await?; builder.with_change_data( - script!(PushPubKey(Box::new(change_script_public_key.clone()))), + script!(PushPubKey(Box::new(change_script_key.pub_key.clone()))), ExecutionStack::default(), - change_script_key_id, - change_spending_key_id, + change_script_key.key_id, + change_commitment_mask_key.key_id, Covenant::default(), ); @@ -2299,49 +2695,35 @@ where )); } - let wallet_sk = self.resources.wallet_identity.wallet_node_key_id.clone(); - let wallet_pk = self.resources.key_manager.get_public_key_at_key_id(&wallet_sk).await?; + let spend_key = self.resources.key_manager.get_spend_key().await?; + let view_key = self.resources.key_manager.get_view_key().await?; let mut scanned_outputs = vec![]; for output in outputs { - match output.script.as_slice() { - // ---------------------------------------------------------------------------- - // simple one-sided address - [Opcode::PushPubKey(scanned_pk)] => { - match known_keys.iter().find(|x| &x.0 == scanned_pk.as_ref()) { - // none of the keys match, skipping - None => continue, - - // match found - Some(matched_key) => { - let shared_secret = self - .resources - .key_manager - .get_diffie_hellman_shared_secret(&matched_key.1, &output.sender_offset_public_key) - .await?; - scanned_outputs.push(( - output.clone(), - OutputSource::OneSided, - matched_key.1.clone(), - shared_secret, - )); - }, - } - }, - - // ---------------------------------------------------------------------------- - // one-sided stealth address - // NOTE: Extracting the nonce R and a spending (public aka scan_key) key from the script - // NOTE: [RFC 203 on Stealth Addresses](https://rfc.tari.com/RFC-0203_StealthAddresses.html) - [Opcode::PushPubKey(nonce), Opcode::Drop, Opcode::PushPubKey(scanned_pk)] => { - // matching spending (public) keys + if let [Opcode::PushPubKey(scanned_pk)] = output.script.as_slice() { + if let Some(matched_key) = known_keys.iter().find(|x| &x.0 == scanned_pk.as_ref()) { + let shared_secret = self + .resources + .key_manager + .get_diffie_hellman_shared_secret(&view_key.key_id, &output.sender_offset_public_key) + .await?; + scanned_outputs.push(( + output.clone(), + OutputSource::OneSided, + matched_key.1.clone(), + shared_secret, + )); + } + // it is not some known key, so lets try and see if this is a stealth tx for us + else { let stealth_address_hasher = self .resources .key_manager - .get_diffie_hellman_stealth_domain_hasher(&wallet_sk, nonce.as_ref()) + .get_diffie_hellman_stealth_domain_hasher(&view_key.key_id, &output.sender_offset_public_key) .await?; - let script_spending_key = stealth_address_script_spending_key(&stealth_address_hasher, &wallet_pk); + let script_spending_key = + stealth_address_script_spending_key(&stealth_address_hasher, &spend_key.pub_key); if &script_spending_key != scanned_pk.as_ref() { continue; } @@ -2352,13 +2734,13 @@ where let stealth_key = self .resources .key_manager - .import_add_offset_to_private_key(&wallet_sk, stealth_address_offset) + .import_add_offset_to_private_key(&spend_key.key_id, stealth_address_offset) .await?; let shared_secret = self .resources .key_manager - .get_diffie_hellman_shared_secret(&wallet_sk, &output.sender_offset_public_key) + .get_diffie_hellman_shared_secret(&view_key.key_id, &output.sender_offset_public_key) .await?; scanned_outputs.push(( output.clone(), @@ -2366,9 +2748,7 @@ where stealth_key, shared_secret, )); - }, - - _ => {}, + } } } @@ -2384,7 +2764,7 @@ where for (output, output_source, script_private_key, shared_secret) in scanned_outputs { let encryption_key = shared_secret_to_output_encryption_key(&shared_secret)?; - if let Ok((committed_value, spending_key)) = + if let Ok((committed_value, spending_key, payment_id)) = EncryptedData::decrypt_data(&encryption_key, &output.commitment, &output.encrypted_data) { if output.verify_mask( @@ -2409,6 +2789,7 @@ where output.encrypted_data, output.minimum_value_promise, output.proof, + payment_id, ); let tx_id = TxId::new_random(); @@ -2460,6 +2841,14 @@ where } } +fn service_error_with_id(tx_id: TxId, err: String, log_error: bool) -> OutputManagerError { + let err_str = format!("TxId: {} ({})", tx_id, err); + if log_error { + error!(target: LOG_TARGET, "{}", err_str); + } + OutputManagerError::ServiceError(err_str) +} + /// This struct holds the detailed balance of the Output Manager Service. #[derive(Debug, Clone, PartialEq)] pub struct Balance { diff --git a/base_layer/wallet/src/output_manager_service/storage/models.rs b/base_layer/wallet/src/output_manager_service/storage/models.rs index 0e8b4202e4..45a144357d 100644 --- a/base_layer/wallet/src/output_manager_service/storage/models.rs +++ b/base_layer/wallet/src/output_manager_service/storage/models.rs @@ -30,7 +30,7 @@ use tari_common_types::{ }; use tari_core::transactions::{ key_manager::{TariKeyId, TransactionKeyManagerInterface}, - transaction_components::WalletOutput, + transaction_components::{encrypted_data::PaymentId, WalletOutput}, }; use tari_script::{ExecutionStack, TariScript}; @@ -56,6 +56,7 @@ pub struct DbWalletOutput { pub source: OutputSource, pub received_in_tx_id: Option, pub spent_in_tx_id: Option, + pub payment_id: PaymentId, } impl DbWalletOutput { @@ -68,6 +69,7 @@ impl DbWalletOutput { spent_in_tx_id: Option, ) -> Result { let tx_output = output.to_transaction_output(key_manager).await?; + let payment_id = output.payment_id.clone(); Ok(DbWalletOutput { hash: tx_output.hash(), commitment: tx_output.commitment, @@ -82,6 +84,7 @@ impl DbWalletOutput { source, received_in_tx_id, spent_in_tx_id, + payment_id, }) } } diff --git a/base_layer/wallet/src/output_manager_service/storage/output_source.rs b/base_layer/wallet/src/output_manager_service/storage/output_source.rs index d64b7f8d26..19d178f0df 100644 --- a/base_layer/wallet/src/output_manager_service/storage/output_source.rs +++ b/base_layer/wallet/src/output_manager_service/storage/output_source.rs @@ -19,13 +19,7 @@ // 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 core::{ - convert::TryFrom, - result::{ - Result, - Result::{Err, Ok}, - }, -}; +use core::convert::TryFrom; use strum_macros::Display; diff --git a/base_layer/wallet/src/output_manager_service/storage/output_status.rs b/base_layer/wallet/src/output_manager_service/storage/output_status.rs index 495cc72e37..76e4ecf1ab 100644 --- a/base_layer/wallet/src/output_manager_service/storage/output_status.rs +++ b/base_layer/wallet/src/output_manager_service/storage/output_status.rs @@ -19,13 +19,7 @@ // 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 core::{ - convert::TryFrom, - result::{ - Result, - Result::{Err, Ok}, - }, -}; +use core::convert::TryFrom; use strum_macros::Display; 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 8e0018044d..0cf8498398 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 @@ -29,7 +29,6 @@ use diesel::{ prelude::*, r2d2::{ConnectionManager, PooledConnection}, result::Error as DieselError, - SqliteConnection, }; use log::*; pub use new_output_sql::NewOutputSql; @@ -61,6 +60,7 @@ use crate::{ schema::{known_one_sided_payment_scripts, outputs}, storage::sqlite_utilities::wallet_db_connection::WalletDbConnection, }; + mod new_output_sql; mod output_sql; const LOG_TARGET: &str = "wallet::output_manager_service::database::wallet"; @@ -1131,7 +1131,7 @@ impl OutputManagerBackend for OutputManagerSqliteDatabase { if OutputSql::find_by_commitment_and_cancelled(&output.commitment.to_vec(), false, &mut conn).is_ok() { return Err(OutputManagerStorageError::DuplicateOutput); } - let new_output = NewOutputSql::new(output, Some(OutputStatus::EncumberedToBeReceived), Some(tx_id))?; + let new_output = NewOutputSql::new(output, Some(OutputStatus::UnspentMinedUnconfirmed), Some(tx_id))?; new_output.commit(&mut conn)?; if start.elapsed().as_millis() > 0 { @@ -1498,7 +1498,7 @@ mod test { let mut outputs_spent = Vec::new(); let mut outputs_unspent = Vec::new(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); for _i in 0..2 { let (_, uo) = make_input(MicroMinotari::from(100 + OsRng.next_u64() % 1000), &key_manager).await; let uo = DbWalletOutput::from_wallet_output(uo, &key_manager, None, OutputSource::Standard, None, None) diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs index 634ec87098..8407219629 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/new_output_sql.rs @@ -22,7 +22,7 @@ use borsh::BorshSerialize; // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH // DAMAGE. use derivative::Derivative; -use diesel::{prelude::*, SqliteConnection}; +use diesel::prelude::*; use tari_common_types::transaction::TxId; use tari_utilities::ByteArray; @@ -80,7 +80,7 @@ impl NewOutputSql { let output = Self { commitment: output.commitment.to_vec(), spending_key: output.wallet_output.spending_key_id.to_string(), - rangeproof: output.wallet_output.rangeproof.map(|proof| proof.to_vec()), + rangeproof: output.wallet_output.range_proof.map(|proof| proof.to_vec()), value: output.wallet_output.value.as_u64() as i64, output_type: i32::from(output.wallet_output.features.output_type.as_byte()), maturity: output.wallet_output.features.maturity as i64, diff --git a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs index 83d7d2478c..0fff2dc300 100644 --- a/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs +++ b/base_layer/wallet/src/output_manager_service/storage/sqlite_db/output_sql.rs @@ -28,7 +28,7 @@ use std::{ use borsh::BorshDeserialize; use chrono::NaiveDateTime; use derivative::Derivative; -use diesel::{prelude::*, sql_query, SqliteConnection}; +use diesel::{prelude::*, sql_query}; use log::*; use tari_common_sqlite::util::diesel_ext::ExpectedRowsExtension; use tari_common_types::{ @@ -37,7 +37,14 @@ use tari_common_types::{ }; use tari_core::transactions::{ tari_amount::MicroMinotari, - transaction_components::{EncryptedData, OutputFeatures, OutputType, TransactionOutputVersion, WalletOutput}, + transaction_components::{ + encrypted_data::PaymentId, + EncryptedData, + OutputFeatures, + OutputType, + TransactionOutputVersion, + WalletOutput, + }, }; use tari_crypto::tari_utilities::ByteArray; use tari_key_manager::key_manager_service::KeyId; @@ -102,6 +109,7 @@ pub struct OutputSql { pub minimum_value_promise: i64, pub source: i32, pub last_validation_timestamp: Option, + pub payment_id: Option>, } impl OutputSql { @@ -667,6 +675,19 @@ impl OutputSql { })?; let encrypted_data = EncryptedData::from_bytes(&self.encrypted_data)?; + let payment_id = match self.payment_id { + Some(bytes) => PaymentId::from_bytes(&bytes).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create payment id from stored bytes" + ); + OutputManagerStorageError::ConversionError { + reason: "payment id could not be converted from bytes".to_string(), + } + })?, + None => PaymentId::Empty, + }; + let wallet_output = WalletOutput::new_with_rangeproof( TransactionOutputVersion::get_current_version(), MicroMinotari::from(self.value as u64), @@ -755,6 +776,7 @@ impl OutputSql { Some(bytes) => Some(RangeProof::from_canonical_bytes(&bytes)?), None => None, }, + payment_id.clone(), ); let commitment = Commitment::from_vec(&self.commitment)?; @@ -800,6 +822,7 @@ impl OutputSql { source: self.source.try_into()?, received_in_tx_id: self.received_in_tx_id.map(|d| (d as u64).into()), spent_in_tx_id: self.spent_in_tx_id.map(|d| (d as u64).into()), + payment_id, }) } } diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index fb49078d48..3421bcc2b0 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -37,6 +37,7 @@ diesel::table! { mined_timestamp -> Nullable, transaction_signature_nonce -> Binary, transaction_signature_key -> Binary, + payment_id -> Nullable, } } @@ -117,6 +118,7 @@ diesel::table! { minimum_value_promise -> BigInt, source -> Integer, last_validation_timestamp -> Nullable, + payment_id -> Nullable, } } diff --git a/base_layer/wallet/src/storage/sqlite_db/wallet.rs b/base_layer/wallet/src/storage/sqlite_db/wallet.rs index 74b073b711..962a6bb744 100644 --- a/base_layer/wallet/src/storage/sqlite_db/wallet.rs +++ b/base_layer/wallet/src/storage/sqlite_db/wallet.rs @@ -34,7 +34,7 @@ use argon2::password_hash::{ use blake2::Blake2b; use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305}; use chrono::NaiveDateTime; -use diesel::{prelude::*, result::Error, SqliteConnection}; +use diesel::{prelude::*, result::Error}; use digest::{consts::U32, generic_array::GenericArray, FixedOutput}; use itertools::Itertools; use log::*; diff --git a/base_layer/wallet/src/transaction_service/error.rs b/base_layer/wallet/src/transaction_service/error.rs index 2554814d4e..692d4b3926 100644 --- a/base_layer/wallet/src/transaction_service/error.rs +++ b/base_layer/wallet/src/transaction_service/error.rs @@ -197,6 +197,8 @@ pub enum TransactionServiceError { InvalidBurnTransaction(String), #[error("Invalid data for field {field}")] InvalidDataError { field: String }, + #[error("Transaction has invalid address: `{0}`")] + InvalidAddress(String), } impl From for TransactionServiceError { diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 856d5dc56c..105821f900 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -32,7 +32,7 @@ use tari_common_types::{ burnt_proof::BurntProof, tari_address::TariAddress, transaction::{ImportStatus, TxId}, - types::{FixedHash, PrivateKey, PublicKey, Signature}, + types::{FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, }; use tari_comms::types::CommsPublicKey; use tari_core::{ @@ -41,6 +41,7 @@ use tari_core::{ transactions::{ tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, BuildInfo, CodeTemplateRegistration, OutputFeatures, @@ -50,7 +51,10 @@ use tari_core::{ }, }, }; +use tari_crypto::ristretto::pedersen::PedersenCommitment; +use tari_script::CheckSigSchnorrSignature; use tari_service_framework::reply_channel::SenderService; +use tari_utilities::hex::Hex; use tokio::sync::broadcast; use tower::Service; @@ -98,6 +102,35 @@ pub enum TransactionServiceRequest { claim_public_key: Option, sidechain_deployment_key: Option, }, + CreateNMUtxo { + amount: MicroMinotari, + fee_per_gram: MicroMinotari, + n: u8, + m: u8, + public_keys: Vec, + message: [u8; 32], + maturity: u64, + }, + EncumberAggregateUtxo { + fee_per_gram: MicroMinotari, + output_hash: HashOutput, + expected_commitment: PedersenCommitment, + script_input_shares: HashMap, + script_signature_public_nonces: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + }, + FetchUnspentOutputs { + output_hashes: Vec, + }, + FinalizeSentAggregateTransaction { + tx_id: u64, + total_meta_data_signature: Signature, + total_script_data_signature: Signature, + script_offset: PrivateKey, + }, RegisterValidatorNode { amount: MicroMinotari, validator_node_public_key: CommsPublicKey, @@ -125,6 +158,7 @@ pub enum TransactionServiceRequest { output_features: Box, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, }, SendOneSidedToStealthAddressTransaction { destination: TariAddress, @@ -133,6 +167,7 @@ pub enum TransactionServiceRequest { output_features: Box, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, }, SendShaAtomicSwapTransaction(TariAddress, MicroMinotari, UtxoSelectionCriteria, MicroMinotari, String), CancelTransaction(TxId), @@ -145,6 +180,7 @@ pub enum TransactionServiceRequest { current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, }, SubmitTransactionToSelf(TxId, Transaction, MicroMinotari, MicroMinotari, String), SetLowPowerMode, @@ -162,6 +198,7 @@ pub enum TransactionServiceRequest { } impl fmt::Display for TransactionServiceRequest { + #[allow(clippy::too_many_lines)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::GetPendingInboundTransactions => write!(f, "GetPendingInboundTransactions"), @@ -177,8 +214,91 @@ impl fmt::Display for TransactionServiceRequest { amount, message, .. - } => write!(f, "SendTransaction (to {}, {}, {})", destination, amount, message), + } => write!( + f, + "SendTransaction (amount: {}, to: {}, message: {})", + amount, destination, message + ), Self::BurnTari { amount, message, .. } => write!(f, "Burning Tari ({}, {})", amount, message), + Self::CreateNMUtxo { + amount, + fee_per_gram: _, + n, + m, + public_keys: _, + message: _, + maturity: _, + } => f.write_str(&format!( + "Creating a new n-of-m aggregate uxto with: amount = {}, n = {}, m = {}", + amount, n, m + )), + Self::EncumberAggregateUtxo { + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + .. + } => f.write_str(&format!( + "Creating encumber n-of-m utxo with: fee_per_gram = {}, output_hash = {}, commitment = {}, \ + script_input_shares = {:?},, script_signature_shares = {:?}, sender_offset_public_key_shares = {:?}, \ + metadata_ephemeral_public_key_shares = {:?}, dh_shared_secret_shares = {:?}, recipient_address = {}", + fee_per_gram, + output_hash, + expected_commitment.to_hex(), + script_input_shares + .iter() + .map(|v| format!( + "(public_key: {}, sig: {}, nonce: {})", + v.0.to_hex(), + v.1.get_signature().to_hex(), + v.1.get_public_nonce().to_hex() + )) + .collect::>(), + script_signature_public_nonces + .iter() + .map(|v| format!("(public nonce: {})", v.to_hex(),)) + .collect::>(), + sender_offset_public_key_shares + .iter() + .map(|v| v.to_hex()) + .collect::>(), + metadata_ephemeral_public_key_shares + .iter() + .map(|v| v.to_hex()) + .collect::>(), + dh_shared_secret_shares + .iter() + .map(|v| v.to_hex()) + .collect::>(), + recipient_address, + )), + Self::FetchUnspentOutputs { output_hashes } => { + write!( + f, + "FetchUnspentOutputs({:?})", + output_hashes.iter().map(|v| v.to_hex()).collect::>() + ) + }, + Self::FinalizeSentAggregateTransaction { + tx_id, + total_meta_data_signature, + total_script_data_signature, + script_offset, + } => f.write_str(&format!( + "Finalizing encumbered n-of-m tx(#{}) with: meta_sig(sig: {}, nonce: {}), script_sig(sig: {}, nonce: \ + {}) and script_offset: {}", + tx_id, + total_meta_data_signature.get_signature().to_hex(), + total_meta_data_signature.get_public_nonce().to_hex(), + total_script_data_signature.get_signature().to_hex(), + total_script_data_signature.get_public_nonce().to_hex(), + script_offset.to_hex(), + )), Self::RegisterValidatorNode { validator_node_public_key, message, @@ -219,8 +339,9 @@ impl fmt::Display for TransactionServiceRequest { .. } => write!( f, - "ImportUtxo (from {}, {}, {} and {:?} and {:?} and {:?} and {:?}", - source_address, amount, message, import_status, tx_id, current_height, mined_timestamp + "ImportUtxoWithStatus (amount: {}, from: {}, message: {}, import status: {:?}, TxId: {:?}, height: \ + {:?}, mined at: {:?}", + amount, source_address, message, import_status, tx_id, current_height, mined_timestamp ), Self::SubmitTransactionToSelf(tx_id, _, _, _, _) => write!(f, "SubmitTransaction ({})", tx_id), Self::SetLowPowerMode => write!(f, "SetLowPowerMode "), @@ -246,6 +367,9 @@ impl fmt::Display for TransactionServiceRequest { #[derive(Debug)] pub enum TransactionServiceResponse { TransactionSent(TxId), + TransactionSentWithOutputHash(TxId, FixedHash), + EncumberAggregateUtxo(TxId, Box, Box, Box, Box), + UnspentOutputs(Vec), TransactionImported(TxId), BurntTransactionSent { tx_id: TxId, @@ -550,6 +674,7 @@ impl TransactionServiceHandle { output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { match self .handle @@ -560,6 +685,7 @@ impl TransactionServiceHandle { output_features: Box::new(output_features), fee_per_gram, message, + payment_id, }) .await?? { @@ -595,6 +721,115 @@ impl TransactionServiceHandle { } } + pub async fn create_aggregate_signature_utxo( + &mut self, + amount: MicroMinotari, + fee_per_gram: MicroMinotari, + n: u8, + m: u8, + public_keys: Vec, + message: [u8; 32], + maturity: u64, + ) -> Result<(TxId, FixedHash), TransactionServiceError> { + match self + .handle + .call(TransactionServiceRequest::CreateNMUtxo { + amount, + fee_per_gram, + n, + m, + public_keys, + message, + maturity, + }) + .await?? + { + TransactionServiceResponse::TransactionSentWithOutputHash(tx_id, output_hash) => Ok((tx_id, output_hash)), + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } + + #[allow(clippy::mutable_key_type)] + pub async fn encumber_aggregate_utxo( + &mut self, + fee_per_gram: MicroMinotari, + output_hash: HashOutput, + expected_commitment: PedersenCommitment, + script_input_shares: HashMap, + script_signature_public_nonces: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), TransactionServiceError> { + match self + .handle + .call(TransactionServiceRequest::EncumberAggregateUtxo { + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + }) + .await?? + { + TransactionServiceResponse::EncumberAggregateUtxo( + tx_id, + transaction, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + ) => Ok(( + tx_id, + *transaction, + *total_script_key, + *total_metadata_ephemeral_public_key, + *total_script_nonce, + )), + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } + + pub async fn fetch_unspent_outputs( + &mut self, + output_hashes: Vec, + ) -> Result, TransactionServiceError> { + match self + .handle + .call(TransactionServiceRequest::FetchUnspentOutputs { output_hashes }) + .await?? + { + TransactionServiceResponse::UnspentOutputs(outputs) => Ok(outputs), + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } + + pub async fn finalize_aggregate_utxo( + &mut self, + tx_id: u64, + total_meta_data_signature: Signature, + total_script_data_signature: Signature, + script_offset: PrivateKey, + ) -> Result { + match self + .handle + .call(TransactionServiceRequest::FinalizeSentAggregateTransaction { + tx_id, + total_meta_data_signature, + total_script_data_signature, + script_offset, + }) + .await?? + { + TransactionServiceResponse::TransactionSent(tx_id) => Ok(tx_id), + _ => Err(TransactionServiceError::UnexpectedApiResponse), + } + } + pub async fn send_one_sided_to_stealth_address_transaction( &mut self, destination: TariAddress, @@ -603,6 +838,7 @@ impl TransactionServiceHandle { output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, ) -> Result { match self .handle @@ -613,6 +849,7 @@ impl TransactionServiceHandle { output_features: Box::new(output_features), fee_per_gram, message, + payment_id, }) .await?? { @@ -759,6 +996,7 @@ impl TransactionServiceHandle { current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, ) -> Result { match self .handle @@ -771,6 +1009,7 @@ impl TransactionServiceHandle { current_height, mined_timestamp, scanned_output, + payment_id, }) .await?? { diff --git a/base_layer/wallet/src/transaction_service/mod.rs b/base_layer/wallet/src/transaction_service/mod.rs index 9aaf37563f..241835cbdb 100644 --- a/base_layer/wallet/src/transaction_service/mod.rs +++ b/base_layer/wallet/src/transaction_service/mod.rs @@ -24,6 +24,8 @@ use std::{marker::PhantomData, sync::Arc}; use futures::{Stream, StreamExt}; use log::*; +use tari_common::configuration::Network; +use tari_comms::NodeIdentity; use tari_comms_dht::Dht; use tari_core::{ consensus::ConsensusManager, @@ -60,7 +62,6 @@ use crate::{ service::TransactionService, storage::database::{TransactionBackend, TransactionDatabase}, }, - util::wallet_identity::WalletIdentity, }; pub mod config; @@ -84,7 +85,8 @@ where config: TransactionServiceConfig, subscription_factory: Arc, tx_backend: Option, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, consensus_manager: ConsensusManager, factories: CryptoFactories, wallet_database: Option>, @@ -101,7 +103,8 @@ where config: TransactionServiceConfig, subscription_factory: Arc, backend: T, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, consensus_manager: ConsensusManager, factories: CryptoFactories, wallet_database: WalletDatabase, @@ -110,7 +113,8 @@ where config, subscription_factory, tx_backend: Some(backend), - wallet_identity, + node_identity, + network, consensus_manager, factories, wallet_database: Some(wallet_database), @@ -222,10 +226,11 @@ where .take() .expect("Cannot start Transaction Service without providing a wallet database"); - let wallet_identity = self.wallet_identity.clone(); + let node_identity = self.node_identity.clone(); let consensus_manager = self.consensus_manager.clone(); let factories = self.factories.clone(); let config = self.config.clone(); + let network = self.network; context.spawn_when_ready(move |handles| async move { let outbound_message_service = handles.expect_handle::().outbound_requester(); @@ -249,12 +254,15 @@ where outbound_message_service, connectivity, publisher, - wallet_identity, + node_identity, + network, consensus_manager, factories, handles.get_shutdown_signal(), base_node_service_handle, ) + .await + .expect("Could not initialize Transaction Manager Service") .start() .await; diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs index c0a16a38aa..09fc1b4cbd 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_receive_protocol.rs @@ -315,7 +315,7 @@ where tokio::select! { Some((address, tx_id, tx)) = receiver.recv() => { incoming_finalized_transaction = Some(tx); - if inbound_tx.source_address != address { + if inbound_tx.source_address.public_spend_key() != address.public_spend_key() { warn!( target: LOG_TARGET, "Finalized Transaction did not come from the expected Public Key" @@ -435,7 +435,7 @@ where let completed_transaction = CompletedTransaction::new( self.id, self.source_address.clone(), - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), inbound_tx.amount, finalized_transaction .body @@ -448,6 +448,7 @@ where TransactionDirection::Inbound, None, None, + None, ) .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; diff --git a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs index ab12db4f47..e6c0a95f23 100644 --- a/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs +++ b/base_layer/wallet/src/transaction_service/protocols/transaction_send_protocol.rs @@ -234,9 +234,8 @@ where Ok(sp) => { let _result = service_reply_channel .send(Ok(TransactionServiceResponse::TransactionSent(self.id))) - .map_err(|e| { + .inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send service reply"); - e }); Ok(sp) }, @@ -244,9 +243,8 @@ where let error_string = e.to_string(); let _size = service_reply_channel .send(Err(TransactionServiceError::from(e))) - .map_err(|e| { + .inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send service reply"); - e }); Err(TransactionServiceProtocolError::new( self.id, @@ -495,7 +493,7 @@ where let rr_tx_id = rr.tx_id; reply = Some(rr); - if outbound_tx.destination_address.public_key() != &spk { + if outbound_tx.destination_address.comms_public_key() != &spk { warn!( target: LOG_TARGET, "Transaction Reply did not come from the expected Public Key" @@ -510,7 +508,7 @@ where if result.is_ok() { info!(target: LOG_TARGET, "Cancelling Transaction Send Protocol (TxId: {})", self.id); let _ = send_transaction_cancelled_message( - self.id,self.dest_address.public_key().clone(), + self.id,self.dest_address.comms_public_key().clone(), self.resources.outbound_message_service.clone(), ) .await.map_err(|e| { warn!( @@ -595,7 +593,7 @@ where let completed_transaction = CompletedTransaction::new( tx_id, - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), outbound_tx.destination_address, outbound_tx.amount, outbound_tx.fee, @@ -606,6 +604,7 @@ where TransactionDirection::Outbound, None, None, + None, ) .map_err(|e| TransactionServiceProtocolError::new(self.id, TransactionServiceError::from(e)))?; @@ -621,7 +620,7 @@ where send_finalized_transaction_message( tx_id, tx.clone(), - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), self.resources.outbound_message_service.clone(), self.resources.config.direct_send_timeout, self.resources.config.transaction_routing_mechanism, @@ -704,7 +703,7 @@ where .resources .outbound_message_service .send_direct_unencrypted( - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), OutboundDomainMessage::new(&TariMessageType::SenderPartialTransaction, proto_message.clone()), "transaction send".to_string(), ) @@ -715,7 +714,7 @@ where if wait_on_dial( send_states, self.id, - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), "Transaction", self.resources.config.direct_send_timeout, ) @@ -793,7 +792,7 @@ where direct_send_result = wait_on_dial( send_states, self.id, - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), "Transaction", self.resources.config.direct_send_timeout, ) @@ -849,8 +848,8 @@ where .resources .outbound_message_service .closest_broadcast( - self.dest_address.public_key().clone(), - OutboundEncryption::encrypt_for(self.dest_address.public_key().clone()), + self.dest_address.comms_public_key().clone(), + OutboundEncryption::encrypt_for(self.dest_address.comms_public_key().clone()), vec![], OutboundDomainMessage::new(&TariMessageType::SenderPartialTransaction, proto_message), ) @@ -940,7 +939,7 @@ where ) -> Result<(), TransactionServiceProtocolError> { let _ = send_transaction_cancelled_message( self.id, - self.dest_address.public_key().clone(), + self.dest_address.comms_public_key().clone(), self.resources.outbound_message_service.clone(), ) .await diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 2b757d679e..86df279f24 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -22,7 +22,7 @@ use std::{ collections::{HashMap, HashSet}, - convert::{TryFrom, TryInto}, + convert::TryInto, sync::Arc, time::{Duration, Instant}, }; @@ -33,33 +33,36 @@ use futures::{pin_mut, stream::FuturesUnordered, Stream, StreamExt}; use log::*; use rand::rngs::OsRng; use sha2::Sha256; +use tari_common::configuration::Network; use tari_common_types::{ burnt_proof::BurntProof, - tari_address::TariAddress, + tari_address::{TariAddress, TariAddressFeatures}, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, - types::{FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, + types::{CommitmentFactory, FixedHash, HashOutput, PrivateKey, PublicKey, Signature}, }; -use tari_comms::types::CommsPublicKey; +use tari_comms::{types::CommsPublicKey, NodeIdentity}; use tari_comms_dht::outbound::OutboundMessageRequester; use tari_core::{ consensus::{ConsensusManager, MaxSizeBytes, MaxSizeString}, covenants::Covenant, mempool::FeePerGramStat, one_sided::{ - diffie_hellman_stealth_domain_hasher, + public_key_to_output_encryption_key, shared_secret_to_output_encryption_key, shared_secret_to_output_spending_key, stealth_address_script_spending_key, }, - proto::base_node as base_node_proto, + proto::{base_node as base_node_proto, base_node::FetchMatchingUtxos}, transactions::{ - key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerBranch}, + key_manager::{SecretTransactionKeyManagerInterface, TariKeyId, TransactionKeyManagerBranch}, tari_amount::MicroMinotari, transaction_components::{ + encrypted_data::PaymentId, BuildInfo, CodeTemplateRegistration, KernelFeatures, OutputFeatures, + RangeProofType, TemplateType, Transaction, TransactionOutput, @@ -77,13 +80,21 @@ use tari_core::{ }; use tari_crypto::{ keys::{PublicKey as PKtrait, SecretKey}, - ristretto::RistrettoPublicKey, + ristretto::{pedersen::PedersenCommitment, RistrettoPublicKey}, signatures::SchnorrSignature, tari_utilities::ByteArray, }; use tari_key_manager::key_manager_service::KeyId; use tari_p2p::domain_message::DomainMessage; -use tari_script::{inputs, one_sided_payment_script, script, stealth_payment_script, TariScript}; +use tari_script::{ + push_pubkey_script, + script, + slice_to_boxed_message, + CheckSigSchnorrSignature, + ExecutionStack, + ScriptContext, + TariScript, +}; use tari_service_framework::{reply_channel, reply_channel::Receiver}; use tari_shutdown::ShutdownSignal; use tokio::{ @@ -133,7 +144,7 @@ use crate::{ }, utc::utc_duration_since, }, - util::{wallet_identity::WalletIdentity, watch::Watch}, + util::watch::Watch, utxo_scanner_service::RECOVERY_KEY, OperationId, }; @@ -224,7 +235,7 @@ where TWalletConnectivity: WalletConnectivityInterface, TKeyManagerInterface: SecretTransactionKeyManagerInterface, { - pub fn new( + pub async fn new( config: TransactionServiceConfig, db: TransactionDatabase, wallet_db: WalletDatabase, @@ -242,14 +253,31 @@ where outbound_message_service: OutboundMessageRequester, connectivity: TWalletConnectivity, event_publisher: TransactionEventSender, - wallet_identity: WalletIdentity, + node_identity: Arc, + network: Network, consensus_manager: ConsensusManager, factories: CryptoFactories, shutdown_signal: ShutdownSignal, base_node_service: BaseNodeServiceHandle, - ) -> Self { + ) -> Result { // Collect the resources that all protocols will need so that they can be neatly cloned as the protocols are // spawned. + let view_key = core_key_manager_service.get_view_key().await?; + let spend_key = core_key_manager_service.get_spend_key().await?; + let comms_key = core_key_manager_service.get_comms_key().await?; + let interactive_features = if spend_key == comms_key { + TariAddressFeatures::create_interactive_and_one_sided() + } else { + TariAddressFeatures::create_one_sided_only() + }; + let one_sided_tari_address = TariAddress::new_dual_address( + view_key.pub_key.clone(), + comms_key.pub_key, + network, + TariAddressFeatures::create_one_sided_only(), + ); + let interactive_tari_address = + TariAddress::new_dual_address(view_key.pub_key, spend_key.pub_key, network, interactive_features); let resources = TransactionServiceResources { db: db.clone(), output_manager_service, @@ -257,7 +285,9 @@ where outbound_message_service, connectivity, event_publisher: event_publisher.clone(), - wallet_identity, + interactive_tari_address, + one_sided_tari_address, + node_identity: node_identity.clone(), factories, config: config.clone(), shutdown_signal, @@ -270,7 +300,7 @@ where }; let timeout_update_watch = Watch::new(timeout); - Self { + Ok(Self { config, db, transaction_stream: Some(transaction_stream), @@ -293,18 +323,11 @@ where last_seen_tip_height: None, validation_in_progress: Arc::new(Mutex::new(())), consensus_manager, - } + }) } #[allow(clippy::too_many_lines)] pub async fn start(mut self) -> Result<(), TransactionServiceError> { - // we need to ensure the wallet identity secret key is stored in the key manager - let _key_id = self - .resources - .transaction_key_manager_service - .import_key(self.resources.wallet_identity.node_identity.secret_key().clone()) - .await?; - let request_stream = self .request_stream .take() @@ -417,7 +440,7 @@ where } Err(e) => { warn!(target: LOG_TARGET, "Failed to handle incoming Transaction message: {} for NodeID: {}, Trace: {}", - e, self.resources.wallet_identity.node_identity.node_id().short_str(), msg.dht_header.message_tag); + e, self.resources.node_identity.node_id().short_str(), msg.dht_header.message_tag); let _size = self.event_publisher.send(Arc::new(TransactionEvent::Error(format!("Error handling \ Transaction Sender message: {:?}", e).to_string()))); } @@ -440,12 +463,12 @@ where Err(TransactionServiceError::TransactionDoesNotExistError) => { trace!(target: LOG_TARGET, "Unable to handle incoming Transaction Reply message from NodeId: \ {} due to Transaction not existing. This usually means the message was a repeated message \ - from Store and Forward, Trace: {}", self.resources.wallet_identity.node_identity.node_id().short_str(), + from Store and Forward, Trace: {}", self.resources.node_identity.node_id().short_str(), msg.dht_header.message_tag); }, Err(e) => { warn!(target: LOG_TARGET, "Failed to handle incoming Transaction Reply message: {} \ - for NodeId: {}, Trace: {}", e, self.resources.wallet_identity.node_identity.node_id().short_str(), + for NodeId: {}, Trace: {}", e, self.resources.node_identity.node_id().short_str(), msg.dht_header.message_tag); let _size = self.event_publisher.send(Arc::new(TransactionEvent::Error("Error handling \ Transaction Recipient Reply message".to_string()))); @@ -476,12 +499,12 @@ where Err(TransactionServiceError::TransactionDoesNotExistError) => { trace!(target: LOG_TARGET, "Unable to handle incoming Finalized Transaction message from NodeId: \ {} due to Transaction not existing. This usually means the message was a repeated message \ - from Store and Forward, Trace: {}", self.resources.wallet_identity.node_identity.node_id().short_str(), + from Store and Forward, Trace: {}", self.resources.node_identity.node_id().short_str(), msg.dht_header.message_tag); }, Err(e) => { warn!(target: LOG_TARGET, "Failed to handle incoming Transaction Finalized message: {} \ - for NodeID: {}, Trace: {}", e , self.resources.wallet_identity.node_identity.node_id().short_str(), + for NodeID: {}, Trace: {}", e , self.resources.node_identity.node_id().short_str(), msg.dht_header.message_tag.as_value()); let _size = self.event_publisher.send(Arc::new(TransactionEvent::Error("Error handling Transaction \ Finalized message".to_string(),))); @@ -501,7 +524,7 @@ where trace!(target: LOG_TARGET, "Handling Base Node Response, Trace: {}", msg.dht_header.message_tag); let _result = self.handle_base_node_response(inner_msg).await.map_err(|e| { warn!(target: LOG_TARGET, "Error handling base node service response from {}: {:?} for \ - NodeID: {}, Trace: {}", origin_public_key, e, self.resources.wallet_identity.node_identity.node_id().short_str(), + NodeID: {}, Trace: {}", origin_public_key, e, self.resources.node_identity.node_id().short_str(), msg.dht_header.message_tag.as_value()); e }); @@ -626,6 +649,7 @@ where output_features, fee_per_gram, message, + payment_id, } => self .send_one_sided_transaction( destination, @@ -634,10 +658,11 @@ where *output_features, fee_per_gram, message, + payment_id, transaction_broadcast_join_handles, ) .await - .map(|(tx_id, _)| TransactionServiceResponse::TransactionSent(tx_id)), + .map(TransactionServiceResponse::TransactionSent), TransactionServiceRequest::SendOneSidedToStealthAddressTransaction { destination, amount, @@ -645,6 +670,7 @@ where output_features, fee_per_gram, message, + payment_id, } => self .send_one_sided_to_stealth_address_transaction( destination, @@ -653,10 +679,11 @@ where *output_features, fee_per_gram, message, + payment_id, transaction_broadcast_join_handles, ) .await - .map(|(tx_id, _)| TransactionServiceResponse::TransactionSent(tx_id)), + .map(TransactionServiceResponse::TransactionSent), TransactionServiceRequest::BurnTari { amount, selection_criteria, @@ -679,6 +706,82 @@ where tx_id, proof: Box::new(proof), }), + TransactionServiceRequest::CreateNMUtxo { + amount, + fee_per_gram, + n, + m, + public_keys, + message, + maturity, + } => self + .create_aggregate_signature_utxo( + amount, + fee_per_gram, + n, + m, + public_keys, + message, + maturity, + transaction_broadcast_join_handles, + ) + .await + .map(|(tx_id, output_hash)| { + TransactionServiceResponse::TransactionSentWithOutputHash(tx_id, output_hash) + }), + TransactionServiceRequest::EncumberAggregateUtxo { + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + } => self + .encumber_aggregate_tx( + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address, + ) + .await + .map( + |(tx_id, tx, total_script_pubkey, total_metadata_ephemeral_public_key, total_script_nonce)| { + TransactionServiceResponse::EncumberAggregateUtxo( + tx_id, + Box::new(tx), + Box::new(total_script_pubkey), + Box::new(total_metadata_ephemeral_public_key), + Box::new(total_script_nonce), + ) + }, + ), + TransactionServiceRequest::FetchUnspentOutputs { output_hashes } => { + let unspent_outputs = self.fetch_unspent_outputs_from_node(output_hashes).await?; + Ok(TransactionServiceResponse::UnspentOutputs(unspent_outputs)) + }, + TransactionServiceRequest::FinalizeSentAggregateTransaction { + tx_id, + total_meta_data_signature, + total_script_data_signature, + script_offset, + } => Ok(TransactionServiceResponse::TransactionSent( + self.finalized_aggregate_encumbed_tx( + tx_id.into(), + total_meta_data_signature, + total_script_data_signature, + script_offset, + transaction_broadcast_join_handles, + ) + .await?, + )), TransactionServiceRequest::RegisterValidatorNode { amount, validator_node_public_key, @@ -818,6 +921,7 @@ where current_height, mined_timestamp, scanned_output, + payment_id, } => self .add_utxo_import_transaction_with_status( amount, @@ -828,6 +932,7 @@ where current_height, mined_timestamp, scanned_output, + payment_id, ) .await .map(TransactionServiceResponse::UtxoImported), @@ -876,9 +981,8 @@ where // If the individual handlers did not already send the API response then do it here. if let Some(rp) = reply_channel { - let _result = rp.send(response).map_err(|e| { + let _result = rp.send(response).inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send reply"); - e }); } Ok(()) @@ -955,10 +1059,7 @@ where if let OutputManagerEvent::TxoValidationSuccess(_) = (*event).clone() { let db = self.db.clone(); let output_manager_handle = self.resources.output_manager_service.clone(); - let metadata = match self.wallet_db.get_chain_metadata() { - Ok(data) => data, - Err(_) => None, - }; + let metadata = self.wallet_db.get_chain_metadata().unwrap_or_default(); let tip_height = match metadata { Some(val) => val.best_block_height(), None => 0u64, @@ -994,26 +1095,31 @@ where JoinHandle>>, >, reply_channel: oneshot::Sender>, - ) -> Result<(TxId, Option), TransactionServiceError> { + ) -> Result<(), TransactionServiceError> { let tx_id = TxId::new_random(); - if destination.network() != self.resources.wallet_identity.network { + if destination.network() != self.resources.interactive_tari_address.network() { let _result = reply_channel .send(Err(TransactionServiceError::InvalidNetwork)) - .map_err(|e| { + .inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send service reply"); - e }); return Err(TransactionServiceError::InvalidNetwork); } - let dest_pubkey = destination.public_key(); // If we're paying ourselves, let's complete and submit the transaction immediately - if self.resources.wallet_identity.address.public_key() == dest_pubkey { + if &self + .resources + .transaction_key_manager_service + .get_comms_key() + .await? + .pub_key == + destination.comms_public_key() + { debug!( target: LOG_TARGET, "Received transaction with spend-to-self transaction" ); - let (fee, transaction, main_output) = self + let (fee, transaction, _template_addr) = self .resources .output_manager_service .create_pay_to_self_transaction(tx_id, amount, selection_criteria, output_features, fee_per_gram, None) @@ -1028,8 +1134,8 @@ where transaction_broadcast_join_handles, CompletedTransaction::new( tx_id, - self.resources.wallet_identity.address.clone(), - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), + self.resources.interactive_tari_address.clone(), amount, fee, transaction, @@ -1039,18 +1145,18 @@ where TransactionDirection::Inbound, None, None, + None, )?, ) .await?; let _result = reply_channel .send(Ok(TransactionServiceResponse::TransactionSent(tx_id))) - .map_err(|e| { + .inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send service reply"); - e }); - return Ok((tx_id, Some(main_output))); + return Ok(()); } let (tx_reply_sender, tx_reply_receiver) = mpsc::channel(100); @@ -1076,7 +1182,404 @@ where let join_handle = tokio::spawn(protocol.execute()); join_handles.push(join_handle); - Ok((tx_id, None)) + Ok(()) + } + + /// Creates a utxo with aggregate public key out of m-of-n public keys + #[allow(clippy::too_many_lines)] + pub async fn create_aggregate_signature_utxo( + &mut self, + amount: MicroMinotari, + fee_per_gram: MicroMinotari, + n: u8, + m: u8, + public_keys: Vec, + message: [u8; 32], + maturity: u64, + transaction_broadcast_join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + ) -> Result<(TxId, FixedHash), TransactionServiceError> { + let tx_id = TxId::new_random(); + + let msg = slice_to_boxed_message(message.as_bytes()); + let script = script!(CheckMultiSigVerifyAggregatePubKey(n, m, public_keys.clone(), msg)); + + // Empty covenant + let covenant = Covenant::default(); + + // Default range proof + let minimum_value_promise = amount; + + // Prepare sender part of transaction + let mut stp = self + .resources + .output_manager_service + .prepare_transaction_to_send( + tx_id, + amount, + UtxoSelectionCriteria::default(), + OutputFeatures { + range_proof_type: RangeProofType::RevealedValue, + maturity, + ..Default::default() + }, + fee_per_gram, + TransactionMetadata::default(), + "".to_string(), + script.clone(), + covenant.clone(), + minimum_value_promise, + ) + .await?; + let sender_message = TransactionSenderMessage::new_single_round_message( + stp.get_single_round_message(&self.resources.transaction_key_manager_service) + .await?, + ); + + // This call is needed to advance the state from `SingleRoundMessageReady` to `CollectingSingleSignature`, + // but the returned value is not used + let _single_round_sender_data = stp + .build_single_round_message(&self.resources.transaction_key_manager_service) + .await + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + self.resources + .output_manager_service + .confirm_pending_transaction(tx_id) + .await + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + // Prepare receiver part of the transaction + + // In generating an aggregate public key utxo, we can use a randomly generated spend key + let spending_key = PrivateKey::random(&mut OsRng); + let sum_keys = public_keys.iter().fold(PublicKey::default(), |acc, x| acc + x); + let encryption_private_key = public_key_to_output_encryption_key(&sum_keys)?; + + let sender_offset_private_key = stp + .get_recipient_sender_offset_private_key() + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))? + .ok_or(TransactionServiceProtocolError::new( + tx_id, + TransactionServiceError::InvalidKeyId("Missing sender offset keyid".to_string()), + ))?; + + let encryption_key_id = self + .resources + .transaction_key_manager_service + .import_key(encryption_private_key) + .await?; + + let sender_offset_public_key = self + .resources + .transaction_key_manager_service + .get_public_key_at_key_id(&sender_offset_private_key) + .await?; + + let spending_key_id = self + .resources + .transaction_key_manager_service + .import_key(spending_key.clone()) + .await?; + + let wallet_output = WalletOutputBuilder::new(amount, spending_key_id) + .with_features( + sender_message + .single() + .ok_or(TransactionServiceProtocolError::new( + tx_id, + TransactionServiceError::InvalidMessageError("Sent invalid message type".to_string()), + ))? + .features + .clone(), + ) + .with_script(script) + // We don't want the given utxo to be spendable as an input to a later transaction, so we set + // spendable height of the current utxo to be u64::MAx + .with_script_lock_height(u64::MAX) + .encrypt_data_for_recovery( + &self.resources.transaction_key_manager_service, + Some(&encryption_key_id), + PaymentId::Empty, + ) + .await? + .with_input_data( + ExecutionStack::default(), + ) + .with_covenant(covenant) + .with_sender_offset_public_key(sender_offset_public_key) + .with_script_key(TariKeyId::default()) + .with_minimum_value_promise(minimum_value_promise) + .sign_as_sender_and_receiver( + &self.resources.transaction_key_manager_service, + &sender_offset_private_key, + ) + .await + .unwrap() + .try_build(&self.resources.transaction_key_manager_service) + .await + .unwrap(); + + let tip_height = self.last_seen_tip_height.unwrap_or(0); + let consensus_constants = self.consensus_manager.consensus_constants(tip_height); + let rtp = ReceiverTransactionProtocol::new( + sender_message, + wallet_output.clone(), + &self.resources.transaction_key_manager_service, + consensus_constants, + ) + .await; + let recipient_reply = rtp.get_signed_data()?.clone(); + + // Start finalize + stp.add_single_recipient_info(recipient_reply, &self.resources.transaction_key_manager_service) + .await + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + + // Finalize: + stp.finalize(&self.resources.transaction_key_manager_service) + .await + .map_err(|e| { + error!( + target: LOG_TARGET, + "Transaction (TxId: {}) could not be finalized. Failure error: {:?}", tx_id, e, + ); + TransactionServiceProtocolError::new(tx_id, e.into()) + })?; + info!( + target: LOG_TARGET, + "Finalized create n of m transaction TxId: {}", tx_id + ); + + // This event being sent is important, but not critical to the protocol being successful. Send only fails if + // there are no subscribers. + let _size = self + .event_publisher + .send(Arc::new(TransactionEvent::TransactionCompletedImmediately(tx_id))); + + // Broadcast create n of m aggregate public key transaction + let tx = stp + .get_transaction() + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + let fee = stp + .get_fee_amount() + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + self.resources + .output_manager_service + .add_output_with_tx_id(tx_id, wallet_output.clone(), Some(SpendingPriority::Normal)) + .await?; + self.submit_transaction( + transaction_broadcast_join_handles, + CompletedTransaction::new( + tx_id, + self.resources.interactive_tari_address.clone(), + self.resources.interactive_tari_address.clone(), + amount, + fee, + tx.clone(), + TransactionStatus::Completed, + "".to_string(), + Utc::now().naive_utc(), + TransactionDirection::Outbound, + None, + None, + None, + ) + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?, + ) + .await?; + + // we want to print out the hash of the utxo + let output_hash = wallet_output + .hash(&self.resources.transaction_key_manager_service) + .await?; + Ok((tx_id, output_hash)) + } + + async fn fetch_unspent_outputs_from_node( + &mut self, + hashes: Vec, + ) -> Result, TransactionServiceError> { + // lets get the output from the blockchain + let req = FetchMatchingUtxos { + output_hashes: hashes.iter().map(|v| v.to_vec()).collect(), + }; + let results: Vec = self + .resources + .connectivity + .obtain_base_node_wallet_rpc_client() + .await + .ok_or_else(|| { + TransactionServiceError::ServiceError("Could not connect to base node rpc client".to_string()) + })? + .fetch_matching_utxos(req) + .await? + .outputs + .into_iter() + .filter_map(|o| match o.try_into() { + Ok(output) => Some(output), + _ => None, + }) + .collect(); + Ok(results) + } + + /// Creates an encumbered uninitialized transaction + #[allow(clippy::mutable_key_type)] + pub async fn encumber_aggregate_tx( + &mut self, + fee_per_gram: MicroMinotari, + output_hash: HashOutput, + expected_commitment: PedersenCommitment, + script_input_shares: HashMap, + script_signature_public_nonces: Vec, + sender_offset_public_key_shares: Vec, + metadata_ephemeral_public_key_shares: Vec, + dh_shared_secret_shares: Vec, + recipient_address: TariAddress, + ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), TransactionServiceError> { + let tx_id = TxId::new_random(); + + match self + .resources + .output_manager_service + .encumber_aggregate_utxo( + tx_id, + fee_per_gram, + output_hash, + expected_commitment, + script_input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + recipient_address.clone(), + ) + .await + { + Ok(( + transaction, + amount, + fee, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) => { + let completed_tx = CompletedTransaction::new( + tx_id, + self.resources.interactive_tari_address.clone(), + recipient_address, + amount, + fee, + transaction.clone(), + TransactionStatus::Pending, + "claimed n-of-m utxo".to_string(), + Utc::now().naive_utc(), + TransactionDirection::Outbound, + None, + None, + None, + ) + .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; + self.db.insert_completed_transaction(tx_id, completed_tx)?; + Ok(( + tx_id, + transaction, + total_script_key, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) + }, + Err(e) => Err(e.into()), + } + } + + /// Creates an encumbered uninitialized transaction + pub async fn finalized_aggregate_encumbed_tx( + &mut self, + tx_id: TxId, + total_meta_data_signature: Signature, + total_script_data_signature: Signature, + script_offset: PrivateKey, + transaction_broadcast_join_handles: &mut FuturesUnordered< + JoinHandle>>, + >, + ) -> Result { + let mut transaction = self.db.get_completed_transaction(tx_id)?; + + // Add the aggregate signature components + transaction.transaction.script_offset = &transaction.transaction.script_offset + &script_offset; + + transaction.transaction.body.update_metadata_signature( + &(transaction.transaction.body.outputs()[0].commitment.clone()), + &transaction.transaction.body.outputs()[0].metadata_signature + &total_meta_data_signature, + )?; + + transaction.transaction.body.update_script_signature( + &(transaction.transaction.body.inputs()[0].commitment()?.clone()), + &transaction.transaction.body.inputs()[0].script_signature + &total_script_data_signature, + )?; + + // Validate the aggregate signatures and script offset + let factory = CommitmentFactory::default(); + let mut input_keys = PublicKey::default(); + for input in transaction.transaction.body.inputs() { + let context = ScriptContext::new( + self.last_seen_tip_height.unwrap_or(0), + &[0; 32], + input + .commitment() + .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?, + ); + input_keys = input_keys + + input + .run_and_verify_script(&factory, Some(context)) + .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?; + } + let mut output_keys = PublicKey::default(); + for output in transaction.transaction.body.outputs() { + output + .verify_metadata_signature() + .map_err(|e| TransactionServiceError::ServiceError(format!("TxId: {}, {}", tx_id, e)))?; + output_keys = output_keys + output.sender_offset_public_key.clone(); + } + let lhs = input_keys - output_keys; + if lhs != PublicKey::from_secret_key(&transaction.transaction.script_offset) { + return Err(TransactionServiceError::ServiceError(format!( + "Invalid script offset (TxId: {})", + tx_id + ))); + } + + // Update the wallet database + let _res = self + .resources + .output_manager_service + .update_output_metadata_signature(transaction.transaction.body.outputs()[0].clone()) + .await; + + self.db.update_completed_transaction(tx_id, transaction)?; + + self.resources + .output_manager_service + .confirm_pending_transaction(tx_id) + .await?; + + // Notify that the transaction was successfully resolved. + let _size = self + .event_publisher + .send(Arc::new(TransactionEvent::TransactionCompletedImmediately(tx_id))); + + self.complete_send_transaction_protocol( + Ok(TransactionSendResult { + tx_id, + transaction_status: TransactionStatus::Completed, + }), + transaction_broadcast_join_handles, + ); + + Ok(tx_id) } /// broadcasts a SHA-XTR atomic swap transaction @@ -1096,7 +1599,6 @@ where JoinHandle>>, >, ) -> Result, TransactionServiceError> { - let dest_pubkey = destination.public_key(); let tx_id = TxId::new_random(); // this can be anything, so lets generate a random private key let pre_image = PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)); @@ -1109,9 +1611,9 @@ where // lets create the HTLC script let script = script!( HashSha256 PushHash(Box::new(hash)) Equal IfThen - PushPubKey(Box::new(dest_pubkey.clone())) + PushPubKey(Box::new(destination.public_spend_key().clone())) Else - CheckHeightVerify(height) PushPubKey(Box::new(self.resources.wallet_identity.node_identity.public_key().clone())) + CheckHeightVerify(height) PushPubKey(Box::new(self.resources.node_identity.public_key().clone())) EndIf ); @@ -1167,7 +1669,15 @@ where let shared_secret = self .resources .transaction_key_manager_service - .get_diffie_hellman_shared_secret(&sender_offset_private_key, destination.public_key()) + .get_diffie_hellman_shared_secret( + &sender_offset_private_key, + destination + .public_view_key() + .ok_or(TransactionServiceProtocolError::new( + tx_id, + TransactionServiceError::InvalidAddress("Missing public view key".to_string()), + ))?, + ) .await?; let spending_key = shared_secret_to_output_spending_key(&shared_secret) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; @@ -1208,14 +1718,22 @@ where .clone(), ) .with_script(script) - .encrypt_data_for_recovery(&self.resources.transaction_key_manager_service, Some(&encryption_key)) + .encrypt_data_for_recovery( + &self.resources.transaction_key_manager_service, + Some(&encryption_key), + PaymentId::Empty, + ) .await? - .with_input_data(inputs!(PublicKey::from_secret_key( - self.resources.wallet_identity.node_identity.secret_key() - ))) + .with_input_data(ExecutionStack::default()) .with_covenant(covenant) .with_sender_offset_public_key(sender_offset_public_key) - .with_script_key(self.resources.wallet_identity.wallet_node_key_id.clone()) + .with_script_key( + self.resources + .transaction_key_manager_service + .get_spend_key() + .await? + .key_id, + ) .with_minimum_value_promise(minimum_value_promise) .sign_as_sender_and_receiver( &self.resources.transaction_key_manager_service, @@ -1278,7 +1796,7 @@ where transaction_broadcast_join_handles, CompletedTransaction::new( tx_id, - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), destination, amount, fee, @@ -1289,6 +1807,7 @@ where TransactionDirection::Outbound, None, None, + None, )?, ) .await?; @@ -1312,10 +1831,18 @@ where transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, >, - script: TariScript, - ) -> Result<(TxId, HashOutput), TransactionServiceError> { + recipient_script: Option, + payment_id: PaymentId, + ) -> Result { let tx_id = TxId::new_random(); + // For a stealth transaction, the script is not provided because the public key that should be included + // is not known at this stage. This will only be known later. For now, + // we include a default public key to ensure that the script size is correct. + let (mut script, use_stealth_address) = match recipient_script { + Some(s) => (s, false), + None => (push_pubkey_script(&Default::default()), true), + }; // Prepare sender part of the transaction let mut stp = self .resources @@ -1335,7 +1862,15 @@ where .await?; // This call is needed to advance the state from `SingleRoundMessageReady` to `SingleRoundMessageReady`, - // but the returned value is not used + // but the returned value is not used. We have to wait until the sender transaction protocol creates a + // sender_offset_private_key for us, so we can use it to create the shared secret + let key = self + .resources + .transaction_key_manager_service + .get_next_key(TransactionKeyManagerBranch::SenderOffsetLedger.get_branch_key()) + .await?; + + stp.change_recipient_sender_offset_private_key(key.key_id)?; let _single_round_sender_data = stp .build_single_round_message(&self.resources.transaction_key_manager_service) .await @@ -1359,10 +1894,37 @@ where TransactionServiceError::InvalidKeyId("Missing sender offset keyid".to_string()), ))?; + if use_stealth_address { + // lets fix the script with the correct one + let c = self + .resources + .transaction_key_manager_service + .get_diffie_hellman_stealth_domain_hasher( + &sender_offset_private_key, + dest_address + .public_view_key() + .ok_or(TransactionServiceError::OneSidedTransactionError( + "Missing public view key".to_string(), + ))?, + ) + .await?; + + let script_spending_key = stealth_address_script_spending_key(&c, dest_address.public_spend_key()); + script = push_pubkey_script(&script_spending_key); + } + let shared_secret = self .resources .transaction_key_manager_service - .get_diffie_hellman_shared_secret(&sender_offset_private_key, dest_address.public_key()) + .get_diffie_hellman_shared_secret( + &sender_offset_private_key, + dest_address + .public_view_key() + .ok_or(TransactionServiceProtocolError::new( + tx_id, + TransactionServiceError::OneSidedTransactionError("Missing public view key".to_string()), + ))?, + ) .await?; let spending_key = shared_secret_to_output_spending_key(&shared_secret) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; @@ -1404,13 +1966,15 @@ where .clone(), ) .with_script(script) - .encrypt_data_for_recovery(&self.resources.transaction_key_manager_service, Some(&encryption_key)) + .encrypt_data_for_recovery( + &self.resources.transaction_key_manager_service, + Some(&encryption_key), + payment_id.clone(), + ) .await? - .with_input_data(inputs!(PublicKey::from_secret_key( - self.resources.wallet_identity.node_identity.secret_key() - ))) + .with_input_data(Default::default()) .with_sender_offset_public_key(sender_offset_public_key) - .with_script_key(self.resources.wallet_identity.wallet_node_key_id.clone()) + .with_script_key(KeyId::Zero) .with_minimum_value_promise(minimum_value_promise) .sign_as_sender_and_receiver( &self.resources.transaction_key_manager_service, @@ -1432,7 +1996,6 @@ where let recipient_reply = rtp.get_signed_data()?.clone(); - let main_output_hash = recipient_reply.output.hash(); // Start finalizing stp.add_presigned_recipient_info(recipient_reply) .map_err(|e| TransactionServiceProtocolError::new(tx_id, e.into()))?; @@ -1468,7 +2031,7 @@ where transaction_broadcast_join_handles, CompletedTransaction::new( tx_id, - self.resources.wallet_identity.address.clone(), + self.resources.one_sided_tari_address.clone(), dest_address, amount, fee, @@ -1479,11 +2042,12 @@ where TransactionDirection::Outbound, None, None, + Some(payment_id), )?, ) .await?; - Ok((tx_id, main_output_hash)) + Ok(tx_id) } /// Sends a one side payment transaction to a recipient @@ -1499,20 +2063,15 @@ where output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, >, - ) -> Result<(TxId, HashOutput), TransactionServiceError> { - if destination.network() != self.resources.wallet_identity.network { + ) -> Result { + if destination.network() != self.resources.one_sided_tari_address.network() { return Err(TransactionServiceError::InvalidNetwork); } - if self.resources.wallet_identity.node_identity.public_key() == destination.public_key() { - warn!(target: LOG_TARGET, "One-sided spend-to-self transactions not supported"); - return Err(TransactionServiceError::OneSidedTransactionError( - "One-sided spend-to-self transactions not supported".to_string(), - )); - } - let dest_pubkey = destination.public_key().clone(); + let dest_pubkey = destination.public_spend_key().clone(); self.send_one_sided_or_stealth( destination, amount, @@ -1521,7 +2080,8 @@ where fee_per_gram, message, transaction_broadcast_join_handles, - one_sided_payment_script(&dest_pubkey), + Some(push_pubkey_script(&dest_pubkey)), + payment_id, ) .await } @@ -1603,27 +2163,28 @@ where stp.get_single_round_message(&self.resources.transaction_key_manager_service) .await?, ); - let (spend_key_id, public_spend_key, _script_key_id, _) = self + let (commitment_mask_key, _) = self .resources .transaction_key_manager_service - .get_next_spend_and_script_key_ids() + .get_next_commitment_mask_and_script_key() .await?; let recovery_key_id = self .resources .transaction_key_manager_service - .get_recovery_key_id() - .await?; + .get_view_key() + .await? + .key_id; let recovery_key_id = match claim_public_key { Some(ref claim_public_key) => { // For claimable L2 burn transactions, we derive a shared secret and encryption key from a nonce (in // this case a new spend key from the key manager) and the provided claim public key. The public - // nonce/spend_key is returned back to the caller. + // nonce/commitment_mask_key is returned back to the caller. let shared_secret = self .resources .transaction_key_manager_service - .get_diffie_hellman_shared_secret(&spend_key_id, claim_public_key) + .get_diffie_hellman_shared_secret(&commitment_mask_key.key_id, claim_public_key) .await?; let encryption_key = shared_secret_to_output_encryption_key(&shared_secret)?; self.resources @@ -1644,7 +2205,7 @@ where tx_id, TransactionServiceError::InvalidKeyId("Missing sender offset keyid".to_string()), ))?; - let output = WalletOutputBuilder::new(amount, spend_key_id.clone()) + let output = WalletOutputBuilder::new(amount, commitment_mask_key.key_id.clone()) .with_features( sender_message .single() @@ -1656,11 +2217,13 @@ where .clone(), ) .with_script(script!(Nop)) - .encrypt_data_for_recovery(&self.resources.transaction_key_manager_service, Some(&recovery_key_id)) + .encrypt_data_for_recovery( + &self.resources.transaction_key_manager_service, + Some(&recovery_key_id), + PaymentId::Empty, + ) .await? - .with_input_data(inputs!(PublicKey::from_secret_key( - self.resources.wallet_identity.node_identity.secret_key() - ))) + .with_input_data(Default::default()) .with_sender_offset_public_key( sender_message .single() @@ -1671,7 +2234,7 @@ where .sender_offset_public_key .clone(), ) - .with_script_key(self.resources.wallet_identity.wallet_node_key_id.clone()) + .with_script_key(KeyId::Zero) .with_minimum_value_promise( sender_message .single() @@ -1708,7 +2271,7 @@ where ownership_proof = Some( self.resources .transaction_key_manager_service - .generate_burn_proof(&spend_key_id, &amount.into(), &claim_public_key) + .generate_burn_proof(&commitment_mask_key.key_id, &amount.into(), &claim_public_key) .await?, ); } @@ -1746,7 +2309,7 @@ where transaction_broadcast_join_handles, CompletedTransaction::new( tx_id, - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), TariAddress::default(), amount, fee, @@ -1757,6 +2320,7 @@ where TransactionDirection::Outbound, None, None, + None, )?, ) .await?; @@ -1764,7 +2328,7 @@ where Ok((tx_id, BurntProof { // Key used to claim the burn on L2 - reciprocal_claim_public_key: public_spend_key, + reciprocal_claim_public_key: commitment_mask_key.pub_key, commitment, ownership_proof, range_proof, @@ -1807,7 +2371,7 @@ where sidechain_id_knowledge_proof, ); self.send_transaction( - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), amount, selection_criteria, output_features, @@ -1839,17 +2403,17 @@ where JoinHandle>>, >, ) -> Result<(TxId, HashOutput), TransactionServiceError> { - let (author_pub_id, author_pub_key) = self + let author_key = self .resources .transaction_key_manager_service - .get_next_key(&TransactionKeyManagerBranch::CodeTemplateAuthor.get_branch_key()) + .get_next_key(TransactionKeyManagerBranch::CodeTemplateAuthor.get_branch_key()) .await?; let (nonce_secret, nonce_pub) = RistrettoPublicKey::random_keypair(&mut OsRng); let (sidechain_id, sidechain_id_knowledge_proof) = match sidechain_deployment_key { Some(k) => ( Some(PublicKey::from_secret_key(&k)), Some( - SchnorrSignature::sign(&k, author_pub_key.to_vec(), &mut OsRng) + SchnorrSignature::sign(&k, author_key.pub_key.as_bytes(), &mut OsRng) .map_err(|e| TransactionServiceError::SidechainSigningError(e.to_string()))?, ), ), @@ -1857,7 +2421,7 @@ where }; let mut template_registration = CodeTemplateRegistration { - author_public_key: author_pub_key.clone(), + author_public_key: author_key.pub_key.clone(), author_signature: Signature::default(), template_name: template_name .try_into() @@ -1883,7 +2447,7 @@ where let author_sig = self .resources .transaction_key_manager_service - .sign_raw(&challenge, &author_pub_id, nonce_secret) + .sign_raw(&challenge, &author_key.key_id, nonce_secret) .await .map_err(|e| TransactionServiceError::SidechainSigningError(e.to_string()))?; @@ -1921,26 +2485,14 @@ where output_features: OutputFeatures, fee_per_gram: MicroMinotari, message: String, + payment_id: PaymentId, transaction_broadcast_join_handles: &mut FuturesUnordered< JoinHandle>>, >, - ) -> Result<(TxId, HashOutput), TransactionServiceError> { - if destination.network() != self.resources.wallet_identity.network { + ) -> Result { + if destination.network() != self.resources.one_sided_tari_address.network() { return Err(TransactionServiceError::InvalidNetwork); } - if self.resources.wallet_identity.node_identity.public_key() == destination.public_key() { - warn!(target: LOG_TARGET, "One-sided spend-to-self transactions not supported"); - return Err(TransactionServiceError::OneSidedTransactionError( - "One-sided-to-stealth-address spend-to-self transactions not supported".to_string(), - )); - } - - let (nonce_private_key, nonce_public_key) = PublicKey::random_keypair(&mut OsRng); - - let dest_pubkey = destination.public_key().clone(); - let c = diffie_hellman_stealth_domain_hasher(&nonce_private_key, &dest_pubkey); - - let script_spending_key = stealth_address_script_spending_key(&c, &dest_pubkey); self.send_one_sided_or_stealth( destination, @@ -1950,7 +2502,8 @@ where fee_per_gram, message, transaction_broadcast_join_handles, - stealth_payment_script(&nonce_public_key, &script_spending_key), + None, // The stealth address for the script will be calculated in the next step + payment_id, ) .await } @@ -2006,7 +2559,7 @@ where if let Ok(ctx) = completed_tx { // Check that it is from the same person - if ctx.destination_address.public_key() != &source_pubkey { + if ctx.destination_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } if !check_cooldown(ctx.last_send_timestamp) { @@ -2053,7 +2606,7 @@ where if let Ok(otx) = cancelled_outbound_tx { // Check that it is from the same person - if otx.destination_address.public_key() != &source_pubkey { + if otx.destination_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } if !check_cooldown(otx.last_send_timestamp) { @@ -2219,7 +2772,7 @@ where // Check that an inbound transaction exists to be cancelled and that the Source Public key for that transaction // is the same as the cancellation message if let Ok(inbound_tx) = self.db.get_pending_inbound_transaction(tx_id) { - if inbound_tx.source_address.public_key() == &source_pubkey { + if inbound_tx.source_address.comms_public_key() == &source_pubkey { self.cancel_pending_transaction(tx_id).await?; } else { trace!( @@ -2338,7 +2891,7 @@ where if let Ok(Some(any_tx)) = self.db.get_any_cancelled_transaction(data.tx_id) { let tx = CompletedTransaction::from(any_tx); - if tx.source_address.public_key() != &source_pubkey { + if tx.source_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } trace!( @@ -2358,7 +2911,7 @@ where // Check if this transaction has already been received. if let Ok(inbound_tx) = self.db.get_pending_inbound_transaction(data.tx_id) { // Check that it is from the same person - if inbound_tx.source_address.public_key() != &source_pubkey { + if inbound_tx.source_address.comms_public_key() != &source_pubkey { return Err(TransactionServiceError::InvalidSourcePublicKey); } // Check if the last reply is beyond the resend cooldown @@ -2415,8 +2968,13 @@ where .insert(data.tx_id, tx_finalized_sender); self.receiver_transaction_cancellation_senders .insert(data.tx_id, cancellation_sender); - // we are making the assumption that because we received this transaction, its on the same network as us. - let source_address = TariAddress::new(source_pubkey, self.resources.wallet_identity.network); + // We are recieving an interactive transaction from someone on our network, so we assume its features are + // interactive and its the same network + let source_address = TariAddress::new_single_address( + source_pubkey, + self.resources.interactive_tari_address.network(), + TariAddressFeatures::INTERACTIVE, + ); let protocol = TransactionReceiveProtocol::new( data.tx_id, source_address, @@ -2470,8 +3028,13 @@ where ) })?; - // assuming since we talked to the node, it has the same identity than - let source_address = TariAddress::new(source_pubkey, self.resources.wallet_identity.network); + // assuming since we talked to the node, that it has an interactive address, we dont know what the view key is + // but we know its interactive, so make the view key 0, and the spend key the source public key. + let source_address = TariAddress::new_single_address( + source_pubkey, + self.resources.interactive_tari_address.network(), + TariAddressFeatures::INTERACTIVE, + ); let sender = match self.finalized_transaction_senders.get_mut(&tx_id) { None => { // First check if perhaps we know about this inbound transaction but it was cancelled @@ -2930,21 +3493,27 @@ where current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, ) -> Result { let tx_id = if let Some(id) = tx_id { id } else { TxId::new_random() }; self.db.add_utxo_import_transaction_with_status( tx_id, value, source_address, - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), message, import_status.clone(), current_height, mined_timestamp, scanned_output, + payment_id, )?; let transaction_event = match import_status { - ImportStatus::Imported => TransactionEvent::TransactionImported(tx_id), + ImportStatus::Imported => TransactionEvent::DetectedTransactionUnconfirmed { + tx_id, + num_confirmations: 0, + is_valid: true, + }, ImportStatus::OneSidedUnconfirmed | ImportStatus::CoinbaseUnconfirmed => { TransactionEvent::DetectedTransactionUnconfirmed { tx_id, @@ -3030,8 +3599,8 @@ where transaction_broadcast_join_handles, CompletedTransaction::new( tx_id, - self.resources.wallet_identity.address.clone(), - self.resources.wallet_identity.address.clone(), + self.resources.interactive_tari_address.clone(), + self.resources.interactive_tari_address.clone(), amount, fee, tx, @@ -3041,6 +3610,7 @@ where TransactionDirection::Inbound, None, None, + None, )?, ) .await?; @@ -3071,7 +3641,9 @@ pub struct TransactionServiceResources, pub consensus_manager: ConsensusManager, pub factories: CryptoFactories, pub config: TransactionServiceConfig, @@ -3091,52 +3663,3 @@ pub struct TransactionSendResult { pub tx_id: TxId, pub transaction_status: TransactionStatus, } - -#[cfg(test)] -mod tests { - use tari_crypto::ristretto::RistrettoSecretKey; - use tari_script::{stealth_payment_script, Opcode}; - - use super::*; - - #[test] - fn test_stealth_addresses() { - // recipient's keys - let (a, big_a) = PublicKey::random_keypair(&mut OsRng); - let (_b, big_b) = PublicKey::random_keypair(&mut OsRng); - - // Sender generates a random nonce key-pair: R=r⋅G - let (r, big_r) = PublicKey::random_keypair(&mut OsRng); - - // Sender calculates a ECDH shared secret: c=H(r⋅a⋅G)=H(a⋅R)=H(r⋅A), - // where H(⋅) is a cryptographic hash function - let c = diffie_hellman_stealth_domain_hasher(&r, &big_a); - - // using spending key `Ks=c⋅G+B` as the last public key in the one-sided payment script - let sender_spending_key = stealth_address_script_spending_key(&c, &big_b); - - let script = stealth_payment_script(&big_r, &sender_spending_key); - - // ---------------------------------------------------------------------------- - // imitating the receiving end, scanning and extraction - - // Extracting the nonce R and a spending key from the script - if let [Opcode::PushPubKey(big_r), Opcode::Drop, Opcode::PushPubKey(provided_spending_key)] = script.as_slice() - { - // calculating Ks with the provided R nonce from the script - let c = diffie_hellman_stealth_domain_hasher(&a, big_r); - - // computing a spending key `Ks=(c+b)G` for comparison - let receiver_spending_key = stealth_address_script_spending_key(&c, &big_b); - - // computing a scanning key `Ks=cG+B` for comparison - let scanning_key = - PublicKey::from_secret_key(&RistrettoSecretKey::from_uniform_bytes(c.as_ref()).unwrap()) + big_b; - - assert_eq!(provided_spending_key.as_ref(), &sender_spending_key); - assert_eq!(receiver_spending_key, sender_spending_key); - assert_eq!(scanning_key, sender_spending_key); - assert_eq!(scanning_key, receiver_spending_key); - } - } -} diff --git a/base_layer/wallet/src/transaction_service/storage/database.rs b/base_layer/wallet/src/transaction_service/storage/database.rs index 7e22f7133f..08e9f16d1b 100644 --- a/base_layer/wallet/src/transaction_service/storage/database.rs +++ b/base_layer/wallet/src/transaction_service/storage/database.rs @@ -37,7 +37,7 @@ use tari_common_types::{ }; use tari_core::transactions::{ tari_amount::MicroMinotari, - transaction_components::{Transaction, TransactionOutput}, + transaction_components::{encrypted_data::PaymentId, Transaction, TransactionOutput}, }; use crate::transaction_service::{ @@ -83,6 +83,12 @@ pub trait TransactionBackend: Send + Sync + Clone { fn write(&self, op: WriteOperation) -> Result, TransactionStorageError>; /// Check if a transaction exists in any of the collections fn transaction_exists(&self, tx_id: TxId) -> Result; + /// Update a previously completed transaction with new data + fn update_completed_transaction( + &self, + tx_id: TxId, + transaction: CompletedTransaction, + ) -> Result<(), TransactionStorageError>; /// Complete outbound transaction, this operation must delete the `OutboundTransaction` with the provided /// `TxId` and insert the provided `CompletedTransaction` into `CompletedTransactions`. fn complete_outbound_transaction( @@ -439,6 +445,14 @@ where T: TransactionBackend + 'static Ok(*t) } + pub fn update_completed_transaction( + &self, + tx_id: TxId, + transaction: CompletedTransaction, + ) -> Result<(), TransactionStorageError> { + self.db.update_completed_transaction(tx_id, transaction) + } + pub fn get_imported_transactions(&self) -> Result, TransactionStorageError> { let t = self.db.fetch_imported_transactions()?; Ok(t) @@ -681,7 +695,12 @@ where T: TransactionBackend + 'static current_height: Option, mined_timestamp: Option, scanned_output: TransactionOutput, + payment_id: PaymentId, ) -> Result<(), TransactionStorageError> { + let payment_id = match payment_id { + PaymentId::Empty => None, + v => Some(v), + }; let transaction = CompletedTransaction::new( tx_id, source_address, @@ -701,6 +720,7 @@ where T: TransactionBackend + 'static TransactionDirection::Inbound, current_height, mined_timestamp, + payment_id, )?; self.db diff --git a/base_layer/wallet/src/transaction_service/storage/models.rs b/base_layer/wallet/src/transaction_service/storage/models.rs index 9830c2a8fa..fa81435234 100644 --- a/base_layer/wallet/src/transaction_service/storage/models.rs +++ b/base_layer/wallet/src/transaction_service/storage/models.rs @@ -34,7 +34,7 @@ use tari_common_types::{ }; use tari_core::transactions::{ tari_amount::MicroMinotari, - transaction_components::Transaction, + transaction_components::{encrypted_data::PaymentId, Transaction}, ReceiverTransactionProtocol, SenderTransactionProtocol, }; @@ -147,6 +147,7 @@ pub struct CompletedTransaction { pub mined_height: Option, pub mined_in_block: Option, pub mined_timestamp: Option, + pub payment_id: Option, } impl CompletedTransaction { @@ -163,6 +164,7 @@ impl CompletedTransaction { direction: TransactionDirection, mined_height: Option, mined_timestamp: Option, + payment_id: Option, ) -> Result { if status == TransactionStatus::Coinbase { return Err(TransactionStorageError::CoinbaseNotSupported); @@ -191,6 +193,7 @@ impl CompletedTransaction { mined_height, mined_in_block: None, mined_timestamp, + payment_id, }) } } @@ -270,6 +273,7 @@ impl From for CompletedTransaction { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, } } } @@ -299,6 +303,7 @@ impl From for CompletedTransaction { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, } } } 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 010b35d342..9e4a7d28a7 100644 --- a/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/transaction_service/storage/sqlite_db.rs @@ -28,7 +28,7 @@ use std::{ use chacha20poly1305::XChaCha20Poly1305; use chrono::{NaiveDateTime, Utc}; -use diesel::{prelude::*, result::Error as DieselError, SqliteConnection}; +use diesel::{prelude::*, result::Error as DieselError}; use log::*; use tari_common_sqlite::{sqlite_connection_pool::PooledDbConnection, util::diesel_ext::ExpectedRowsExtension}; use tari_common_types::{ @@ -43,7 +43,7 @@ use tari_common_types::{ }, types::{BlockHash, PrivateKey, PublicKey, Signature}, }; -use tari_core::transactions::tari_amount::MicroMinotari; +use tari_core::transactions::{tari_amount::MicroMinotari, transaction_components::encrypted_data::PaymentId}; use tari_utilities::{hex::Hex, ByteArray, Hidden}; use thiserror::Error; use tokio::time::Instant; @@ -66,6 +66,7 @@ use crate::{ }, }, }; + const LOG_TARGET: &str = "wallet::transaction_service::database::wallet"; /// A Sqlite backend for the Transaction Service. The Backend is accessed via a connection pool to the Sqlite file. @@ -458,6 +459,32 @@ impl TransactionBackend for TransactionServiceSqliteDatabase { Ok(result) } + fn update_completed_transaction( + &self, + tx_id: TxId, + transaction: CompletedTransaction, + ) -> Result<(), TransactionStorageError> { + let start = Instant::now(); + let mut conn = self.database_connection.get_pooled_connection()?; + let acquire_lock = start.elapsed(); + let tx = CompletedTransactionSql::find_by_cancelled(tx_id, false, &mut conn)?; + + tx.delete(&mut conn)?; + let cipher = acquire_read_lock!(self.cipher); + let completed_tx = CompletedTransactionSql::try_from(transaction, &cipher)?; + completed_tx.commit(&mut conn)?; + if start.elapsed().as_millis() > 0 { + trace!( + target: LOG_TARGET, + "sqlite profile - update_completed_transaction: lock {} + db_op {} = {} ms", + acquire_lock.as_millis(), + (start.elapsed() - acquire_lock).as_millis(), + start.elapsed().as_millis() + ); + } + Ok(()) + } + fn get_pending_transaction_counterparty_address_by_tx_id( &self, tx_id: TxId, @@ -1320,7 +1347,7 @@ impl InboundTransactionSql { .map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?; let i = Self { tx_id: i.tx_id.as_u64() as i64, - source_address: i.source_address.to_bytes().to_vec(), + source_address: i.source_address.to_vec(), amount: u64::from(i.amount) as i64, receiver_protocol: receiver_protocol_bytes.to_vec(), message: i.message, @@ -1568,7 +1595,7 @@ impl OutboundTransactionSql { .map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?; let outbound_tx = Self { tx_id: o.tx_id.as_u64() as i64, - destination_address: o.destination_address.to_bytes().to_vec(), + destination_address: o.destination_address.to_vec(), amount: u64::from(o.amount) as i64, fee: u64::from(o.fee) as i64, sender_protocol: sender_protocol_bytes.to_vec(), @@ -1672,6 +1699,7 @@ pub struct CompletedTransactionSql { mined_timestamp: Option, transaction_signature_nonce: Vec, transaction_signature_key: Vec, + payment_id: Option>, } impl CompletedTransactionSql { @@ -1936,11 +1964,14 @@ impl CompletedTransactionSql { fn try_from(c: CompletedTransaction, cipher: &XChaCha20Poly1305) -> Result { let transaction_bytes = bincode::serialize(&c.transaction).map_err(|e| TransactionStorageError::BincodeSerialize(e.to_string()))?; - + let payment_id = match c.payment_id { + Some(id) => Some(id.as_bytes()), + None => Some(Vec::new()), + }; let output = Self { tx_id: c.tx_id.as_u64() as i64, - source_address: c.source_address.to_bytes().to_vec(), - destination_address: c.destination_address.to_bytes().to_vec(), + source_address: c.source_address.to_vec(), + destination_address: c.destination_address.to_vec(), amount: u64::from(c.amount) as i64, fee: u64::from(c.fee) as i64, transaction_protocol: transaction_bytes.to_vec(), @@ -1957,6 +1988,7 @@ impl CompletedTransactionSql { mined_timestamp: c.mined_timestamp, transaction_signature_nonce: c.transaction_signature.get_public_nonce().to_vec(), transaction_signature_key: c.transaction_signature.get_signature().to_vec(), + payment_id, }; output.encrypt(cipher).map_err(TransactionStorageError::AeadError) @@ -2030,6 +2062,18 @@ impl CompletedTransaction { }, None => None, }; + let payment_id = match c.payment_id { + Some(bytes) => PaymentId::from_bytes(&bytes).map_err(|_| { + error!( + target: LOG_TARGET, + "Could not create payment id from stored bytes" + ); + CompletedTransactionConversionError::BincodeDeserialize( + "payment id could not be converted from bytes".to_string(), + ) + })?, + None => PaymentId::Empty, + }; let output = Self { tx_id: (c.tx_id as u64).into(), @@ -2054,6 +2098,7 @@ impl CompletedTransaction { mined_height: c.mined_height.map(|ic| ic as u64), mined_in_block, mined_timestamp: c.mined_timestamp, + payment_id: Some(payment_id), }; // zeroize sensitive data @@ -2155,7 +2200,7 @@ impl UnconfirmedTransactionInfoSql { #[cfg(test)] mod test { - use std::{default::Default, mem::size_of, time::Duration}; + use std::{mem::size_of, time::Duration}; use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305}; use chrono::Utc; @@ -2174,7 +2219,7 @@ mod test { key_manager::create_memory_db_key_manager, tari_amount::MicroMinotari, test_helpers::{create_wallet_output_with_data, TestParams}, - transaction_components::{OutputFeatures, Transaction}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, Transaction}, transaction_protocol::sender::TransactionSenderMessage, ReceiverTransactionProtocol, SenderTransactionProtocol, @@ -2204,7 +2249,7 @@ mod test { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_crud() { - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let consensus_constants = create_consensus_constants(0); let db_name = format!("{}.sqlite3", string(8).as_str()); let temp_dir = tempdir().unwrap(); @@ -2271,12 +2316,12 @@ mod test { script!(Nop), inputs!(change.script_key_pk), change.script_key_id, - change.spend_key_id, + change.commitment_mask_key_id, Default::default(), ); let mut stp = builder.build().await.unwrap(); - let address = TariAddress::new( + let address = TariAddress::new_single_address_with_interactive_only( PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2294,7 +2339,7 @@ mod test { send_count: 0, last_send_timestamp: None, }; - let address = TariAddress::new( + let address = TariAddress::new_single_address_with_interactive_only( PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2361,7 +2406,8 @@ mod test { &consensus_constants, ) .await; - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2378,7 +2424,8 @@ mod test { send_count: 0, last_send_timestamp: None, }; - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2431,11 +2478,13 @@ mod test { PrivateKey::random(&mut OsRng), PrivateKey::random(&mut OsRng), ); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2458,12 +2507,15 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2486,6 +2538,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; CompletedTransactionSql::try_from(completed_tx1.clone(), &cipher) @@ -2634,7 +2687,8 @@ mod test { let key_ga = Key::from_slice(&key); let cipher = XChaCha20Poly1305::new(key_ga); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2660,7 +2714,8 @@ mod test { let decrypted_inbound_tx = InboundTransaction::try_from(db_inbound_tx, &cipher).unwrap(); assert_eq!(inbound_tx, decrypted_inbound_tx); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2688,11 +2743,13 @@ mod test { let decrypted_outbound_tx = OutboundTransaction::try_from(db_outbound_tx, &cipher).unwrap(); assert_eq!(outbound_tx, decrypted_outbound_tx); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2721,6 +2778,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: Some(PaymentId::Empty), }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx.clone(), &cipher).unwrap(); @@ -2773,7 +2831,8 @@ mod test { }) .expect("Migrations failed"); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2794,7 +2853,8 @@ mod test { inbound_tx_sql.commit(&mut conn).unwrap(); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2816,11 +2876,13 @@ mod test { outbound_tx_sql.commit(&mut conn).unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2849,6 +2911,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx, &cipher).unwrap(); @@ -2956,11 +3019,13 @@ mod test { 10 => (None, TransactionStatus::MinedConfirmed), _ => (None, TransactionStatus::Completed), }; - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2989,6 +3054,7 @@ mod test { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let completed_tx_sql = CompletedTransactionSql::try_from(completed_tx.clone(), &cipher).unwrap(); diff --git a/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs index c4799fc81c..4f0579491f 100644 --- a/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs +++ b/base_layer/wallet/src/transaction_service/tasks/check_faux_transaction_status.rs @@ -50,10 +50,7 @@ pub async fn check_detected_transactions ) { // Reorged faux transactions cannot be detected by excess signature, thus use last known confirmed transaction // height or current tip height with safety margin to determine if these should be returned - let last_mined_transaction = match db.fetch_last_mined_transaction() { - Ok(tx) => tx, - Err(_) => None, - }; + let last_mined_transaction = db.fetch_last_mined_transaction().unwrap_or_default(); let height_with_margin = tip_height.saturating_sub(SAFETY_HEIGHT_MARGIN); let check_height = if let Some(tx) = last_mined_transaction { @@ -110,6 +107,11 @@ pub async fn check_detected_transactions "Checking {} detected transaction statuses", all_detected_transactions.len() ); + trace!( + target: LOG_TARGET, + "Checking transaction statuses for {:?} ", + all_detected_transactions.iter().map(|tx| tx.tx_id).collect::>() + ); for tx in all_detected_transactions { let output_info_for_tx_id = match output_manager.get_output_info_for_tx_id(tx.tx_id).await { Ok(s) => s, @@ -118,6 +120,11 @@ pub async fn check_detected_transactions return; }, }; + trace!( + target: LOG_TARGET, + "TxId: {}, {:?} ", + tx.tx_id, output_info_for_tx_id + ); // Its safe to assume that statuses should be the same as they are all in the same transaction and they cannot // be different. let output_status = output_info_for_tx_id.statuses[0]; diff --git a/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs b/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs index 462ba452e1..6cf5b7a34c 100644 --- a/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs +++ b/base_layer/wallet/src/transaction_service/tasks/send_transaction_reply.rs @@ -67,7 +67,7 @@ pub async fn send_transaction_reply( TransactionRoutingMechanism::StoreAndForwardOnly => { send_transaction_reply_store_and_forward( inbound_transaction.tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.public_spend_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -97,7 +97,7 @@ pub async fn send_transaction_reply_direct( .map_err(TransactionServiceError::ServiceError)?; match outbound_message_service .send_direct_unencrypted( - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), OutboundDomainMessage::new(&TariMessageType::ReceiverPartialTransactionReply, proto_message.clone()), "wallet transaction reply".to_string(), ) @@ -108,7 +108,7 @@ pub async fn send_transaction_reply_direct( if wait_on_dial( send_states, tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), "Transaction Reply", direct_send_timeout, ) @@ -127,7 +127,7 @@ pub async fn send_transaction_reply_direct( if transaction_routing_mechanism == TransactionRoutingMechanism::DirectAndStoreAndForward { store_and_forward_send_result = send_transaction_reply_store_and_forward( tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -142,7 +142,7 @@ pub async fn send_transaction_reply_direct( if transaction_routing_mechanism == TransactionRoutingMechanism::DirectAndStoreAndForward { store_and_forward_send_result = send_transaction_reply_store_and_forward( tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -153,7 +153,7 @@ pub async fn send_transaction_reply_direct( if transaction_routing_mechanism == TransactionRoutingMechanism::DirectAndStoreAndForward { store_and_forward_send_result = send_transaction_reply_store_and_forward( tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), proto_message.clone(), &mut outbound_message_service, ) @@ -169,7 +169,7 @@ pub async fn send_transaction_reply_direct( direct_send_result = wait_on_dial( send_states, tx_id, - inbound_transaction.source_address.public_key().clone(), + inbound_transaction.source_address.comms_public_key().clone(), "Transaction Reply", direct_send_timeout, ) diff --git a/base_layer/wallet/src/transaction_service/utc.rs b/base_layer/wallet/src/transaction_service/utc.rs index 8b172c03ba..c9d445b876 100644 --- a/base_layer/wallet/src/transaction_service/utc.rs +++ b/base_layer/wallet/src/transaction_service/utc.rs @@ -13,14 +13,11 @@ pub struct NegativeDurationError { } /// The function compared two non-leap UTC timestamps. -/// 1. `chrono` uses `SystemTime` and will never -/// produce leap-seconds. -/// 2. `chrono` supports leap seconds that can be read from -/// the string format (as `60` second), because it's required by the standard (ISO 8601). -/// 3. Leap-second handled automatically by NTP and we -/// could ignore it as soon as `chrono` doesn't handle -/// them accurately. No guarantees and only the one -/// second handeled. +/// 1. `chrono` uses `SystemTime` and will never produce leap-seconds. +/// 2. `chrono` supports leap seconds that can be read from the string format (as `60` second), because it's required by +/// the standard (ISO 8601). +/// 3. Leap-second handled automatically by NTP and we could ignore it as soon as `chrono` doesn't handle them +/// accurately. No guarantees and only the one second handeled. pub fn utc_duration_since(since: &NaiveDateTime) -> Result { let now_ms = Utc::now().naive_utc().timestamp_millis(); let since_ms = since.timestamp_millis(); diff --git a/base_layer/wallet/src/util/wallet_identity.rs b/base_layer/wallet/src/util/wallet_identity.rs index 37340c417c..00fe954fed 100644 --- a/base_layer/wallet/src/util/wallet_identity.rs +++ b/base_layer/wallet/src/util/wallet_identity.rs @@ -30,30 +30,39 @@ use tari_core::transactions::key_manager::TariKeyId; #[derive(Clone, Debug)] pub struct WalletIdentity { pub node_identity: Arc, - pub network: Network, - pub address: TariAddress, + pub address_interactive: TariAddress, + pub address_one_sided: TariAddress, pub wallet_node_key_id: TariKeyId, } impl WalletIdentity { - pub fn new(node_identity: Arc, network: Network) -> Self { - let address = TariAddress::new(node_identity.public_key().clone(), network); + pub fn new( + node_identity: Arc, + address_interactive: TariAddress, + address_one_sided: TariAddress, + ) -> Self { let wallet_node_key_id = TariKeyId::Imported { key: node_identity.public_key().clone(), }; WalletIdentity { node_identity, - network, - address, + address_interactive, + address_one_sided, wallet_node_key_id, } } + + pub fn network(&self) -> Network { + self.address_interactive.network() + } } impl Display for WalletIdentity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "{}", self.node_identity)?; - writeln!(f, "Network: {:?}", self.network)?; + writeln!(f, "Tari Address interactive: {}", self.address_interactive)?; + writeln!(f, "Tari Address one-sided: {}", self.address_one_sided)?; + writeln!(f, "Network: {:?}", self.address_interactive.network())?; Ok(()) } } diff --git a/base_layer/wallet/src/utxo_scanner_service/initializer.rs b/base_layer/wallet/src/utxo_scanner_service/initializer.rs index 14df2190aa..88e20ebcb3 100644 --- a/base_layer/wallet/src/utxo_scanner_service/initializer.rs +++ b/base_layer/wallet/src/utxo_scanner_service/initializer.rs @@ -20,10 +20,14 @@ // 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::marker::PhantomData; + use futures::future; use log::*; +use tari_common::configuration::Network; +use tari_common_types::tari_address::{TariAddress, TariAddressFeatures}; use tari_comms::connectivity::ConnectivityRequester; -use tari_core::transactions::CryptoFactories; +use tari_core::transactions::{key_manager::TransactionKeyManagerInterface, CryptoFactories}; use tari_service_framework::{async_trait, ServiceInitializationError, ServiceInitializer, ServiceInitializerContext}; use tokio::sync::broadcast; @@ -33,7 +37,7 @@ use crate::{ output_manager_service::handle::OutputManagerHandle, storage::database::{WalletBackend, WalletDatabase}, transaction_service::handle::TransactionServiceHandle, - util::{wallet_identity::WalletIdentity, watch::Watch}, + util::watch::Watch, utxo_scanner_service::{ handle::UtxoScannerHandle, service::UtxoScannerService, @@ -43,27 +47,31 @@ use crate::{ const LOG_TARGET: &str = "wallet::utxo_scanner_service::initializer"; -pub struct UtxoScannerServiceInitializer { +pub struct UtxoScannerServiceInitializer { backend: Option>, factories: CryptoFactories, - wallet_identity: WalletIdentity, + network: Network, + phantom: PhantomData, } -impl UtxoScannerServiceInitializer +impl UtxoScannerServiceInitializer where T: WalletBackend + 'static { - pub fn new(backend: WalletDatabase, factories: CryptoFactories, wallet_identity: WalletIdentity) -> Self { + pub fn new(backend: WalletDatabase, factories: CryptoFactories, network: Network) -> Self { Self { backend: Some(backend), factories, - wallet_identity, + network, + phantom: PhantomData, } } } #[async_trait] -impl ServiceInitializer for UtxoScannerServiceInitializer -where T: WalletBackend + 'static +impl ServiceInitializer for UtxoScannerServiceInitializer +where + T: WalletBackend + 'static, + TKeyManagerInterface: TransactionKeyManagerInterface, { async fn initialize(&mut self, context: ServiceInitializerContext) -> Result<(), ServiceInitializationError> { trace!(target: LOG_TARGET, "Utxo scanner initialization"); @@ -86,7 +94,7 @@ where T: WalletBackend + 'static .take() .expect("Cannot start Utxo scanner service without setting a storage backend"); let factories = self.factories.clone(); - let wallet_identity = self.wallet_identity.clone(); + let network = self.network; context.spawn_when_ready(move |handles| async move { let transaction_service = handles.expect_handle::(); @@ -94,18 +102,34 @@ where T: WalletBackend + 'static let comms_connectivity = handles.expect_handle::(); let wallet_connectivity = handles.expect_handle::(); let base_node_service_handle = handles.expect_handle::(); + let key_manager = handles.expect_handle::(); + + let view_key = key_manager + .get_view_key() + .await + .expect("Could not initialize UTXO scanner Service"); + let spend_key = key_manager + .get_spend_key() + .await + .expect("Could not initialize UTXO scanner Service"); + let one_sided_tari_address = TariAddress::new_dual_address( + view_key.pub_key, + spend_key.pub_key, + network, + TariAddressFeatures::create_one_sided_only(), + ); let scanning_service = UtxoScannerService::::builder() .with_peers(vec![]) .with_retry_limit(2) .with_mode(UtxoScannerMode::Scanning) - .build_with_resources( + .build_with_resources::( backend, comms_connectivity, wallet_connectivity.clone(), output_manager_service, transaction_service, - wallet_identity, + one_sided_tari_address, factories, handles.get_shutdown_signal(), event_sender, @@ -113,6 +137,7 @@ where T: WalletBackend + 'static one_sided_message_watch_receiver, recovery_message_watch_receiver, ) + .await .run(); futures::pin_mut!(scanning_service); diff --git a/base_layer/wallet/src/utxo_scanner_service/service.rs b/base_layer/wallet/src/utxo_scanner_service/service.rs index e70c168884..4bfc99d0fe 100644 --- a/base_layer/wallet/src/utxo_scanner_service/service.rs +++ b/base_layer/wallet/src/utxo_scanner_service/service.rs @@ -23,7 +23,7 @@ use chrono::NaiveDateTime; use futures::FutureExt; use log::*; -use tari_common_types::types::HashOutput; +use tari_common_types::{tari_address::TariAddress, types::HashOutput}; use tari_comms::{connectivity::ConnectivityRequester, peer_manager::Peer, types::CommsPublicKey}; use tari_core::transactions::{tari_amount::MicroMinotari, CryptoFactories}; use tari_shutdown::{Shutdown, ShutdownSignal}; @@ -39,7 +39,6 @@ use crate::{ output_manager_service::handle::OutputManagerHandle, storage::database::{WalletBackend, WalletDatabase}, transaction_service::handle::TransactionServiceHandle, - util::wallet_identity::WalletIdentity, utxo_scanner_service::{ handle::UtxoScannerEvent, utxo_scanner_task::UtxoScannerTask, @@ -194,7 +193,7 @@ pub struct UtxoScannerResources { pub current_base_node_watcher: watch::Receiver>, pub output_manager_service: OutputManagerHandle, pub transaction_service: TransactionServiceHandle, - pub wallet_identity: WalletIdentity, + pub one_sided_tari_address: TariAddress, pub factories: CryptoFactories, pub recovery_message: String, pub one_sided_payment_message: String, diff --git a/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs b/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs index ebd3e6f544..606cc25f82 100644 --- a/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs +++ b/base_layer/wallet/src/utxo_scanner_service/utxo_scanner_task.rs @@ -38,6 +38,7 @@ use tari_comms::{ protocol::rpc::RpcClientLease, traits::OrOptional, types::CommsPublicKey, + Minimized, PeerConnection, }; use tari_core::{ @@ -193,7 +194,7 @@ where }); if let Ok(Some(connection)) = self.resources.comms_connectivity.get_connection(peer.clone()).await { - if connection.clone().disconnect().await.is_ok() { + if connection.clone().disconnect(Minimized::No).await.is_ok() { debug!(target: LOG_TARGET, "Disconnected base node peer {}", peer); } } @@ -225,7 +226,6 @@ where )); let timer = Instant::now(); - loop { let tip_header = self.get_chain_tip_header(&mut client).await?; let tip_header_hash = tip_header.hash(); @@ -562,6 +562,7 @@ where height: u64, ) -> Result, UtxoScannerError> { let mut found_outputs: Vec<(WalletOutput, String, ImportStatus, TxId, TransactionOutput)> = Vec::new(); + let start = Instant::now(); found_outputs.append( &mut self .resources @@ -585,6 +586,8 @@ where }) .collect::, _>>()?, ); + let scanned_time = start.elapsed(); + let start = Instant::now(); found_outputs.append( &mut self @@ -612,6 +615,13 @@ where }) .collect::, _>>()?, ); + let one_sided_time = start.elapsed(); + trace!( + target: LOG_TARGET, + "Scanned for outputs: outputs took {} ms , one-sided took {} ms", + scanned_time.as_millis(), + one_sided_time.as_millis(), + ); Ok(found_outputs) } @@ -626,7 +636,7 @@ where for (wo, message, import_status, tx_id, to) in utxos { let source_address = if wo.features.is_coinbase() { // It's a coinbase, so we know we mined it (we do mining with cold wallets). - self.resources.wallet_identity.address.clone() + self.resources.one_sided_tari_address.clone() } else { // Because we do not know the source public key we are making it the default key of zeroes to make it // clear this value is a placeholder. @@ -714,6 +724,7 @@ where Some(current_height), Some(mined_timestamp), scanned_output, + wallet_output.payment_id, ) .await?; diff --git a/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs b/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs index 13a8509421..c249a38b25 100644 --- a/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs +++ b/base_layer/wallet/src/utxo_scanner_service/uxto_scanner_service_builder.rs @@ -20,8 +20,10 @@ // 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::tari_address::TariAddress; use tari_comms::{connectivity::ConnectivityRequester, types::CommsPublicKey}; -use tari_core::transactions::CryptoFactories; +use tari_core::transactions::{key_manager::TransactionKeyManagerInterface, CryptoFactories}; +use tari_key_manager::key_manager_service::KeyManagerServiceError; use tari_shutdown::ShutdownSignal; use tokio::sync::{broadcast, watch}; @@ -34,7 +36,6 @@ use crate::{ sqlite_db::wallet::WalletSqliteDatabase, }, transaction_service::handle::TransactionServiceHandle, - util::wallet_identity::WalletIdentity, utxo_scanner_service::{ handle::UtxoScannerEvent, service::{UtxoScannerResources, UtxoScannerService}, @@ -98,12 +99,12 @@ impl UtxoScannerServiceBuilder { self } - pub fn build_with_wallet( + pub async fn build_with_wallet( &mut self, wallet: &WalletSqlite, shutdown_signal: ShutdownSignal, - ) -> UtxoScannerService { - let wallet_identity = WalletIdentity::new(wallet.comms.node_identity(), wallet.network.as_network()); + ) -> Result, KeyManagerServiceError> { + let one_sided_tari_address = wallet.get_wallet_one_sided_address().await?; let resources = UtxoScannerResources { db: wallet.db.clone(), comms_connectivity: wallet.comms.connectivity(), @@ -111,7 +112,7 @@ impl UtxoScannerServiceBuilder { current_base_node_watcher: wallet.wallet_connectivity.get_current_base_node_watcher(), output_manager_service: wallet.output_manager_service.clone(), transaction_service: wallet.transaction_service.clone(), - wallet_identity, + one_sided_tari_address, factories: wallet.factories.clone(), recovery_message: self.recovery_message.clone(), one_sided_payment_message: self.one_sided_message.clone(), @@ -119,7 +120,7 @@ impl UtxoScannerServiceBuilder { let (event_sender, _) = broadcast::channel(200); - UtxoScannerService::new( + Ok(UtxoScannerService::new( self.peers.drain(..).collect(), self.retry_limit, self.mode.clone().unwrap_or_default(), @@ -129,17 +130,21 @@ impl UtxoScannerServiceBuilder { wallet.base_node_service.clone(), wallet.utxo_scanner_service.get_one_sided_payment_message_watcher(), wallet.utxo_scanner_service.get_recovery_message_watcher(), - ) + )) } - pub fn build_with_resources( + pub async fn build_with_resources< + TBackend: WalletBackend + 'static, + TWalletConnectivity: WalletConnectivityInterface, + TKeyManagerInterface: TransactionKeyManagerInterface, + >( &mut self, db: WalletDatabase, comms_connectivity: ConnectivityRequester, wallet_connectivity: TWalletConnectivity, output_manager_service: OutputManagerHandle, transaction_service: TransactionServiceHandle, - wallet_identity: WalletIdentity, + one_sided_tari_address: TariAddress, factories: CryptoFactories, shutdown_signal: ShutdownSignal, event_sender: broadcast::Sender, @@ -154,7 +159,7 @@ impl UtxoScannerServiceBuilder { wallet_connectivity, output_manager_service, transaction_service, - wallet_identity, + one_sided_tari_address, factories, recovery_message: self.recovery_message.clone(), one_sided_payment_message: self.one_sided_message.clone(), diff --git a/base_layer/wallet/src/wallet.rs b/base_layer/wallet/src/wallet.rs index 0c4452fd4e..74a04b1b04 100644 --- a/base_layer/wallet/src/wallet.rs +++ b/base_layer/wallet/src/wallet.rs @@ -29,9 +29,9 @@ use log::*; use rand::rngs::OsRng; use tari_common::configuration::bootstrap::ApplicationType; use tari_common_types::{ - tari_address::TariAddress, + tari_address::{TariAddress, TariAddressFeatures}, transaction::{ImportStatus, TxId}, - types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, SignatureWithDomain}, + types::{ComAndPubSignature, Commitment, PrivateKey, PublicKey, RangeProof, SignatureWithDomain}, wallet_types::WalletType, }; use tari_comms::{ @@ -54,9 +54,9 @@ use tari_core::{ consensus::{ConsensusManager, NetworkConsensus}, covenants::Covenant, transactions::{ - key_manager::{SecretTransactionKeyManagerInterface, TransactionKeyManagerInitializer}, + key_manager::{SecretTransactionKeyManagerInterface, TariKeyId, TransactionKeyManagerInitializer}, tari_amount::MicroMinotari, - transaction_components::{EncryptedData, OutputFeatures, UnblindedOutput}, + transaction_components::{encrypted_data::PaymentId, EncryptedData, OutputFeatures, UnblindedOutput}, CryptoFactories, }, }; @@ -64,7 +64,7 @@ use tari_crypto::{hash_domain, signatures::SchnorrSignatureError}; use tari_key_manager::{ cipher_seed::CipherSeed, key_manager::KeyManager, - key_manager_service::{storage::database::KeyManagerBackend, KeyDigest}, + key_manager_service::{storage::database::KeyManagerBackend, KeyDigest, KeyManagerBranch, KeyManagerServiceError}, mnemonic::{Mnemonic, MnemonicLanguage}, SeedWords, }; @@ -77,14 +77,14 @@ use tari_p2p::{ PeerSeedsConfig, TransportType, }; -use tari_script::{one_sided_payment_script, ExecutionStack, TariScript}; +use tari_script::{push_pubkey_script, ExecutionStack, TariScript}; use tari_service_framework::StackBuilder; use tari_shutdown::ShutdownSignal; use tari_utilities::{hex::Hex, ByteArray}; use crate::{ base_node_service::{handle::BaseNodeServiceHandle, BaseNodeServiceInitializer}, - config::{WalletConfig, KEY_MANAGER_COMMS_SECRET_KEY_BRANCH_KEY}, + config::WalletConfig, connectivity_service::{WalletConnectivityHandle, WalletConnectivityInitializer, WalletConnectivityInterface}, error::{WalletError, WalletStorageError}, output_manager_service::{ @@ -136,6 +136,7 @@ pub struct Wallet { pub db: WalletDatabase, pub output_db: OutputManagerDatabase, pub factories: CryptoFactories, + wallet_type: WalletType, _u: PhantomData, _v: PhantomData, _w: PhantomData, @@ -165,8 +166,10 @@ where key_manager_backend: TKeyManagerBackend, shutdown_signal: ShutdownSignal, master_seed: CipherSeed, - wallet_type: WalletType, + wallet_type: Option, + user_agent: String, ) -> Result { + let wallet_type = read_or_create_wallet_type(wallet_type, &wallet_database)?; let buf_size = cmp::max(WALLET_BUFFER_MIN_SIZE, config.buffer_size); let (publisher, subscription_factory) = pubsub_connector(buf_size); let peer_message_subscription_factory = Arc::new(subscription_factory); @@ -184,10 +187,10 @@ where config.transaction_service_config, config.buffer_size, ); - let wallet_identity = WalletIdentity::new(node_identity.clone(), config.network); let stack = StackBuilder::new(shutdown_signal) .add_initializer(P2pInitializer::new( config.p2p.clone(), + user_agent, peer_seeds, config.network, node_identity.clone(), @@ -198,19 +201,19 @@ where output_manager_backend.clone(), factories.clone(), config.network.into(), - wallet_identity.clone(), )) .add_initializer(TransactionKeyManagerInitializer::new( key_manager_backend, master_seed, factories.clone(), - wallet_type, + wallet_type.clone(), )) .add_initializer(TransactionServiceInitializer::::new( config.transaction_service_config, peer_message_subscription_factory.clone(), transaction_backend, - wallet_identity.clone(), + node_identity.clone(), + config.network, consensus_manager, factories.clone(), wallet_database.clone(), @@ -235,10 +238,10 @@ where wallet_database.clone(), )) .add_initializer(WalletConnectivityInitializer::new(config.base_node_service_config)) - .add_initializer(UtxoScannerServiceInitializer::new( + .add_initializer(UtxoScannerServiceInitializer::::new( wallet_database.clone(), factories.clone(), - wallet_identity.clone(), + config.network, )); // Check if we have update config. FFI wallets don't do this, the update on mobile is done differently. @@ -315,13 +318,18 @@ where } else { None }; - - persist_one_sided_payment_script_for_node_identity(&mut output_manager_handle, wallet_identity.clone()) - .await - .map_err(|e| { - error!(target: LOG_TARGET, "{:?}", e); - e - })?; + let spend_key = key_manager_handle.get_spend_key().await?; + + persist_one_sided_payment_script_for_node_identity( + &mut output_manager_handle, + &spend_key.pub_key, + spend_key.key_id, + ) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "{:?}", e); + e + })?; wallet_database.set_node_features(comms.node_identity().features())?; let identity_sig = comms.node_identity().identity_signature_read().as_ref().cloned(); @@ -352,8 +360,7 @@ where db: wallet_database, output_db: output_manager_database, factories, - #[cfg(feature = "test_harness")] - transaction_backend: transaction_backend_handle, + wallet_type, _u: PhantomData, _v: PhantomData, _w: PhantomData, @@ -473,6 +480,42 @@ where } } + pub async fn get_wallet_interactive_address(&self) -> Result { + let view_key = self.key_manager_service.get_view_key().await?; + let comms_key = self.key_manager_service.get_comms_key().await?; + let features = match self.wallet_type { + WalletType::DerivedKeys => TariAddressFeatures::default(), + WalletType::Ledger(_) | WalletType::ProvidedKeys(_) => TariAddressFeatures::create_interactive_only(), + }; + Ok(TariAddress::new_dual_address( + view_key.pub_key, + comms_key.pub_key, + self.network.as_network(), + features, + )) + } + + pub async fn get_wallet_one_sided_address(&self) -> Result { + let view_key = self.key_manager_service.get_view_key().await?; + let spend_key = self.key_manager_service.get_spend_key().await?; + Ok(TariAddress::new_dual_address( + view_key.pub_key, + spend_key.pub_key, + self.network.as_network(), + TariAddressFeatures::create_one_sided_only(), + )) + } + + pub async fn get_wallet_id(&self) -> Result { + let address_interactive = self.get_wallet_interactive_address().await?; + let address_one_sided = self.get_wallet_one_sided_address().await?; + Ok(WalletIdentity::new( + self.comms.node_identity(), + address_interactive, + address_one_sided, + )) + } + pub fn get_software_updater(&self) -> Option { self.updater_service.as_ref().cloned() } @@ -496,8 +539,8 @@ where covenant: Covenant, encrypted_data: EncryptedData, minimum_value_promise: MicroMinotari, + range_proof: Option, ) -> Result { - // lets import the private keys let unblinded_output = UnblindedOutput::new_current_version( amount, spending_key.clone(), @@ -511,6 +554,7 @@ where covenant, encrypted_data, minimum_value_promise, + range_proof, ); self.import_unblinded_output_as_non_rewindable(unblinded_output, source_address, message) .await @@ -525,34 +569,34 @@ where source_address: TariAddress, message: String, ) -> Result { + let value = unblinded_output.value; + let wallet_output = unblinded_output + .to_wallet_output(&self.key_manager_service, PaymentId::Empty) + .await?; let tx_id = self .transaction_service .import_utxo_with_status( - unblinded_output.value, + value, source_address, message, ImportStatus::Imported, None, None, None, - unblinded_output - .clone() - .to_wallet_output(&self.key_manager_service) - .await? - .to_transaction_output(&self.key_manager_service) - .await?, + wallet_output.to_transaction_output(&self.key_manager_service).await?, + PaymentId::Empty, ) .await?; - let wallet_output = unblinded_output.to_wallet_output(&self.key_manager_service).await?; // As non-rewindable self.output_manager_service .add_unvalidated_output(tx_id, wallet_output.clone(), None) .await?; info!( target: LOG_TARGET, - "UTXO (Commitment: {}, value: {}) imported into wallet as 'ImportStatus::Imported' and is non-rewindable", + "UTXO (Commitment: {}, value: {}, txID: {}) imported into wallet as 'ImportStatus::Imported' and is non-rewindable", wallet_output.commitment(&self.key_manager_service).await?.to_hex(), wallet_output.value, + tx_id, ); Ok(tx_id) @@ -771,22 +815,22 @@ pub fn read_or_create_wallet_type( match (db_wallet_type, wallet_type) { (None, None) => { - panic!("Something is very wrong, no wallet type was found in the DB, or provided (on first run)") + // this is most likely an older wallet pre ledger support, lets put it in software + let wallet_type = WalletType::default(); + db.set_wallet_type(wallet_type.clone())?; + Ok(wallet_type) }, (None, Some(t)) => { - db.set_wallet_type(t)?; - Ok(t) + db.set_wallet_type(t.clone())?; + Ok(t.clone()) }, (Some(t), _) => Ok(t), } } pub fn derive_comms_secret_key(master_seed: &CipherSeed) -> Result { - let comms_key_manager = KeyManager::::from( - master_seed.clone(), - KEY_MANAGER_COMMS_SECRET_KEY_BRANCH_KEY.to_string(), - 0, - ); + let comms_key_manager = + KeyManager::::from(master_seed.clone(), KeyManagerBranch::Comms.get_branch_key(), 0); Ok(comms_key_manager.derive_key(0)?.key) } @@ -795,15 +839,16 @@ pub fn derive_comms_secret_key(master_seed: &CipherSeed) -> Result Result<(), WalletError> { - let script = one_sided_payment_script(wallet_identity.node_identity.public_key()); + let script = push_pubkey_script(spend_key); let known_script = KnownOneSidedPaymentScript { script_hash: script .as_hash::>() .map_err(|e| WalletError::OutputManagerError(OutputManagerError::ScriptError(e)))? .to_vec(), - script_key_id: wallet_identity.wallet_node_key_id.clone(), + script_key_id: spend_key_id, script, input: ExecutionStack::default(), script_lock_height: 0, diff --git a/base_layer/wallet/tests/key_manager_service_tests/service.rs b/base_layer/wallet/tests/key_manager_service_tests/service.rs index 8846d5c28c..18bbe2adf0 100644 --- a/base_layer/wallet/tests/key_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/key_manager_service_tests/service.rs @@ -66,7 +66,7 @@ async fn get_key_at_test_with_encryption() { }) .await .unwrap(); - assert_eq!(key_1.1, key_1_2); + assert_eq!(key_1.pub_key, key_1_2); } #[tokio::test] @@ -147,7 +147,7 @@ async fn key_manager_find_index() { let _next_key = key_manager.get_next_key("branch1").await.unwrap(); let _next_key = key_manager.get_next_key("branch1").await.unwrap(); let key_1 = key_manager.get_next_key("branch1").await.unwrap(); - let index = key_manager.find_key_index("branch1", &key_1.1).await.unwrap(); + let index = key_manager.find_key_index("branch1", &key_1.pub_key).await.unwrap(); assert_eq!(index, 3); } @@ -170,7 +170,7 @@ async fn key_manager_update_current_key_index_if_higher() { let _next_key_result = key_manager.get_next_key("branch1").await.unwrap(); let _next_key_result = key_manager.get_next_key("branch1").await.unwrap(); let key_1 = key_manager.get_next_key("branch1").await.unwrap(); - let index = key_manager.find_key_index("branch1", &key_1.1).await.unwrap(); + let index = key_manager.find_key_index("branch1", &key_1.pub_key).await.unwrap(); assert_eq!(index, 3); @@ -188,7 +188,7 @@ async fn key_manager_update_current_key_index_if_higher() { .unwrap(); let index = key_manager.find_key_index("branch1", &key_1_2).await.unwrap(); assert_eq!(index, 7); - assert_eq!(key_1_2, key_1.1); + assert_eq!(key_1_2, key_1.pub_key); } #[tokio::test] @@ -213,17 +213,17 @@ async fn key_manager_test_index() { let key_2 = key_manager .get_public_key_at_key_id(&KeyId::Managed { branch: "branch2".to_string(), - index: result.0.managed_index().unwrap(), + index: result.key_id.managed_index().unwrap(), }) .await .unwrap(); assert_eq!( - result.0.managed_index().unwrap(), - key_manager.find_key_index("branch1", &result.1).await.unwrap() + result.key_id.managed_index().unwrap(), + key_manager.find_key_index("branch1", &result.pub_key).await.unwrap() ); assert_eq!( - result.0.managed_index().unwrap(), + result.key_id.managed_index().unwrap(), key_manager.find_key_index("branch2", &key_2).await.unwrap() ); } diff --git a/base_layer/wallet/tests/other/mod.rs b/base_layer/wallet/tests/other/mod.rs index 31eb36e322..91456421cf 100644 --- a/base_layer/wallet/tests/other/mod.rs +++ b/base_layer/wallet/tests/other/mod.rs @@ -154,7 +154,7 @@ async fn create_wallet( auxiliary_tcp_listener_address: None, rpc_max_simultaneous_sessions: 0, rpc_max_sessions_per_peer: 0, - listener_liveness_check_interval: None, + listener_self_liveness_check_interval: None, }; let sql_database_path = comms_config @@ -250,7 +250,7 @@ async fn test_wallet() { .await .unwrap(); let bob_identity = (*bob_wallet.comms.node_identity()).clone(); - let bob_address = TariAddress::new(bob_identity.public_key().clone(), network); + let bob_address = TariAddress::new_single_address_with_interactive_only(bob_identity.public_key().clone(), network); alice_wallet .comms @@ -283,7 +283,7 @@ async fn test_wallet() { let mut alice_event_stream = alice_wallet.transaction_service.get_event_stream(); let value = MicroMinotari::from(1000); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap() let (_utxo, uo1) = make_non_recoverable_input(&mut OsRng, MicroMinotari(2500), &OutputFeatures::default(), &key_manager).await; alice_wallet.output_manager_service.add_output(uo1, None).await.unwrap(); @@ -320,7 +320,7 @@ async fn test_wallet() { let mut contacts = Vec::new(); for i in 0..2 { let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); - let address = TariAddress::new(public_key, Network::LocalNet); + let address = TariAddress::new_single_address_with_interactive_only(public_key, Network::LocalNet); contacts.push(Contact::new(random::string(8), address, None, None, false)); @@ -571,7 +571,7 @@ async fn test_store_and_forward_send_tx() { .unwrap(); let carol_identity = carol_wallet.comms.node_identity(); - let carol_address = TariAddress::new(carol_identity.public_key().clone(), network); + let carol_address = TariAddress::new_single_address_with_interactive_only(carol_identity.public_key().clone(), network); let mut carol_event_stream = carol_wallet.transaction_service.get_event_stream(); alice_wallet @@ -589,7 +589,7 @@ async fn test_store_and_forward_send_tx() { .unwrap(); let value = MicroMinotari::from(1000); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap() let (_utxo, uo1) = make_non_recoverable_input(&mut OsRng, MicroMinotari(2500), &OutputFeatures::default(), &key_manager).await; alice_wallet.output_manager_service.add_output(uo1, None).await.unwrap(); @@ -692,7 +692,7 @@ async fn test_import_utxo() { auxiliary_tcp_listener_address: None, rpc_max_simultaneous_sessions: 0, rpc_max_sessions_per_peer: 0, - listener_liveness_check_interval: None, + listener_self_liveness_check_interval: None, }; let config = WalletConfig { p2p: comms_config, @@ -733,12 +733,12 @@ async fn test_import_utxo() { let input = inputs!(claim); let temp_features = OutputFeatures::create_coinbase(50, None, RangeProofType::BulletProofPlus); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap() let p = TestParams::new(&key_manager); let utxo = create_wallet_output_with_data(script.clone(), temp_features, &p, 20000 * uT, &key_manager).await.unwrap(); - let output = utxo.as_transaction_output(&key_manager).unwrap(); + let output = utxo.to_transaction_output(&key_manager).unwrap(); let expected_output_hash = output.hash(); - let node_address = TariAddress::new(node_identity.public_key().clone(), network); + let node_address = TariAddress::new_single_address_with_interactive_only(node_identity.public_key().clone(), network); alice_wallet .set_base_node_peer( node_identity.public_key().clone(), @@ -860,7 +860,7 @@ async fn test_contacts_service_liveness() { .await .unwrap(); let alice_identity = alice_wallet.comms.node_identity(); - let alice_address = TariAddress::new(alice_identity.public_key().clone(), network); + let alice_address = TariAddress::new_single_address_with_interactive_only(alice_identity.public_key().clone(), network); let mut bob_wallet = create_wallet( bob_db_tempdir.path(), @@ -874,7 +874,7 @@ async fn test_contacts_service_liveness() { .await .unwrap(); let bob_identity = (*bob_wallet.comms.node_identity()).clone(); - let bob_address = TariAddress::new(bob_identity.public_key().clone(), network); + let bob_address = TariAddress::new_single_address_with_interactive_only(bob_identity.public_key().clone(), network); alice_wallet .comms diff --git a/base_layer/wallet/tests/output_manager_service_tests/service.rs b/base_layer/wallet/tests/output_manager_service_tests/service.rs index d879c86c1a..df7deae575 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/service.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/service.rs @@ -40,10 +40,8 @@ use minotari_wallet::{ }, test_utils::create_consensus_constants, transaction_service::handle::TransactionServiceHandle, - util::wallet_identity::WalletIdentity, }; use rand::{rngs::OsRng, RngCore}; -use tari_common::configuration::Network; use tari_common_types::{ transaction::TxId, types::{ComAndPubSignature, FixedHash, PublicKey}, @@ -66,17 +64,18 @@ use tari_core::{ MemoryDbKeyManager, TransactionKeyManagerBranch, TransactionKeyManagerInterface, + TransactionKeyManagerLabel, }, tari_amount::{uT, MicroMinotari, T}, test_helpers::{create_wallet_output_with_data, TestParams}, - transaction_components::{OutputFeatures, TransactionOutput, WalletOutput}, + transaction_components::{encrypted_data::PaymentId, OutputFeatures, TransactionOutput, WalletOutput}, transaction_protocol::{sender::TransactionSenderMessage, TransactionMetadata}, weight::TransactionWeight, CryptoFactories, SenderTransactionProtocol, }, }; -use tari_key_manager::key_manager_service::KeyManagerInterface; +use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface}; use tari_script::{inputs, script, TariScript}; use tari_service_framework::reply_channel; use tari_shutdown::Shutdown; @@ -159,9 +158,8 @@ async fn setup_output_manager_service( wallet_connectivity_mock.set_base_node_wallet_rpc_client(connect_rpc_client(&mut connection).await); } - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); - let wallet_identity = WalletIdentity::new(server_node_identity.clone(), Network::LocalNet); let output_manager_service = OutputManagerService::new( OutputManagerServiceConfig { ..Default::default() }, oms_request_receiver, @@ -172,7 +170,6 @@ async fn setup_output_manager_service( shutdown.to_signal(), basenode_service_handle, wallet_connectivity_mock.clone(), - wallet_identity, key_manager.clone(), ) .await @@ -197,7 +194,6 @@ async fn setup_output_manager_service( pub async fn setup_oms_with_bn_state( backend: T, height: Option, - node_identity: Arc, ) -> ( OutputManagerHandle, Shutdown, @@ -226,8 +222,7 @@ pub async fn setup_oms_with_bn_state( mock_base_node_service.set_base_node_state(height); task::spawn(mock_base_node_service.run()); let connectivity = create_wallet_connectivity_mock(); - let key_manager = create_memory_db_key_manager(); - let wallet_identity = WalletIdentity::new(node_identity.clone(), Network::LocalNet); + let key_manager = create_memory_db_key_manager().unwrap(); let output_manager_service = OutputManagerService::new( OutputManagerServiceConfig { ..Default::default() }, oms_request_receiver, @@ -238,7 +233,6 @@ pub async fn setup_oms_with_bn_state( shutdown.to_signal(), base_node_service_handle.clone(), connectivity, - wallet_identity, key_manager.clone(), ) .await @@ -284,7 +278,7 @@ async fn generate_sender_transaction_message( script!(Nop), inputs!(change.script_key_pk), change.script_key_id, - change.spend_key_id, + change.commitment_mask_key_id, Covenant::default(), ); @@ -388,12 +382,10 @@ async fn fee_estimate() { #[tokio::test] async fn test_utxo_selection_no_chain_metadata() { let (connection, _tempdir) = get_temp_sqlite_database_connection(); - let server_node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); let backend = OutputManagerSqliteDatabase::new(connection.clone()); // no chain metadata - let (mut oms, _shutdown, _, _, _, key_manager) = - setup_oms_with_bn_state(backend.clone(), None, server_node_identity).await; + let (mut oms, _shutdown, _, _, _, key_manager) = setup_oms_with_bn_state(backend.clone(), None).await; let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight_params()); // no utxos - not enough funds @@ -521,11 +513,9 @@ async fn test_utxo_selection_no_chain_metadata() { async fn test_utxo_selection_with_chain_metadata() { let (connection, _tempdir) = get_temp_sqlite_database_connection(); - let server_node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); // setup with chain metadata at a height of 6 let backend = OutputManagerSqliteDatabase::new(connection); - let (mut oms, _shutdown, _, _, _, key_manager) = - setup_oms_with_bn_state(backend.clone(), Some(6), server_node_identity).await; + let (mut oms, _shutdown, _, _, _, key_manager) = setup_oms_with_bn_state(backend.clone(), Some(6)).await; let fee_calc = Fee::new(*create_consensus_constants(0).transaction_weight_params()); // no utxos - not enough funds @@ -673,12 +663,9 @@ async fn test_utxo_selection_with_chain_metadata() { async fn test_utxo_selection_with_tx_priority() { let (connection, _tempdir) = get_temp_sqlite_database_connection(); - let server_node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); - // setup with chain metadata at a height of 6 let backend = OutputManagerSqliteDatabase::new(connection); - let (mut oms, _shutdown, _, _, _, key_manager) = - setup_oms_with_bn_state(backend.clone(), Some(6), server_node_identity).await; + let (mut oms, _shutdown, _, _, _, key_manager) = setup_oms_with_bn_state(backend.clone(), Some(6)).await; let amount = MicroMinotari::from(2000); let fee_per_gram = MicroMinotari::from(2); @@ -1933,7 +1920,7 @@ async fn test_txo_revalidation() { .set_base_node_wallet_rpc_client(connect_rpc_client(&mut connection).await); let output1_value = 1_000_000; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let output1 = create_wallet_output_with_data( script!(Nop), OutputFeatures::default(), @@ -2154,7 +2141,7 @@ async fn test_get_status_by_tx_id() { assert_eq!(output_statuses_by_tx_id.statuses.len(), 1); assert_eq!( output_statuses_by_tx_id.statuses[0], - OutputStatus::EncumberedToBeReceived + OutputStatus::UnspentMinedUnconfirmed ); } @@ -2171,37 +2158,44 @@ async fn scan_for_recovery_test() { let mut recoverable_wallet_outputs = Vec::new(); for i in 1..=NUM_RECOVERABLE { - let (spending_key_result, _) = oms + let commitment_mask_key = oms .key_manager_handle .get_next_key(TransactionKeyManagerBranch::CommitmentMask.get_branch_key()) .await .unwrap(); - let (script_key, public_script_key) = oms + let script_key_id = KeyId::Derived { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), + label: TransactionKeyManagerLabel::ScriptKey.get_branch_key(), + index: commitment_mask_key.key_id.managed_index().unwrap(), + }; + let public_script_key = oms .key_manager_handle - .get_next_key(TransactionKeyManagerBranch::ScriptKey.get_branch_key()) + .get_public_key_at_key_id(&script_key_id) .await .unwrap(); + let amount = 1_000 * i as u64; let features = OutputFeatures::default(); let encrypted_data = oms .key_manager_handle - .encrypt_data_for_recovery(&spending_key_result, None, amount) + .encrypt_data_for_recovery(&commitment_mask_key.key_id, None, amount, PaymentId::Empty) .await .unwrap(); let uo = WalletOutput::new_current_version( MicroMinotari::from(amount), - spending_key_result, + commitment_mask_key.key_id, features, script!(Nop), inputs!(public_script_key), - script_key, + script_key_id, PublicKey::default(), ComAndPubSignature::default(), 0, Covenant::new(), encrypted_data, MicroMinotari::zero(), + PaymentId::Empty, &oms.key_manager_handle, ) .await @@ -2211,7 +2205,7 @@ async fn scan_for_recovery_test() { let mut non_recoverable_wallet_outputs = Vec::new(); // we need to create a new key_manager to make the outputs non recoverable - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); for i in 1..=NUM_NON_RECOVERABLE { let uo = make_input( &mut OsRng, @@ -2266,7 +2260,7 @@ async fn recovered_output_key_not_in_keychain() { let backend = OutputManagerSqliteDatabase::new(connection.clone()); let mut oms = setup_output_manager_service(backend.clone(), true).await; // we need to create a new key manager here as we dont want the input be recoverable from oms key chain - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let uo = make_input( &mut OsRng, MicroMinotari::from(1000u64), diff --git a/base_layer/wallet/tests/output_manager_service_tests/storage.rs b/base_layer/wallet/tests/output_manager_service_tests/storage.rs index 86653749cf..8ad7a356a4 100644 --- a/base_layer/wallet/tests/output_manager_service_tests/storage.rs +++ b/base_layer/wallet/tests/output_manager_service_tests/storage.rs @@ -54,7 +54,7 @@ pub async fn test_db_backend(backend: T) { // Add some unspent outputs let mut unspent_outputs = Vec::new(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut unspent = Vec::with_capacity(5); for i in 0..5 { let uo = make_input( @@ -387,7 +387,7 @@ pub async fn test_raw_custom_queries_regression() { // Add some unspent outputs let mut unspent_outputs = Vec::new(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut unspent = Vec::with_capacity(5); for i in 0..5 { let uo = make_input( @@ -572,7 +572,7 @@ pub async fn test_short_term_encumberance() { let db = OutputManagerDatabase::new(backend); let mut unspent_outputs = Vec::new(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); for i in 0..5 { let kmo = make_input( &mut OsRng, @@ -633,7 +633,7 @@ pub async fn test_no_duplicate_outputs() { let db = OutputManagerDatabase::new(backend); // create an output - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let uo = make_input( &mut OsRng, MicroMinotari::from(1000), @@ -681,7 +681,7 @@ pub async fn test_mark_as_unmined() { let db = OutputManagerDatabase::new(backend); // create an output - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let uo = make_input( &mut OsRng, MicroMinotari::from(1000), diff --git a/base_layer/wallet/tests/support/comms_rpc.rs b/base_layer/wallet/tests/support/comms_rpc.rs index 351c9c692b..3e554df074 100644 --- a/base_layer/wallet/tests/support/comms_rpc.rs +++ b/base_layer/wallet/tests/support/comms_rpc.rs @@ -149,7 +149,7 @@ impl BaseNodeWalletRpcMockState { })), tip_info_response: Arc::new(Mutex::new(TipInfoResponse { metadata: Some(ChainMetadataProto { - best_block_height: std::i64::MAX as u64, + best_block_height: i64::MAX as u64, best_block_hash: FixedHash::zero().to_vec(), accumulated_difficulty: Vec::new(), pruned_height: 0, diff --git a/base_layer/wallet/tests/support/output_manager_service_mock.rs b/base_layer/wallet/tests/support/output_manager_service_mock.rs index 9f9e6ea3fc..071be4bb64 100644 --- a/base_layer/wallet/tests/support/output_manager_service_mock.rs +++ b/base_layer/wallet/tests/support/output_manager_service_mock.rs @@ -117,9 +117,8 @@ impl OutputManagerServiceMock { let _result = reply_tx .send(Ok(OutputManagerResponse::RewoundOutputs(outputs))) - .map_err(|e| { + .inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send reply"); - e }); }, OutputManagerRequest::ScanOutputs(requested_outputs) => { @@ -141,9 +140,8 @@ impl OutputManagerServiceMock { .collect(); let _result = reply_tx .send(Ok(OutputManagerResponse::ScanOutputs(outputs))) - .map_err(|e| { + .inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send reply"); - e }); }, OutputManagerRequest::ValidateUtxos => {}, diff --git a/base_layer/wallet/tests/support/transaction_service_mock.rs b/base_layer/wallet/tests/support/transaction_service_mock.rs index 59cdba9e91..5b19a8d771 100644 --- a/base_layer/wallet/tests/support/transaction_service_mock.rs +++ b/base_layer/wallet/tests/support/transaction_service_mock.rs @@ -105,9 +105,8 @@ impl TransactionServiceMock { TransactionServiceRequest::ImportUtxoWithStatus { .. } => { let _result = reply_tx .send(Ok(TransactionServiceResponse::UtxoImported(TxId::from(42u64)))) - .map_err(|e| { + .inspect_err(|_| { warn!(target: LOG_TARGET, "Failed to send reply"); - e }); }, TransactionServiceRequest::ValidateTransactions => {}, diff --git a/base_layer/wallet/tests/support/utils.rs b/base_layer/wallet/tests/support/utils.rs index 65da7d7baa..742c28acf3 100644 --- a/base_layer/wallet/tests/support/utils.rs +++ b/base_layer/wallet/tests/support/utils.rs @@ -28,6 +28,7 @@ use tari_core::{ tari_amount::MicroMinotari, test_helpers::{create_wallet_output_with_data, TestParams}, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, RangeProofType, TransactionOutput, @@ -56,10 +57,9 @@ pub async fn make_fake_input_from_copy( wallet_output: &mut WalletOutput, key_manager: &MemoryDbKeyManager, ) -> WalletOutput { - let (spend_key_id, _spend_key_pk, script_key_id, _script_key_pk) = - key_manager.get_next_spend_and_script_key_ids().await.unwrap(); - wallet_output.spending_key_id = spend_key_id; - wallet_output.script_key_id = script_key_id; + let (commitment_mask_key, script_key) = key_manager.get_next_commitment_mask_and_script_key().await.unwrap(); + wallet_output.spending_key_id = commitment_mask_key.key_id; + wallet_output.script_key_id = script_key.key_id; wallet_output.clone() } @@ -74,13 +74,18 @@ pub async fn create_wallet_output_from_sender_data( .await .unwrap(); let encrypted_data = key_manager - .encrypt_data_for_recovery(&test_params.spend_key_id, None, sender_data.amount.as_u64()) + .encrypt_data_for_recovery( + &test_params.commitment_mask_key_id, + None, + sender_data.amount.as_u64(), + PaymentId::Empty, + ) .await .unwrap(); let mut utxo = WalletOutput::new( TransactionOutputVersion::get_current_version(), sender_data.amount, - test_params.spend_key_id.clone(), + test_params.commitment_mask_key_id.clone(), sender_data.features.clone(), sender_data.script.clone(), inputs!(public_script_key), @@ -91,6 +96,7 @@ pub async fn create_wallet_output_from_sender_data( Covenant::default(), encrypted_data, MicroMinotari::zero(), + PaymentId::Empty, key_manager, ) .await @@ -98,7 +104,7 @@ pub async fn create_wallet_output_from_sender_data( let output_message = TransactionOutput::metadata_signature_message(&utxo); utxo.metadata_signature = key_manager .get_receiver_partial_metadata_signature( - &test_params.spend_key_id, + &test_params.commitment_mask_key_id, &sender_data.amount.into(), &sender_data.sender_offset_public_key, &sender_data.ephemeral_public_nonce, diff --git a/base_layer/wallet/tests/transaction_service_tests/service.rs b/base_layer/wallet/tests/transaction_service_tests/service.rs index 4e745860c6..2f2398a336 100644 --- a/base_layer/wallet/tests/transaction_service_tests/service.rs +++ b/base_layer/wallet/tests/transaction_service_tests/service.rs @@ -71,7 +71,6 @@ use minotari_wallet::{ }, transaction_service::{ config::TransactionServiceConfig, - error::TransactionServiceError, handle::{TransactionEvent, TransactionSendStatus, TransactionServiceHandle}, service::TransactionService, storage::{ @@ -81,7 +80,6 @@ use minotari_wallet::{ }, TransactionServiceInitializer, }, - util::wallet_identity::WalletIdentity, }; use prost::Message; use rand::{rngs::OsRng, RngCore}; @@ -91,7 +89,7 @@ use tari_common_types::{ tari_address::TariAddress, transaction::{ImportStatus, TransactionDirection, TransactionStatus, TxId}, types::{FixedHash, PrivateKey, PublicKey, Signature}, - wallet_types::WalletType, + wallet_types::{ProvidedKeysWallet, WalletType}, }; use tari_comms::{ message::EnvelopeBody, @@ -130,7 +128,13 @@ use tari_core::{ }, tari_amount::*, test_helpers::{create_wallet_output_with_data, TestParams}, - transaction_components::{KernelBuilder, OutputFeatures, RangeProofType, Transaction}, + transaction_components::{ + encrypted_data::PaymentId, + KernelBuilder, + OutputFeatures, + RangeProofType, + Transaction, + }, transaction_protocol::{ proto::protocol as proto, recipient::RecipientSignedMessage, @@ -154,7 +158,7 @@ use tari_key_manager::{ key_manager_service::{storage::sqlite_db::KeyManagerSqliteDatabase, KeyId, KeyManagerInterface}, }; use tari_p2p::{comms_connector::pubsub_connector, domain_message::DomainMessage, Network}; -use tari_script::{inputs, one_sided_payment_script, script, ExecutionStack}; +use tari_script::{inputs, push_pubkey_script, script, ExecutionStack}; use tari_service_framework::{reply_channel, RegisterHandle, StackBuilder}; use tari_shutdown::{Shutdown, ShutdownSignal}; use tari_test_utils::{comms_and_services::get_next_memory_address, random}; @@ -204,7 +208,7 @@ async fn setup_transaction_service>( let passphrase = SafePassword::from("My lovely secret passphrase"); let db = WalletDatabase::new(WalletSqliteDatabase::new(db_connection.clone(), passphrase).unwrap()); - let metadata = ChainMetadata::new(std::i64::MAX as u64, FixedHash::zero(), 0, 0, 1.into(), 0).unwrap(); + let metadata = ChainMetadata::new(i64::MAX as u64, FixedHash::zero(), 0, 0, 1.into(), 0).unwrap(); db.set_chain_metadata(metadata).unwrap(); @@ -215,7 +219,6 @@ async fn setup_transaction_service>( let ts_backend = TransactionServiceSqliteDatabase::new(db_connection.clone(), cipher.clone()); let oms_backend = OutputManagerSqliteDatabase::new(db_connection.clone()); - let wallet_identity = WalletIdentity::new(node_identity, Network::LocalNet); let connection = DbConnection::connect_url(&DbConnectionUrl::MemoryShared(random_string(8))).unwrap(); let cipher = CipherSeed::new(); @@ -224,7 +227,11 @@ async fn setup_transaction_service>( let key_ga = Key::from_slice(&key); let db_cipher = XChaCha20Poly1305::new(key_ga); let kms_backend = KeyManagerSqliteDatabase::init(connection, db_cipher); - + let wallet_type = WalletType::ProvidedKeys(ProvidedKeysWallet { + public_spend_key: PublicKey::from_secret_key(node_identity.secret_key()), + private_spend_key: Some(node_identity.secret_key().clone()), + view_key: SK::random(&mut OsRng), + }); let handles = StackBuilder::new(shutdown_signal) .add_initializer(RegisterHandle::new(dht)) .add_initializer(RegisterHandle::new(comms.connectivity())) @@ -236,13 +243,12 @@ async fn setup_transaction_service>( oms_backend.clone(), factories.clone(), Network::LocalNet.into(), - wallet_identity.clone(), )) .add_initializer(TransactionKeyManagerInitializer::>::new( kms_backend, cipher, factories.clone(), - WalletType::Software, + wallet_type, )) .add_initializer(TransactionServiceInitializer::<_, _, MemoryDbKeyManager>::new( TransactionServiceConfig { @@ -254,7 +260,8 @@ async fn setup_transaction_service>( }, subscription_factory, ts_backend, - wallet_identity, + node_identity.clone(), + Network::LocalNet, consensus_manager, factories, db.clone(), @@ -376,9 +383,8 @@ async fn setup_transaction_service_no_comms( let ts_service_db = TransactionServiceSqliteDatabase::new(db_connection.clone(), cipher.clone()); let ts_db = TransactionDatabase::new(ts_service_db.clone()); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let oms_db = OutputManagerDatabase::new(OutputManagerSqliteDatabase::new(db_connection)); - let wallet_identity = WalletIdentity::new(node_identity.clone(), Network::LocalNet); let output_manager_service = OutputManagerService::new( OutputManagerServiceConfig::default(), oms_request_receiver, @@ -389,7 +395,6 @@ async fn setup_transaction_service_no_comms( shutdown.to_signal(), base_node_service_handle.clone(), wallet_connectivity_service_mock.clone(), - wallet_identity, key_manager.clone(), ) .await @@ -411,7 +416,6 @@ async fn setup_transaction_service_no_comms( max_tx_query_batch_size: 2, ..Default::default() }); - let wallet_identity = WalletIdentity::new(node_identity.clone(), Network::LocalNet); let ts_service = TransactionService::new( test_config, ts_db.clone(), @@ -427,12 +431,15 @@ async fn setup_transaction_service_no_comms( outbound_message_requester, wallet_connectivity_service_mock.clone(), event_publisher, - wallet_identity, + node_identity.clone(), + Network::LocalNet, consensus_manager, factories, shutdown.to_signal(), base_node_service_handle, - ); + ) + .await + .unwrap(); task::spawn(async move { output_manager_service.start().await.unwrap() }); task::spawn(async move { ts_service.start().await.unwrap() }); TransactionServiceNoCommsInterface { @@ -493,18 +500,16 @@ fn try_decode_transaction_reply_message(bytes: Vec) -> Option) -> Option { let envelope_body = EnvelopeBody::decode(&mut bytes.as_slice()).unwrap(); - match envelope_body.decode_part::(1) { - Err(_) => None, - Ok(d) => d, - } + envelope_body + .decode_part::(1) + .unwrap_or_default() } fn try_decode_transaction_cancelled_message(bytes: Vec) -> Option { let envelope_body = EnvelopeBody::decode(&mut bytes.as_slice()).unwrap(); - match envelope_body.decode_part::(1) { - Err(_) => None, - Ok(d) => d, - } + envelope_body + .decode_part::(1) + .unwrap_or_default() } #[tokio::test(flavor = "multi_thread", worker_threads = 1)] @@ -590,7 +595,8 @@ async fn manage_single_transaction() { &alice_key_manager_handle, ) .await; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); assert!(alice_ts .send_transaction( bob_address.clone(), @@ -763,7 +769,8 @@ async fn large_interactive_transaction() { } alice_db.mark_outputs_as_unspent(unspent).unwrap(); let transaction_value = output_value * (outputs_count as u64 - 1); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let message = "TAKE MAH MONEYS!".to_string(); alice_ts @@ -926,7 +933,8 @@ async fn test_spend_dust_to_self_in_oversized_transaction() { let fee_per_gram = MicroMinotari::from(1); let message = "TAKE MAH _OWN_ MONEYS!".to_string(); let value = balance.available_balance - amount_per_output * 10; - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); + let alice_address = + TariAddress::new_single_address_with_interactive_only(alice_node_identity.public_key().clone(), network); assert!(alice_ts .send_transaction( alice_address, @@ -1023,7 +1031,8 @@ async fn test_spend_dust_to_other_in_oversized_transaction() { let fee_per_gram = MicroMinotari::from(1); let message = "GIVE MAH _OWN_ MONEYS AWAY!".to_string(); let value = balance.available_balance - amount_per_output * 10; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( bob_address, @@ -1138,7 +1147,8 @@ async fn test_spend_dust_happy_path() { let message = "TAKE MAH _OWN_ MONEYS!".to_string(); let value_self = (number_of_outputs / 3) * amount_per_output; - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); + let alice_address = + TariAddress::new_single_address_with_interactive_only(alice_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( alice_address, @@ -1182,7 +1192,8 @@ async fn test_spend_dust_happy_path() { let message = "GIVE MAH _OWN_ MONEYS AWAY!".to_string(); let value_bob = (number_of_outputs / 3) * amount_per_output; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( bob_address, @@ -1282,7 +1293,8 @@ async fn single_transaction_to_self() { .unwrap(); let message = "TAKE MAH _OWN_ MONEYS!".to_string(); let value = 10000.into(); - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); + let alice_address = + TariAddress::new_single_address_with_interactive_only(alice_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( alice_address, @@ -1529,7 +1541,7 @@ async fn single_transaction_burn_tari_inner(sidechain_key: Option) { .try_output_key_recovery(output, Some(&recovery_key_id)) .await { - Ok((spending_key_id, value)) => { + Ok((spending_key_id, value, _)) => { assert_eq!(value, burn_value); assert_eq!(spending_key_id, spending_key_id_from_reciprocal_claim_public_key) }, @@ -1610,7 +1622,13 @@ async fn send_one_sided_transaction_to_other() { let message = "SEE IF YOU CAN CATCH THIS ONE..... SIDED TX!".to_string(); let value = 10000.into(); let mut alice_ts_clone = alice_ts.clone(); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let random_pvt_key = PrivateKey::random(&mut OsRng); + let bob_view_key = PublicKey::from_secret_key(&random_pvt_key); + let bob_address = TariAddress::new_dual_address_with_default_features( + bob_view_key, + bob_node_identity.public_key().clone(), + network, + ); let tx_id = alice_ts_clone .send_one_sided_transaction( bob_address, @@ -1619,6 +1637,7 @@ async fn send_one_sided_transaction_to_other() { OutputFeatures::default(), 20.into(), message.clone(), + PaymentId::Empty, ) .await .expect("Alice sending one-sided tx to Bob"); @@ -1722,7 +1741,7 @@ async fn recover_one_sided_transaction() { shutdown.to_signal(), ) .await; - let script = one_sided_payment_script(bob_node_identity.public_key()); + let script = push_pubkey_script(bob_node_identity.public_key()); let known_script = KnownOneSidedPaymentScript { script_hash: script.as_hash::>().unwrap().to_vec(), script_key_id: bob_key_manager_handle @@ -1753,7 +1772,12 @@ async fn recover_one_sided_transaction() { let message = "".to_string(); let value = 10000.into(); let mut alice_ts_clone = alice_ts.clone(); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_view_key = bob_key_manager_handle.get_view_key().await.unwrap(); + let bob_address = TariAddress::new_dual_address_with_default_features( + bob_view_key.pub_key, + bob_node_identity.public_key().clone(), + network, + ); let tx_id = alice_ts_clone .send_one_sided_transaction( bob_address, @@ -1762,6 +1786,131 @@ async fn recover_one_sided_transaction() { OutputFeatures::default(), 20.into(), message.clone(), + PaymentId::Empty, + ) + .await + .expect("Alice sending one-sided tx to Bob"); + + let completed_tx = alice_ts + .get_completed_transaction(tx_id) + .await + .expect("Could not find completed one-sided tx"); + let outputs = completed_tx.transaction.body.outputs().clone(); + + let recovered_outputs_1 = bob_oms + .scan_outputs_for_one_sided_payments(outputs.clone()) + .await + .unwrap(); + // Bob should be able to claim 1 output. + assert_eq!(1, recovered_outputs_1.len()); + assert_eq!(value, recovered_outputs_1[0].output.value); + + // Should ignore already existing outputs + let recovered_outputs_2 = bob_oms.scan_outputs_for_one_sided_payments(outputs).await.unwrap(); + assert!(recovered_outputs_2.is_empty()); +} + +#[tokio::test] +async fn recover_stealth_one_sided_transaction() { + let network = Network::LocalNet; + let consensus_manager = ConsensusManager::builder(network).build().unwrap(); + let factories = CryptoFactories::default(); + // Alice's parameters + let alice_node_identity = Arc::new(NodeIdentity::random( + &mut OsRng, + get_next_memory_address(), + PeerFeatures::COMMUNICATION_NODE, + )); + + // Bob's parameters + let bob_node_identity = Arc::new(NodeIdentity::random( + &mut OsRng, + get_next_memory_address(), + PeerFeatures::COMMUNICATION_NODE, + )); + + let base_node_identity = Arc::new(NodeIdentity::random( + &mut OsRng, + get_next_memory_address(), + PeerFeatures::COMMUNICATION_NODE, + )); + + log::info!( + "manage_single_transaction: Alice: '{}', Bob: '{}', Base: '{}'", + alice_node_identity.node_id().short_str(), + bob_node_identity.node_id().short_str(), + base_node_identity.node_id().short_str() + ); + + let temp_dir = tempdir().unwrap(); + let temp_dir2 = tempdir().unwrap(); + let database_path = temp_dir.path().to_str().unwrap().to_string(); + let database_path2 = temp_dir2.path().to_str().unwrap().to_string(); + + let alice_connection = make_wallet_database_memory_connection(); + let bob_connection = make_wallet_database_memory_connection(); + + let shutdown = Shutdown::new(); + let (mut alice_ts, alice_oms, _alice_comms, _alice_connectivity, alice_key_manager_handle, alice_db) = + setup_transaction_service( + alice_node_identity, + vec![], + consensus_manager.clone(), + factories.clone(), + alice_connection, + database_path, + Duration::from_secs(0), + shutdown.to_signal(), + ) + .await; + + let (_bob_ts, mut bob_oms, _bob_comms, _bob_connectivity, bob_key_manager_handle, _bob_db) = + setup_transaction_service( + bob_node_identity.clone(), + vec![], + consensus_manager, + factories.clone(), + bob_connection, + database_path2, + Duration::from_secs(0), + shutdown.to_signal(), + ) + .await; + + let bob_view_key = bob_key_manager_handle.get_view_key().await.unwrap(); + + let initial_wallet_value = 25000.into(); + let uo1 = make_input( + &mut OsRng, + initial_wallet_value, + &OutputFeatures::default(), + &alice_key_manager_handle, + ) + .await; + let mut alice_oms_clone = alice_oms; + alice_oms_clone.add_output(uo1.clone(), None).await.unwrap(); + alice_db + .mark_outputs_as_unspent(vec![(uo1.hash(&alice_key_manager_handle).await.unwrap(), true)]) + .unwrap(); + + let message = "".to_string(); + let value = 10000.into(); + let mut alice_ts_clone = alice_ts.clone(); + + let bob_address = TariAddress::new_dual_address_with_default_features( + bob_view_key.pub_key, + bob_node_identity.public_key().clone(), + network, + ); + let tx_id = alice_ts_clone + .send_one_sided_to_stealth_address_transaction( + bob_address, + value, + UtxoSelectionCriteria::default(), + OutputFeatures::default(), + 20.into(), + message.clone(), + PaymentId::Empty, ) .await .expect("Alice sending one-sided tx to Bob"); @@ -1858,7 +2007,9 @@ async fn test_htlc_send_and_claim() { let message = "".to_string(); let value = 10000.into(); let bob_pubkey = bob_ts_interface.base_node_identity.public_key().clone(); - let bob_address = TariAddress::new(bob_pubkey.clone(), Network::LocalNet); + let bob_view_key = bob_ts_interface.key_manager_handle.get_view_key().await.unwrap(); + let bob_address = + TariAddress::new_dual_address_with_default_features(bob_view_key.pub_key, bob_pubkey.clone(), network); let (tx_id, pre_image, output) = alice_ts .send_sha_atomic_swap_transaction( bob_address, @@ -1922,87 +2073,6 @@ async fn test_htlc_send_and_claim() { ); } -#[tokio::test] -async fn send_one_sided_transaction_to_self() { - let network = Network::LocalNet; - let consensus_manager = ConsensusManager::builder(network).build().unwrap(); - let factories = CryptoFactories::default(); - // Alice's parameters - let alice_node_identity = Arc::new(NodeIdentity::random( - &mut OsRng, - get_next_memory_address(), - PeerFeatures::COMMUNICATION_NODE, - )); - - let base_node_identity = Arc::new(NodeIdentity::random( - &mut OsRng, - get_next_memory_address(), - PeerFeatures::COMMUNICATION_NODE, - )); - - log::info!( - "manage_single_transaction: Alice: '{}', Base: '{}'", - alice_node_identity.node_id().short_str(), - base_node_identity.node_id().short_str() - ); - - let temp_dir = tempdir().unwrap(); - let database_path = temp_dir.path().to_str().unwrap().to_string(); - - let alice_connection = make_wallet_database_memory_connection(); - - let shutdown = Shutdown::new(); - let (alice_ts, alice_oms, _alice_comms, _alice_connectivity, key_manager_handle, alice_db) = - setup_transaction_service( - alice_node_identity.clone(), - vec![], - consensus_manager, - factories.clone(), - alice_connection, - database_path, - Duration::from_secs(0), - shutdown.to_signal(), - ) - .await; - - let initial_wallet_value = 2500.into(); - let uo1 = make_input( - &mut OsRng, - initial_wallet_value, - &OutputFeatures::default(), - &key_manager_handle, - ) - .await; - let mut alice_oms_clone = alice_oms; - alice_oms_clone.add_output(uo1.clone(), None).await.unwrap(); - alice_db - .mark_outputs_as_unspent(vec![(uo1.hash(&key_manager_handle).await.unwrap(), true)]) - .unwrap(); - - let message = "SEE IF YOU CAN CATCH THIS ONE..... SIDED TX!".to_string(); - let value = 1000.into(); - let mut alice_ts_clone = alice_ts; - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), network); - match alice_ts_clone - .send_one_sided_transaction( - alice_address, - value, - UtxoSelectionCriteria::default(), - OutputFeatures::default(), - 20.into(), - message.clone(), - ) - .await - { - Err(TransactionServiceError::OneSidedTransactionError(e)) => { - assert_eq!(e.as_str(), "One-sided spend-to-self transactions not supported"); - }, - _ => { - panic!("Expected: OneSidedTransactionError(\"One-sided spend-to-self transactions not supported\")"); - }, - }; -} - #[tokio::test(flavor = "multi_thread", worker_threads = 3)] async fn manage_multiple_transactions() { let network = Network::LocalNet; @@ -2175,7 +2245,10 @@ async fn manage_multiple_transactions() { let value_b_to_a_1 = MicroMinotari::from(11000); let value_a_to_c_1 = MicroMinotari::from(14000); log::trace!("Sending A to B 1"); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id_a_to_b_1 = alice_ts .send_transaction( bob_address.clone(), @@ -2189,7 +2262,10 @@ async fn manage_multiple_transactions() { .unwrap(); log::trace!("A to B 1 TxID: {}", tx_id_a_to_b_1); log::trace!("Sending A to C 1"); - let carol_address = TariAddress::new(carol_node_identity.public_key().clone(), Network::LocalNet); + let carol_address = TariAddress::new_single_address_with_interactive_only( + carol_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id_a_to_c_1 = alice_ts .send_transaction( carol_address, @@ -2205,7 +2281,10 @@ async fn manage_multiple_transactions() { assert_eq!(alice_completed_tx.len(), 0); log::trace!("A to C 1 TxID: {}", tx_id_a_to_c_1); - let alice_address = TariAddress::new(alice_node_identity.public_key().clone(), Network::LocalNet); + let alice_address = TariAddress::new_single_address_with_interactive_only( + alice_node_identity.public_key().clone(), + Network::LocalNet, + ); bob_ts .send_transaction( alice_address, @@ -2357,7 +2436,10 @@ async fn test_accepting_unknown_tx_id_and_malformed_reply() { )]) .unwrap(); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); alice_ts_interface .transaction_service_handle .send_transaction( @@ -2420,7 +2502,7 @@ async fn test_accepting_unknown_tx_id_and_malformed_reply() { #[tokio::test] async fn finalize_tx_with_incorrect_pubkey() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let temp_dir = tempdir().unwrap(); let path_string = temp_dir.path().to_str().unwrap().to_string(); @@ -2548,7 +2630,7 @@ async fn finalize_tx_with_incorrect_pubkey() { #[tokio::test] async fn finalize_tx_with_missing_output() { let factories = CryptoFactories::default(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let temp_dir = tempdir().unwrap(); let path_string = temp_dir.path().to_str().unwrap().to_string(); @@ -2788,7 +2870,8 @@ async fn discovery_async_return_test() { let initial_balance = alice_oms.get_balance().await.unwrap(); let value_a_to_c_1 = MicroMinotari::from(14000); - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), network); + let bob_address = + TariAddress::new_single_address_with_interactive_only(bob_node_identity.public_key().clone(), network); let tx_id = alice_ts .send_transaction( bob_address, @@ -2826,7 +2909,8 @@ async fn discovery_async_return_test() { assert_eq!(found_txid, tx_id); assert!(!is_direct_send); - let carol_address = TariAddress::new(carol_node_identity.public_key().clone(), network); + let carol_address = + TariAddress::new_single_address_with_interactive_only(carol_node_identity.public_key().clone(), network); let tx_id2 = alice_ts .send_transaction( carol_address, @@ -2907,11 +2991,13 @@ async fn test_power_mode_updates() { PrivateKey::random(&mut OsRng), PrivateKey::random(&mut OsRng), ); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2934,13 +3020,16 @@ async fn test_power_mode_updates() { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -2963,6 +3052,7 @@ async fn test_power_mode_updates() { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; tx_backend @@ -3122,7 +3212,10 @@ async fn test_transaction_cancellation() { .unwrap(); let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -3218,7 +3311,7 @@ async fn test_transaction_cancellation() { .remove(&tx_id) .is_none()); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let input = create_wallet_output_with_data( script!(Nop), OutputFeatures::default(), @@ -3230,7 +3323,7 @@ async fn test_transaction_cancellation() { .unwrap(); let constants = create_consensus_constants(0); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut builder = SenderTransactionProtocol::builder(constants, key_manager.clone()); let amount = MicroMinotari::from(10_000); let change = TestParams::new(&key_manager).await; @@ -3245,7 +3338,7 @@ async fn test_transaction_cancellation() { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_recipient_data( @@ -3330,7 +3423,7 @@ async fn test_transaction_cancellation() { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_recipient_data( @@ -3465,7 +3558,10 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { .unwrap(); let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -3664,7 +3760,10 @@ async fn test_direct_vs_saf_send_of_tx_reply_and_finalize() { let amount_sent = 20000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let _tx_id2 = alice_ts_interface .transaction_service_handle .send_transaction( @@ -3850,7 +3949,10 @@ async fn test_tx_direct_send_behaviour() { }) .await; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let _tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -4078,7 +4180,7 @@ async fn test_restarting_transaction_protocols() { .await; let constants = create_consensus_constants(0); let fee_calc = Fee::new(*constants.transaction_weight_params()); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut builder = SenderTransactionProtocol::builder(constants.clone(), key_manager.clone()); let fee = fee_calc.calculate(MicroMinotari(4), 1, 1, 1, 0); let change = TestParams::new(&key_manager).await; @@ -4101,7 +4203,7 @@ async fn test_restarting_transaction_protocols() { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ); let mut bob_stp = builder.build().await.unwrap(); @@ -4128,7 +4230,12 @@ async fn test_restarting_transaction_protocols() { }; let tx = bob_stp.get_transaction().unwrap().clone(); - let bob_address = TariAddress::new(bob_identity.public_key().clone(), network); + let bob_view_key = bob_ts_interface.key_manager_handle.get_view_key().await.unwrap(); + let bob_address = TariAddress::new_dual_address_with_default_features( + bob_view_key.pub_key, + bob_identity.public_key().clone(), + network, + ); let inbound_tx = InboundTransaction { tx_id, source_address: bob_address, @@ -4149,8 +4256,12 @@ async fn test_restarting_transaction_protocols() { Box::new(inbound_tx), ))) .unwrap(); - - let alice_address = TariAddress::new(alice_identity.public_key().clone(), network); + let alice_view_key = alice_ts_interface.key_manager_handle.get_view_key().await.unwrap(); + let alice_address = TariAddress::new_dual_address_with_default_features( + alice_view_key.pub_key, + alice_identity.public_key().clone(), + network, + ); let outbound_tx = OutboundTransaction { tx_id, destination_address: alice_address, @@ -4305,7 +4416,10 @@ async fn test_transaction_resending() { let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -4484,7 +4598,7 @@ async fn test_resend_on_startup() { NodeIdentity::random(&mut OsRng, get_next_memory_address(), PeerFeatures::COMMUNICATION_NODE); // First we will check the Send Tranasction message - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let input = create_wallet_output_with_data( script!(Nop), OutputFeatures::default(), @@ -4495,7 +4609,7 @@ async fn test_resend_on_startup() { .await .unwrap(); let constants = create_consensus_constants(0); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut builder = SenderTransactionProtocol::builder(constants.clone(), key_manager.clone()); let amount = MicroMinotari::from(10_000); let change = TestParams::new(&key_manager).await; @@ -4510,7 +4624,7 @@ async fn test_resend_on_startup() { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_recipient_data( @@ -4528,7 +4642,8 @@ async fn test_resend_on_startup() { let tx_sender_msg = TransactionSenderMessage::Single(Box::new(stp_msg)); let tx_id = stp.get_tx_id().unwrap(); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -4661,7 +4776,8 @@ async fn test_resend_on_startup() { &constants, ) .await; - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -4821,7 +4937,10 @@ async fn test_replying_to_cancelled_tx() { )]) .unwrap(); let amount_sent = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -4958,7 +5077,10 @@ async fn test_transaction_timeout_cancellation() { let amount_sent = 10000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let tx_id = alice_ts_interface .transaction_service_handle .send_transaction( @@ -5004,7 +5126,7 @@ async fn test_transaction_timeout_cancellation() { // Now to test if the timeout has elapsed during downtime and that it is honoured on startup // First we will check the Send Transction message - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let input = create_wallet_output_with_data( script!(Nop), OutputFeatures::default(), @@ -5015,7 +5137,7 @@ async fn test_transaction_timeout_cancellation() { .await .unwrap(); let constants = create_consensus_constants(0); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut builder = SenderTransactionProtocol::builder(constants, key_manager.clone()); let amount = MicroMinotari::from(10_000); let change = TestParams::new(&key_manager).await; @@ -5030,7 +5152,7 @@ async fn test_transaction_timeout_cancellation() { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ) .with_recipient_data( @@ -5048,7 +5170,8 @@ async fn test_transaction_timeout_cancellation() { let tx_sender_msg = TransactionSenderMessage::Single(Box::new(stp_msg)); let tx_id = stp.get_tx_id().unwrap(); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -5249,7 +5372,10 @@ async fn transaction_service_tx_broadcast() { let amount_sent1 = 100000 * uT; - let bob_address = TariAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_address = TariAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); // Send Tx1 let tx_id1 = alice_ts_interface .transaction_service_handle @@ -5583,11 +5709,13 @@ async fn broadcast_all_completed_transactions_on_startup() { PrivateKey::random(&mut OsRng), PrivateKey::random(&mut OsRng), ); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -5610,6 +5738,7 @@ async fn broadcast_all_completed_transactions_on_startup() { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: None, }; let completed_tx2 = CompletedTransaction { @@ -5708,7 +5837,7 @@ async fn test_update_faux_tx_on_oms_validation() { let connection = make_wallet_database_memory_connection(); let mut alice_ts_interface = setup_transaction_service_no_comms(factories.clone(), connection, None).await; - let alice_address = TariAddress::new( + let alice_address = TariAddress::new_single_address_with_interactive_only( alice_ts_interface.base_node_identity.public_key().clone(), Network::LocalNet, ); @@ -5748,6 +5877,7 @@ async fn test_update_faux_tx_on_oms_validation() { uo_1.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5764,6 +5894,7 @@ async fn test_update_faux_tx_on_oms_validation() { uo_2.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5780,6 +5911,7 @@ async fn test_update_faux_tx_on_oms_validation() { uo_3.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5883,7 +6015,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { let connection = make_wallet_database_memory_connection(); let mut alice_ts_interface = setup_transaction_service_no_comms(factories.clone(), connection, None).await; - let alice_address = TariAddress::new( + let alice_address = TariAddress::new_single_address_with_interactive_only( alice_ts_interface.base_node_identity.public_key().clone(), Network::LocalNet, ); @@ -5923,6 +6055,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { uo_1.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5939,6 +6072,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { uo_2.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); @@ -5955,6 +6089,7 @@ async fn test_update_coinbase_tx_on_oms_validation() { uo_3.to_transaction_output(&alice_ts_interface.key_manager_handle) .await .unwrap(), + PaymentId::Empty, ) .await .unwrap(); diff --git a/base_layer/wallet/tests/transaction_service_tests/storage.rs b/base_layer/wallet/tests/transaction_service_tests/storage.rs index bf32ac5ca4..f991eb003e 100644 --- a/base_layer/wallet/tests/transaction_service_tests/storage.rs +++ b/base_layer/wallet/tests/transaction_service_tests/storage.rs @@ -20,7 +20,6 @@ // 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 core::default::Default; use std::mem::size_of; use chacha20poly1305::{Key, KeyInit, XChaCha20Poly1305}; @@ -50,10 +49,16 @@ use tari_common_types::{ use tari_core::{ covenants::Covenant, transactions::{ - key_manager::{create_memory_db_key_manager, TransactionKeyManagerBranch, TransactionKeyManagerInterface}, + key_manager::{ + create_memory_db_key_manager, + TransactionKeyManagerBranch, + TransactionKeyManagerInterface, + TransactionKeyManagerLabel, + }, tari_amount::{uT, MicroMinotari}, test_helpers::{create_wallet_output_with_data, TestParams}, transaction_components::{ + encrypted_data::PaymentId, OutputFeatures, RangeProofType, Transaction, @@ -67,14 +72,14 @@ use tari_core::{ }, }; use tari_crypto::keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait}; -use tari_key_manager::key_manager_service::KeyManagerInterface; +use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface}; use tari_script::{inputs, script}; use tari_test_utils::random; use tempfile::tempdir; pub async fn test_db_backend(backend: T) { let mut db = TransactionDatabase::new(backend); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let input = create_wallet_output_with_data( script!(Nop), OutputFeatures::default(), @@ -85,7 +90,7 @@ pub async fn test_db_backend(backend: T) { .await .unwrap(); let constants = create_consensus_constants(0); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let mut builder = SenderTransactionProtocol::builder(constants.clone(), key_manager.clone()); let amount = MicroMinotari::from(10_000); builder @@ -109,7 +114,7 @@ pub async fn test_db_backend(backend: T) { script!(Nop), inputs!(change.script_key_pk), change.script_key_id.clone(), - change.spend_key_id.clone(), + change.commitment_mask_key_id.clone(), Covenant::default(), ); @@ -126,7 +131,8 @@ pub async fn test_db_backend(backend: T) { for i in 0..messages.len() { let tx_id = TxId::from(i + 10); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -175,22 +181,30 @@ pub async fn test_db_backend(backend: T) { panic!("Should have found outbound tx"); } let sender = stp.clone().build_single_round_message(&key_manager).await.unwrap(); - let (spending_key_id, _) = key_manager + let commitment_mask_key = key_manager .get_next_key(TransactionKeyManagerBranch::CommitmentMask.get_branch_key()) .await .unwrap(); - let (script_key_id, public_script_key) = key_manager - .get_next_key(TransactionKeyManagerBranch::ScriptKey.get_branch_key()) - .await - .unwrap(); + let script_key_id = KeyId::Derived { + branch: TransactionKeyManagerBranch::CommitmentMask.get_branch_key(), + label: TransactionKeyManagerLabel::ScriptKey.get_branch_key(), + index: commitment_mask_key.key_id.managed_index().unwrap(), + }; + let public_script_key = key_manager.get_public_key_at_key_id(&script_key_id).await.unwrap(); + let encrypted_data = key_manager - .encrypt_data_for_recovery(&spending_key_id, None, sender.amount.as_u64()) + .encrypt_data_for_recovery( + &commitment_mask_key.key_id, + None, + sender.amount.as_u64(), + PaymentId::Empty, + ) .await .unwrap(); let mut output = WalletOutput::new( TransactionOutputVersion::get_current_version(), sender.amount, - spending_key_id.clone(), + commitment_mask_key.key_id.clone(), sender.features.clone(), sender.script.clone(), inputs!(public_script_key), @@ -201,6 +215,7 @@ pub async fn test_db_backend(backend: T) { Covenant::default(), encrypted_data, MicroMinotari::zero(), + PaymentId::Empty, &key_manager, ) .await @@ -208,7 +223,7 @@ pub async fn test_db_backend(backend: T) { let output_message = TransactionOutput::metadata_signature_message(&output); output.metadata_signature = key_manager .get_receiver_partial_metadata_signature( - &spending_key_id, + &commitment_mask_key.key_id, &sender.amount.into(), &sender.sender_offset_public_key, &sender.ephemeral_public_nonce, @@ -230,7 +245,8 @@ pub async fn test_db_backend(backend: T) { let mut inbound_txs = Vec::new(); for i in 0..messages.len() { - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -299,11 +315,13 @@ pub async fn test_db_backend(backend: T) { ); for i in 0..messages.len() { - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let dest_address = TariAddress::new( + let dest_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -331,6 +349,7 @@ pub async fn test_db_backend(backend: T) { mined_height: None, mined_in_block: None, mined_timestamp: None, + payment_id: Some(PaymentId::Empty), }); db.complete_outbound_transaction(outbound_txs[i].tx_id, completed_txs[i].clone()) .unwrap(); @@ -417,7 +436,8 @@ pub async fn test_db_backend(backend: T) { } else { panic!("Should have found cancelled completed tx"); } - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -468,7 +488,8 @@ pub async fn test_db_backend(backend: T) { let mut cancelled_txs = db.get_cancelled_pending_inbound_transactions().unwrap(); assert_eq!(cancelled_txs.len(), 1); assert!(cancelled_txs.remove(&999u64.into()).is_some()); - let address = TariAddress::new( + let address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -579,6 +600,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(5), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); @@ -608,6 +630,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(6), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); @@ -637,6 +660,7 @@ async fn import_tx_and_read_it_from_db() { TransactionDirection::Inbound, Some(7), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); 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 95ed908ad6..92922c7b27 100644 --- a/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs +++ b/base_layer/wallet/tests/transaction_service_tests/transaction_protocols.rs @@ -47,12 +47,12 @@ use minotari_wallet::{ sqlite_db::TransactionServiceSqliteDatabase, }, }, - util::{wallet_identity::WalletIdentity, watch::Watch}, + util::watch::Watch, }; use rand::{rngs::OsRng, RngCore}; use tari_common::configuration::Network; use tari_common_types::{ - tari_address::TariAddress, + tari_address::{TariAddress, TariAddressFeatures}, transaction::{TransactionDirection, TransactionStatus, TxId}, }; use tari_comms::{ @@ -78,7 +78,7 @@ use tari_core::{ types::Signature as SignatureProto, }, transactions::{ - key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, + key_manager::{create_memory_db_key_manager, MemoryDbKeyManager, TransactionKeyManagerInterface}, tari_amount::{uT, MicroMinotari, T}, test_helpers::schema_to_transaction, transaction_components::OutputFeatures, @@ -146,7 +146,7 @@ pub async fn setup() -> ( let (oms_event_publisher, _) = broadcast::channel(200); let output_manager_service_handle = OutputManagerHandle::new(oms_request_sender, oms_event_publisher); - let core_key_manager_service_handle = create_memory_db_key_manager(); + let core_key_manager_service_handle = create_memory_db_key_manager().unwrap(); let (outbound_message_requester, mock_outbound_service) = create_outbound_service_mock(100); let outbound_mock_state = mock_outbound_service.get_state(); @@ -158,7 +158,22 @@ pub async fn setup() -> ( let shutdown = Shutdown::new(); let network = Network::LocalNet; let consensus_manager = ConsensusManager::builder(network).build().unwrap(); - let wallet_identity = WalletIdentity::new(client_node_identity, network); + let view_key = core_key_manager_service_handle.get_view_key().await.unwrap(); + let comms_key = core_key_manager_service_handle.get_comms_key().await.unwrap(); + let spend_key = core_key_manager_service_handle.get_spend_key().await.unwrap(); + let interactive_features = if spend_key == comms_key { + TariAddressFeatures::create_interactive_and_one_sided() + } else { + TariAddressFeatures::create_one_sided_only() + }; + let one_sided_tari_address = TariAddress::new_dual_address( + view_key.pub_key.clone(), + comms_key.pub_key, + network, + TariAddressFeatures::create_one_sided_only(), + ); + let interactive_tari_address = + TariAddress::new_dual_address(view_key.pub_key, spend_key.pub_key, network, interactive_features); let resources = TransactionServiceResources { db, output_manager_service: output_manager_service_handle, @@ -166,7 +181,9 @@ pub async fn setup() -> ( outbound_message_service: outbound_message_requester, connectivity: wallet_connectivity.clone(), event_publisher: ts_event_publisher, - wallet_identity, + one_sided_tari_address, + interactive_tari_address, + node_identity: client_node_identity.clone(), consensus_manager, factories: CryptoFactories::default(), config: TransactionServiceConfig { @@ -196,7 +213,7 @@ pub async fn add_transaction_to_database( status: Option, db: TransactionDatabase, ) { - let key_manager_handle = create_memory_db_key_manager(); + let key_manager_handle = create_memory_db_key_manager().unwrap(); let uo0 = make_input(&mut OsRng, 10 * amount, &OutputFeatures::default(), &key_manager_handle).await; let (txs1, _uou1) = schema_to_transaction( &[txn_schema!(from: vec![uo0.clone()], to: vec![amount])], @@ -217,6 +234,7 @@ pub async fn add_transaction_to_database( TransactionDirection::Outbound, None, None, + None, ) .unwrap(); db.insert_completed_transaction(tx_id, completed_tx1).unwrap(); diff --git a/base_layer/wallet/tests/utxo_scanner/mod.rs b/base_layer/wallet/tests/utxo_scanner/mod.rs index ba46a85ec7..e985560f59 100644 --- a/base_layer/wallet/tests/utxo_scanner/mod.rs +++ b/base_layer/wallet/tests/utxo_scanner/mod.rs @@ -38,7 +38,7 @@ use minotari_wallet::{ sqlite_utilities::run_migration_and_create_sqlite_connection, }, transaction_service::handle::TransactionServiceRequest, - util::{wallet_identity::WalletIdentity, watch::Watch}, + util::watch::Watch, utxo_scanner_service::{ handle::{UtxoScannerEvent, UtxoScannerHandle}, service::{ScannedBlock, UtxoScannerService}, @@ -61,7 +61,7 @@ use tari_core::{ blocks::BlockHeader, proto::base_node::{ChainMetadata, TipInfoResponse}, transactions::{ - key_manager::{create_memory_db_key_manager, MemoryDbKeyManager}, + key_manager::{create_memory_db_key_manager, MemoryDbKeyManager, TransactionKeyManagerInterface}, tari_amount::MicroMinotari, transaction_components::{OutputFeatures, WalletOutput}, CryptoFactories, @@ -102,6 +102,7 @@ pub struct UtxoScannerTestInterface { } async fn setup( + key_manager: MemoryDbKeyManager, mode: UtxoScannerMode, previous_db: Option>, recovery_message: Option, @@ -149,7 +150,6 @@ async fn setup( task::spawn(oms_mock.run()); let node_identity = build_node_identity(PeerFeatures::COMMUNICATION_NODE); - let wallet_identity = WalletIdentity::new(node_identity, Network::default()); let (event_sender, _) = broadcast::channel(200); let temp_dir = tempdir().unwrap(); @@ -193,20 +193,28 @@ async fn setup( scanner_service_builder.with_recovery_message(message); } - let scanner_service = scanner_service_builder.build_with_resources( - wallet_db.clone(), - comms_connectivity, - wallet_connectivity_mock, - oms_handle, - ts_handle, - wallet_identity, - factories, - shutdown.to_signal(), - event_sender, - base_node_service_handle, - one_sided_message_watch_receiver, - recovery_message_watch_receiver, + let view_key = key_manager.get_view_key().await.unwrap(); + let tari_address = TariAddress::new_dual_address_with_default_features( + view_key.pub_key, + node_identity.public_key().clone(), + Network::default(), ); + let scanner_service = scanner_service_builder + .build_with_resources::( + wallet_db.clone(), + comms_connectivity, + wallet_connectivity_mock, + oms_handle, + ts_handle, + tari_address, + factories, + shutdown.to_signal(), + event_sender, + base_node_service_handle, + one_sided_message_watch_receiver, + recovery_message_watch_receiver, + ) + .await; UtxoScannerTestInterface { scanner_service: Some(scanner_service), @@ -289,7 +297,8 @@ async fn generate_block_headers_and_utxos( #[tokio::test] async fn test_utxo_scanner_recovery() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager().unwrap(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch @@ -299,7 +308,6 @@ async fn test_utxo_scanner_recovery() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; - let key_manager = create_memory_db_key_manager(); let TestBlockData { block_headers, wallet_outputs, @@ -380,7 +388,8 @@ async fn test_utxo_scanner_recovery() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_recovery_with_restart() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager().unwrap(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch @@ -398,7 +407,6 @@ async fn test_utxo_scanner_recovery_with_restart() { const BIRTHDAY_OFFSET: u64 = 5; const SYNC_INTERRUPT: u64 = 6; - let key_manager = create_memory_db_key_manager(); let TestBlockData { block_headers, wallet_outputs, @@ -477,6 +485,7 @@ async fn test_utxo_scanner_recovery_with_restart() { current_height: _, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { assert_eq!(message, "Output found on blockchain during Wallet Recovery".to_string()); @@ -487,6 +496,7 @@ async fn test_utxo_scanner_recovery_with_restart() { test_interface.shutdown_signal.trigger(); let mut test_interface2 = setup( + key_manager.clone(), UtxoScannerMode::Recovery, Some(test_interface.wallet_db), Some("recovery".to_string()), @@ -544,6 +554,7 @@ async fn test_utxo_scanner_recovery_with_restart() { current_height: _, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { assert_eq!(message, "recovery".to_string()); @@ -554,7 +565,8 @@ async fn test_utxo_scanner_recovery_with_restart() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_recovery_with_restart_and_reorg() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager().unwrap(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch @@ -564,7 +576,6 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; const SYNC_INTERRUPT: u64 = 6; - let key_manager = create_memory_db_key_manager(); let TestBlockData { mut block_headers, mut wallet_outputs, @@ -634,7 +645,7 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { .filter(|u| u.height <= 4) .collect::>(); - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let TestBlockData { block_headers: new_block_headers, wallet_outputs: new_wallet_outputs, @@ -645,7 +656,14 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { utxos_by_block.append(&mut new_utxos_by_block); wallet_outputs.extend(new_wallet_outputs); - let mut test_interface2 = setup(UtxoScannerMode::Recovery, Some(test_interface.wallet_db), None, None).await; + let mut test_interface2 = setup( + key_manager.clone(), + UtxoScannerMode::Recovery, + Some(test_interface.wallet_db), + None, + None, + ) + .await; test_interface2 .rpc_service_state .set_utxos_by_block(utxos_by_block.clone()); @@ -725,7 +743,8 @@ async fn test_utxo_scanner_recovery_with_restart_and_reorg() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_scanned_block_cache_clearing() { - let mut test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager().unwrap(); + let mut test_interface = setup(key_manager.clone(), UtxoScannerMode::Recovery, None, None, None).await; for h in 0u64..800u64 { let num_outputs = if h % 2 == 1 { Some(1) } else { None }; @@ -755,7 +774,6 @@ async fn test_utxo_scanner_scanned_block_cache_clearing() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; - let key_manager = create_memory_db_key_manager(); let TestBlockData { block_headers, wallet_outputs: _wallet_outputs, @@ -848,7 +866,9 @@ async fn test_utxo_scanner_scanned_block_cache_clearing() { #[tokio::test] #[allow(clippy::too_many_lines)] async fn test_utxo_scanner_one_sided_payments() { + let key_manager = create_memory_db_key_manager().unwrap(); let mut test_interface = setup( + key_manager.clone(), UtxoScannerMode::Scanning, None, None, @@ -864,7 +884,6 @@ async fn test_utxo_scanner_one_sided_payments() { const NUM_BLOCKS: u64 = 11; const BIRTHDAY_OFFSET: u64 = 5; - let key_manager = create_memory_db_key_manager(); let TestBlockData { mut block_headers, wallet_outputs, @@ -955,6 +974,7 @@ async fn test_utxo_scanner_one_sided_payments() { current_height: _, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { assert_eq!(message, "Output found on blockchain during Wallet Recovery".to_string()); @@ -1053,6 +1073,7 @@ async fn test_utxo_scanner_one_sided_payments() { current_height: h, mined_timestamp: _, scanned_output: _, + payment_id: _, } = req { println!("{:?}", h); @@ -1063,7 +1084,8 @@ async fn test_utxo_scanner_one_sided_payments() { #[tokio::test] async fn test_birthday_timestamp_over_chain() { - let test_interface = setup(UtxoScannerMode::Recovery, None, None, None).await; + let key_manager = create_memory_db_key_manager().unwrap(); + let test_interface = setup(key_manager, UtxoScannerMode::Recovery, None, None, None).await; let cipher_seed = CipherSeed::new(); // get birthday duration, in seconds, from unix epoch @@ -1073,7 +1095,7 @@ async fn test_birthday_timestamp_over_chain() { const NUM_BLOCKS: u64 = 10; const BIRTHDAY_OFFSET: u64 = 5; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let TestBlockData { block_headers, utxos_by_block, diff --git a/base_layer/wallet_ffi/Cargo.toml b/base_layer/wallet_ffi/Cargo.toml index 74cfb6c2d6..b386493a9d 100644 --- a/base_layer/wallet_ffi/Cargo.toml +++ b/base_layer/wallet_ffi/Cargo.toml @@ -50,8 +50,12 @@ tari_test_utils = { workspace = true } tari_service_framework = { workspace = true } tari_core = { workspace = true, default-features = false, features = ["base_node"] } borsh = "1.2" +env_logger = "0.7.1" [build-dependencies] cbindgen = "0.24.3" tari_common = { workspace = true, features = ["build"] } tari_features = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tari_target_network_mainnet)', 'cfg(tari_target_network_nextnet)', 'cfg(tari_target_network_testnet)'] } diff --git a/base_layer/wallet_ffi/README.md b/base_layer/wallet_ffi/README.md index f00bdf193a..a58ca97ab4 100644 --- a/base_layer/wallet_ffi/README.md +++ b/base_layer/wallet_ffi/README.md @@ -132,8 +132,8 @@ Install [Rust](https://www.rust-lang.org/tools/install) Install the following tools and system images ```Shell Script -rustup toolchain add nightly-2024-02-04 -rustup default nightly-2024-02-04 +rustup toolchain add nightly-2024-07-07 +rustup default nightly-2024-07-07 rustup component add rustfmt --toolchain nightly rustup component add clippy rustup target add x86_64-apple-ios aarch64-apple-ios # iPhone and emulator cross compiling diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index 7ff55a7bc8..337a07ce86 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -279,7 +279,8 @@ mod test { let db = TransactionDatabase::new(TransactionServiceSqliteDatabase::new(connection, cipher)); let rtp = ReceiverTransactionProtocol::new_placeholder(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -295,11 +296,13 @@ mod test { db.add_pending_inbound_transaction(1u64.into(), inbound_tx.clone()) .unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -322,13 +325,15 @@ mod test { TransactionDirection::Inbound, None, None, + None, ) .unwrap(); db.insert_completed_transaction(2u64.into(), completed_tx.clone()) .unwrap(); let stp = SenderTransactionProtocol::new_placeholder(); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -363,11 +368,13 @@ mod test { .unwrap(); db.reject_completed_transaction(5u64.into(), TxCancellationReason::Unknown) .unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -390,16 +397,19 @@ mod test { TransactionDirection::Inbound, Some(2), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap_or(NaiveDateTime::MIN)), + None, ) .unwrap(); db.insert_completed_transaction(6u64.into(), faux_unconfirmed_tx.clone()) .unwrap(); - let source_address = TariAddress::new( + let source_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); - let destination_address = TariAddress::new( + let destination_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); @@ -422,6 +432,7 @@ mod test { TransactionDirection::Inbound, Some(5), Some(NaiveDateTime::from_timestamp_opt(0, 0).unwrap()), + None, ) .unwrap(); db.insert_completed_transaction(7u64.into(), faux_confirmed_tx.clone()) @@ -455,7 +466,8 @@ mod test { let (connectivity_tx, connectivity_rx) = watch::channel(OnlineStatus::Offline); let (contacts_liveness_events_sender, _) = broadcast::channel(250); let contacts_liveness_events = contacts_liveness_events_sender.subscribe(); - let comms_address = TariAddress::new( + let comms_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)), Network::LocalNet, ); diff --git a/base_layer/wallet_ffi/src/error.rs b/base_layer/wallet_ffi/src/error.rs index 8b4926a40b..016cb4f3a2 100644 --- a/base_layer/wallet_ffi/src/error.rs +++ b/base_layer/wallet_ffi/src/error.rs @@ -33,7 +33,10 @@ use tari_crypto::{ signatures::SchnorrSignatureError, tari_utilities::{hex::HexError, ByteArrayError}, }; -use tari_key_manager::error::{KeyManagerError, MnemonicError}; +use tari_key_manager::{ + error::{KeyManagerError, MnemonicError}, + key_manager_service::KeyManagerServiceError, +}; use thiserror::Error; const LOG_TARGET: &str = "wallet_ffi::error"; @@ -63,6 +66,7 @@ pub enum InterfaceError { #[derive(Debug, Clone)] pub struct LibWalletError { pub code: i32, + #[allow(dead_code)] pub message: String, } @@ -398,11 +402,13 @@ impl From for LibWalletError { fn from(e: TariAddressError) -> Self { error!(target: LOG_TARGET, "{}", format!("{:?}", e)); match e { - TariAddressError::InvalidNetworkOrChecksum => Self { + TariAddressError::InvalidNetwork => Self { code: 701, message: format!("{:?}", e), }, - TariAddressError::CannotRecoverPublicKey => Self { + TariAddressError::CannotRecoverPublicKey | + TariAddressError::CannotRecoverFeature | + TariAddressError::CannotRecoverNetwork => Self { code: 702, message: format!("{:?}", e), }, @@ -414,6 +420,23 @@ impl From for LibWalletError { code: 704, message: format!("{:?}", e), }, + + TariAddressError::InvalidFeatures => Self { + code: 705, + message: format!("{:?}", e), + }, + TariAddressError::InvalidChecksum => Self { + code: 706, + message: format!("{:?}", e), + }, + TariAddressError::InvalidAddressString => Self { + code: 707, + message: format!("{:?}", e), + }, + TariAddressError::InvalidCharacter => Self { + code: 708, + message: format!("{:?}", e), + }, } } } @@ -510,3 +533,13 @@ impl From for LibWalletError { } } } + +impl From for LibWalletError { + fn from(err: KeyManagerServiceError) -> Self { + error!(target: LOG_TARGET, "{}", format!("{:?}", err)); + Self { + code: 458, + message: format!("{:?}", err), + } + } +} diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 80e25a8ac8..f978fe353e 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -27,7 +27,7 @@ //! becoming a `CompletedTransaction` with the `Completed` status. This means that the transaction has been //! negotiated between the parties and is now ready to be broadcast to the Base Layer. The funds are still encumbered //! as pending because the transaction has not been mined yet. -//! 3. The finalized `CompletedTransaction` will be sent back to the the receiver so that they have a copy. +//! 3. The finalized `CompletedTransaction` will be sent back to the receiver so that they have a copy. //! 4. The wallet will broadcast the `CompletedTransaction` to a Base Node to be added to the mempool. Its status will //! move from `Completed` to `Broadcast`. //! 5. Wait until the transaction is mined. The `CompleteTransaction` status will then move from `Broadcast` to `Mined` @@ -49,7 +49,6 @@ use core::ptr; use std::{ - boxed::Box, convert::{TryFrom, TryInto}, ffi::{CStr, CString}, fmt::{Display, Formatter}, @@ -67,7 +66,7 @@ use error::LibWalletError; use ffi_basenode_state::TariBaseNodeState; use itertools::Itertools; use libc::{c_char, c_int, c_uchar, c_uint, c_ulonglong, c_ushort, c_void}; -use log::{LevelFilter, *}; +use log::*; use log4rs::{ append::{ file::FileAppender, @@ -119,10 +118,10 @@ use tari_common::{ network_check::set_network_if_choice_valid, }; use tari_common_types::{ - emoji::emoji_set, + emoji::{emoji_set, EMOJI}, tari_address::{TariAddress, TariAddressError}, transaction::{TransactionDirection, TransactionStatus, TxId}, - types::{ComAndPubSignature, Commitment, PublicKey, SignatureWithDomain}, + types::{ComAndPubSignature, Commitment, PublicKey, RangeProof, SignatureWithDomain}, wallet_types::WalletType, }; use tari_comms::{ @@ -131,14 +130,27 @@ use tari_comms::{ transports::MemoryTransport, types::CommsPublicKey, }; -use tari_comms_dht::{store_forward::SafConfig, DbConnectionUrl, DhtConfig}; +use tari_comms_dht::{ + store_forward::SafConfig, + DbConnectionUrl, + DhtConfig, + DhtConnectivityConfig, + NetworkDiscoveryConfig, +}; use tari_contacts::contacts_service::{handle::ContactsServiceHandle, types::Contact}; use tari_core::{ borsh::FromBytes, consensus::ConsensusManager, transactions::{ tari_amount::MicroMinotari, - transaction_components::{OutputFeatures, OutputFeaturesVersion, OutputType, RangeProofType, UnblindedOutput}, + transaction_components::{ + encrypted_data::PaymentId, + OutputFeatures, + OutputFeaturesVersion, + OutputType, + RangeProofType, + UnblindedOutput, + }, CryptoFactories, }, }; @@ -189,36 +201,36 @@ mod tasks; const LOG_TARGET: &str = "wallet_ffi"; -pub type TariTransportConfig = tari_p2p::TransportConfig; -pub type TariPublicKey = tari_common_types::types::PublicKey; -pub type TariWalletAddress = tari_common_types::tari_address::TariAddress; +pub type TariTransportConfig = TransportConfig; +pub type TariPublicKey = PublicKey; +pub type TariWalletAddress = TariAddress; pub type TariNodeId = tari_comms::peer_manager::NodeId; pub type TariPrivateKey = tari_common_types::types::PrivateKey; -pub type TariOutputFeatures = tari_core::transactions::transaction_components::OutputFeatures; +pub type TariRangeProof = RangeProof; +pub type TariOutputFeatures = OutputFeatures; pub type TariCommsConfig = tari_p2p::P2pConfig; pub type TariTransactionKernel = tari_core::transactions::transaction_components::TransactionKernel; pub type TariCovenant = tari_core::covenants::Covenant; pub type TariEncryptedOpenings = tari_core::transactions::transaction_components::EncryptedData; -pub type TariComAndPubSignature = tari_common_types::types::ComAndPubSignature; -pub type TariUnblindedOutput = tari_core::transactions::transaction_components::UnblindedOutput; - +pub type TariComAndPubSignature = ComAndPubSignature; +pub type TariUnblindedOutput = UnblindedOutput; pub struct TariUnblindedOutputs(Vec); pub struct TariContacts(Vec); -pub type TariContact = tari_contacts::contacts_service::types::Contact; -pub type TariCompletedTransaction = minotari_wallet::transaction_service::storage::models::CompletedTransaction; +pub type TariContact = Contact; +pub type TariCompletedTransaction = CompletedTransaction; pub type TariTransactionSendStatus = minotari_wallet::transaction_service::handle::TransactionSendStatus; pub type TariFeePerGramStats = minotari_wallet::transaction_service::handle::FeePerGramStatsResponse; pub type TariFeePerGramStat = tari_core::mempool::FeePerGramStat; pub type TariContactsLivenessData = tari_contacts::contacts_service::handle::ContactsLivenessData; pub type TariBalance = minotari_wallet::output_manager_service::service::Balance; -pub type TariMnemonicLanguage = tari_key_manager::mnemonic::MnemonicLanguage; +pub type TariMnemonicLanguage = MnemonicLanguage; pub struct TariCompletedTransactions(Vec); -pub type TariPendingInboundTransaction = minotari_wallet::transaction_service::storage::models::InboundTransaction; -pub type TariPendingOutboundTransaction = minotari_wallet::transaction_service::storage::models::OutboundTransaction; +pub type TariPendingInboundTransaction = InboundTransaction; +pub type TariPendingOutboundTransaction = OutboundTransaction; pub struct TariPendingInboundTransactions(Vec); @@ -289,7 +301,8 @@ pub struct TariUtxo { pub mined_timestamp: u64, pub lock_height: u64, pub status: u8, - pub coinbase_extra: Vec, + pub coinbase_extra: *const c_char, + pub payment_id: *const c_char, } impl From for TariUtxo { @@ -318,7 +331,14 @@ impl From for TariUtxo { OutputStatus::SpentMinedUnconfirmed => 9, OutputStatus::NotStored => 10, }, - coinbase_extra: x.wallet_output.features.coinbase_extra, + coinbase_extra: CString::new(x.wallet_output.features.coinbase_extra.to_hex()) + .expect("failed to obtain hex from a commitment") + .into_raw(), + payment_id: CString::new( + String::from_utf8(x.payment_id.as_bytes()).unwrap_or_else(|_| "Invalid".to_string()), + ) + .expect("failed to obtain string from a payment id") + .into_raw(), } } } @@ -460,7 +480,7 @@ impl TariVector { } #[allow(dead_code)] - fn to_utxo_vec(&self) -> Result, InterfaceError> { + pub fn to_utxo_vec(&self) -> Result, InterfaceError> { if self.tag != TariTypeTag::Utxo { return Err(InterfaceError::InvalidArgument(format!( "expecting Utxo, got {}", @@ -811,7 +831,7 @@ pub unsafe extern "C" fn byte_vector_destroy(bytes: *mut ByteVector) { /// /// # Safety /// None -// converting between here is fine as its used to clamp the the array to length +// converting between here is fine as its used to clamp the array to length #[allow(clippy::cast_possible_wrap)] #[no_mangle] pub unsafe extern "C" fn byte_vector_get_at(ptr: *mut ByteVector, position: c_uint, error_out: *mut c_int) -> c_uchar { @@ -958,6 +978,35 @@ pub unsafe extern "C" fn public_key_get_bytes(pk: *mut TariPublicKey, error_out: Box::into_raw(Box::new(bytes)) } +/// Converts public key to emoji encoding +/// +/// ## Arguments +/// `pk` - The pointer to a TariPublicKey +/// `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 +/// `*mut c_char` - Returns a pointer to a char array. Note that it returns empty +/// if emoji is null or if there was an error creating the emoji string from public key +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn public_key_get_emoji_encoding(pk: *mut TariPublicKey, error_out: *mut c_int) -> *mut c_char { + let mut error = 0; + let mut result = CString::new("").expect("Blank CString will not fail."); + ptr::swap(error_out, &mut error as *mut c_int); + if pk.is_null() { + error = LibWalletError::from(InterfaceError::NullError("public key".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return CString::into_raw(result); + } + let pk_bytes = (*pk).to_vec(); + let emoji_string = pk_bytes.iter().map(|b| EMOJI[*b as usize]).collect::(); + result = CString::new(emoji_string).expect("Emoji will not fail."); + CString::into_raw(result) +} + /// Creates a TariPublicKey from a TariPrivateKey /// /// ## Arguments @@ -1113,52 +1162,11 @@ pub unsafe extern "C" fn tari_address_get_bytes( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } else { - bytes.0 = (*address).to_bytes().to_vec(); + bytes.0 = (*address).to_vec(); } Box::into_raw(Box::new(bytes)) } -/// Creates a TariWalletAddress from a TariPrivateKey -/// -/// ## Arguments -/// `secret_key` - The pointer to a TariPrivateKey -/// `network` - an u8 indicating the network -/// `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 -/// `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress -/// -/// # Safety -/// The ```private_key_destroy``` method must be called when finished with a private key to prevent a memory leak -// casting here is network is a u8 -#[allow(clippy::cast_possible_truncation)] -#[no_mangle] -pub unsafe extern "C" fn tari_address_from_private_key( - secret_key: *mut TariPrivateKey, - network: c_uint, - error_out: *mut c_int, -) -> *mut TariWalletAddress { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - if secret_key.is_null() { - error = LibWalletError::from(InterfaceError::NullError("secret_key".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - } - let key = PublicKey::from_secret_key(&(*secret_key)); - let network = match (network as u8).try_into() { - Ok(network) => network, - Err(_) => { - error = LibWalletError::from(InterfaceError::InvalidArgument("network".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - }, - }; - let address = TariWalletAddress::new(key, network); - Box::into_raw(Box::new(address)) -} - /// Creates a TariWalletAddress from a char array /// /// ## Arguments @@ -1173,7 +1181,7 @@ pub unsafe extern "C" fn tari_address_from_private_key( /// # Safety /// The ```public_key_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak #[no_mangle] -pub unsafe extern "C" fn tari_address_from_hex( +pub unsafe extern "C" fn tari_address_from_base58( address: *const c_char, error_out: *mut c_int, ) -> *mut TariWalletAddress { @@ -1197,11 +1205,11 @@ pub unsafe extern "C" fn tari_address_from_hex( } } - let address = TariWalletAddress::from_hex(key_str.as_str()); + let address = TariWalletAddress::from_base58(key_str.as_str()); match address { Ok(address) => Box::into_raw(Box::new(address)), Err(e) => { - error!(target: LOG_TARGET, "Error creating a Tari Address from Hex: {:?}", e); + error!(target: LOG_TARGET, "Error creating a Tari Address from Base58 string: {:?}", e); error = LibWalletError::from(e).code; ptr::swap(error_out, &mut error as *mut c_int); ptr::null_mut() @@ -1272,6 +1280,186 @@ pub unsafe extern "C" fn tari_address_network(address: *mut TariWalletAddress, e CString::into_raw(result) } +/// Returns the u8 representation of a TariWalletAddress's network +/// +/// ## Arguments +/// `address` - The pointer to a TariWalletAddress +/// `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 +/// `u8` - Returns u8 representing the network. On failure, returns 0. This may be valid so always check the error out +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn tari_address_network_u8(address: *mut TariWalletAddress, error_out: *mut c_int) -> u8 { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if address.is_null() { + error = LibWalletError::from(InterfaceError::NullError("address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + address + .as_ref() + .expect("Address should not be empty") + .network() + .as_byte() +} + +/// Returns the u8 representation of a TariWalletAddress's checksum +/// +/// ## Arguments +/// `address` - The pointer to a TariWalletAddress +/// `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 +/// `u8` - Returns u8 representing the checksum.. On failure, returns 0. This may be valid so always check the error out +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn tari_address_checksum_u8(address: *mut TariWalletAddress, error_out: *mut c_int) -> u8 { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if address.is_null() { + error = LibWalletError::from(InterfaceError::NullError("address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + address + .as_ref() + .expect("Address should not be empty") + .calculate_checksum() +} + +/// Creates a char array from a TariWalletAddress's features +/// +/// ## Arguments +/// `address` - The pointer to a TariWalletAddress +/// `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 +/// `*mut c_char` - Returns a pointer to a char array. Note that it returns empty +/// if there was an error from TariWalletAddress +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn tari_address_features(address: *mut TariWalletAddress, error_out: *mut c_int) -> *mut c_char { + let mut error = 0; + let mut result = CString::new("").expect("Blank CString will not fail."); + ptr::swap(error_out, &mut error as *mut c_int); + if address.is_null() { + error = LibWalletError::from(InterfaceError::NullError("address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return CString::into_raw(result); + } + let features_string = address + .as_ref() + .expect("Address should not be empty") + .features() + .to_string(); + result = CString::new(features_string).expect("string will not fail."); + CString::into_raw(result) +} + +/// Creates a char array from a TariWalletAddress's features +/// +/// ## Arguments +/// `address` - The pointer to a TariWalletAddress +/// `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 +/// u8` - Returns u8 representing the features. On failure, returns 0. This may be valid so always check the error out +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn tari_address_features_u8(address: *mut TariWalletAddress, error_out: *mut c_int) -> u8 { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if address.is_null() { + error = LibWalletError::from(InterfaceError::NullError("address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + } + address + .as_ref() + .expect("Address should not be empty") + .features() + .as_u8() +} + +/// Creates a public key from a TariWalletAddress's view key +/// +/// ## Arguments +/// `address` - The pointer to a TariWalletAddress +/// `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 +/// `*mut TariPublicKey` - Returns a pointer to a TariPublicKey. Note that it returns null if there is no key present +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn tari_address_view_key( + address: *mut TariWalletAddress, + error_out: *mut c_int, +) -> *mut TariPublicKey { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if address.is_null() { + error = LibWalletError::from(InterfaceError::NullError("address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let view_key = address.as_ref().expect("Address should not be empty").public_view_key(); + match view_key { + Some(key) => Box::into_raw(Box::new(key.clone())), + None => { + debug!(target: LOG_TARGET, "No view key present on Tari Address"); + ptr::null_mut() + }, + } +} + +/// Creates a public key from a TariWalletAddress's spend key +/// +/// ## Arguments +/// `address` - The pointer to a TariWalletAddress +/// `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 +/// `*mut TariPublicKey` - Returns a pointer to a TariPublicKey. Note that it returns null +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn tari_address_spend_key( + address: *mut TariWalletAddress, + error_out: *mut c_int, +) -> *mut TariPublicKey { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if address.is_null() { + error = LibWalletError::from(InterfaceError::NullError("address".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let spend_key = address + .as_ref() + .expect("Address should not be empty") + .public_spend_key(); + Box::into_raw(Box::new(spend_key.clone())) +} + /// Creates a TariWalletAddress from a char array in emoji format /// /// ## Arguments @@ -1311,6 +1499,24 @@ pub unsafe extern "C" fn emoji_id_to_tari_address( } } +/// Does a lookup of the emoji character for a byte, using the emoji encoding of tari +/// +/// ## Arguments +/// `byte` - u8 byte to lookup the emoji for +/// +/// ## Returns +/// `*mut c_char` - Returns a pointer to a char array. Note that it returns empty +/// if emoji is null or if there was an error creating the emoji string from TariWalletAddress +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn byte_to_emoji(byte: u8) -> *mut c_char { + let emoji = EMOJI[byte as usize].to_string(); + let result = CString::new(emoji).expect("Emoji will not fail."); + CString::into_raw(result) +} + /// -------------------------------------------------------------------------------------------- /// /// /// ------------------------------- ComAndPubSignature Signature ---------------------------------------/// @@ -1497,6 +1703,7 @@ pub unsafe extern "C" fn create_tari_unblinded_output( encrypted_data: *mut TariEncryptedOpenings, minimum_value_promise: c_ulonglong, script_lock_height: c_ulonglong, + range_proof: *mut TariRangeProof, error_out: *mut c_int, ) -> *mut TariUnblindedOutput { let mut error = 0; @@ -1591,7 +1798,17 @@ pub unsafe extern "C" fn create_tari_unblinded_output( let encrypted_data = if encrypted_data.is_null() { TariEncryptedOpenings::default() } else { - *encrypted_data + (*encrypted_data).clone() + }; + + let proof = if range_proof.is_null() { + error = LibWalletError::from(InterfaceError::NullError("range_proof".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else if *range_proof == TariRangeProof::default() { + None + } else { + Some((*range_proof).clone()) }; let unblinded_output = UnblindedOutput::new_current_version( @@ -1607,6 +1824,7 @@ pub unsafe extern "C" fn create_tari_unblinded_output( covenant, encrypted_data, minimum_value_promise.into(), + proof, ); Box::into_raw(Box::new(unblinded_output)) @@ -1771,7 +1989,7 @@ pub unsafe extern "C" fn unblinded_outputs_get_length( /// /// # Safety /// The ```contact_destroy``` method must be called when finished with a TariContact to prevent a memory leak -// converting between here is fine as its used to clamp the the array to length +// converting between here is fine as its used to clamp the array to length #[allow(clippy::cast_possible_wrap)] #[no_mangle] pub unsafe extern "C" fn unblinded_outputs_get_at( @@ -1812,22 +2030,75 @@ pub unsafe extern "C" fn unblinded_outputs_destroy(outputs: *mut TariUnblindedOu } } +/// Get the TariUnblindedOutputs from a TariWallet +/// +/// ## Arguments +/// `wallet` - The TariWallet pointer +/// `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 +/// `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if +/// wallet is null +/// +/// # Safety +/// The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a +/// memory leak +#[no_mangle] +pub unsafe extern "C" fn wallet_get_unspent_outputs( + wallet: *mut TariWallet, + error_out: *mut c_int, +) -> *mut TariUnblindedOutputs { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let mut outputs = Vec::new(); + if wallet.is_null() { + error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + + let received_outputs = (*wallet) + .runtime + .block_on((*wallet).wallet.output_manager_service.get_unspent_outputs()); + match received_outputs { + Ok(rec_outputs) => { + for output in rec_outputs { + let unblinded = (*wallet).runtime.block_on(UnblindedOutput::from_wallet_output( + output.wallet_output, + &(*wallet).wallet.key_manager_service, + )); + match unblinded { + Ok(uo) => { + outputs.push(uo); + }, + Err(e) => { + error = LibWalletError::from(WalletError::TransactionError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + } + } + Box::into_raw(Box::new(TariUnblindedOutputs(outputs))) + }, + Err(e) => { + error = LibWalletError::from(WalletError::OutputManagerError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + /// Import an external UTXO into the wallet as a non-rewindable (i.e. non-recoverable) output. This will add a spendable /// UTXO (as EncumberedToBeReceived) and create a faux completed transaction to record the event. /// /// ## Arguments /// `wallet` - The TariWallet pointer -/// `amount` - The value of the UTXO in MicroMinotari -/// `spending_key` - The private spending key +/// `output` - The pointer to a TariUnblindedOutput +/// `range_proof` - The pointer to a TariRangeProof. If the 'range_proof_type' is 'RevealedValue', a default range proof +/// can be provided. /// `source_address` - The tari address of the source of the transaction -/// `features` - Options for an output's structure or use -/// `metadata_signature` - UTXO signature with the script offset private key, k_O -/// `sender_offset_public_key` - Tari script offset pubkey, K_O -/// `script_private_key` - Tari script private key, k_S, is used to create the script signature -/// `covenant` - The covenant that will be executed when spending this output /// `message` - The message that the transaction will have -/// `encrypted_data` - Encrypted data. -/// `minimum_value_promise` - The minimum value of the commitment that is proven by the range proof /// `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. /// @@ -1852,6 +2123,11 @@ pub unsafe extern "C" fn wallet_import_external_utxo_as_non_rewindable( ptr::swap(error_out, &mut error as *mut c_int); return 0; } + if output.is_null() { + error = LibWalletError::from(InterfaceError::NullError("output".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + }; let source_address = if source_address.is_null() { TariWalletAddress::default() } else { @@ -1897,68 +2173,7 @@ pub unsafe extern "C" fn wallet_import_external_utxo_as_non_rewindable( }, } } - -/// Get the TariUnblindedOutputs from a TariWallet -/// -/// ## Arguments -/// `wallet` - The TariWallet pointer -/// `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 -/// `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if -/// wallet is null -/// -/// # Safety -/// The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a -/// memory leak -#[no_mangle] -pub unsafe extern "C" fn wallet_get_unspent_outputs( - wallet: *mut TariWallet, - error_out: *mut c_int, -) -> *mut TariUnblindedOutputs { - let mut error = 0; - ptr::swap(error_out, &mut error as *mut c_int); - let mut outputs = Vec::new(); - if wallet.is_null() { - error = LibWalletError::from(InterfaceError::NullError("wallet".to_string())).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - } - - let received_outputs = (*wallet) - .runtime - .block_on((*wallet).wallet.output_manager_service.get_unspent_outputs()); - match received_outputs { - Ok(rec_outputs) => { - for output in rec_outputs { - let unblinded = (*wallet).runtime.block_on(UnblindedOutput::from_wallet_output( - output.wallet_output, - &(*wallet).wallet.key_manager_service, - )); - match unblinded { - Ok(uo) => { - outputs.push(uo); - }, - Err(e) => { - error = LibWalletError::from(WalletError::TransactionError(e)).code; - ptr::swap(error_out, &mut error as *mut c_int); - return ptr::null_mut(); - }, - } - } - Box::into_raw(Box::new(TariUnblindedOutputs(outputs))) - }, - Err(e) => { - error = LibWalletError::from(WalletError::OutputManagerError(e)).code; - ptr::swap(error_out, &mut error as *mut c_int); - ptr::null_mut() - }, - } -} - /// -------------------------------------------------------------------------------------------- /// - /// -------------------------------- Private Key ----------------------------------------------- /// /// Creates a TariPrivateKey from a ByteVector @@ -2079,24 +2294,158 @@ pub unsafe extern "C" fn private_key_from_hex(key: *const c_char, error_out: *mu ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } else { - match CStr::from_ptr(key).to_str() { + match CStr::from_ptr(key).to_str() { + Ok(v) => { + key_str = v.to_owned(); + }, + _ => { + error = LibWalletError::from(InterfaceError::PointerError("key".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + } + + let secret_key = TariPrivateKey::from_hex(key_str.as_str()); + + match secret_key { + Ok(secret_key) => Box::into_raw(Box::new(secret_key)), + Err(e) => { + error!(target: LOG_TARGET, "Error creating a Public Key from Hex: {:?}", e); + + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + +/// -------------------------------------------------------------------------------------------- /// +/// -------------------------------- Range Proof ----------------------------------------------- /// + +/// Creates a default TariRangeProof +/// +/// ## Arguments +/// None. +/// +/// ## Returns +/// `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() +/// if bytes is null or if there was an error creating the TariRangeProof from bytes +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_default() -> *mut TariRangeProof { + Box::into_raw(Box::default()) +} + +/// Gets a TariRangeProof from a TariUnblindedOutput +/// +/// ## Arguments +/// `unblinded_output` - The pointer to a TariUnblindedOutput +/// `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 +/// `*mut TariRangeProof` - Returns a TariRangeProof, note that it returns ptr::null_mut() +/// if TariUnblindedOutput is null or position is invalid +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[allow(clippy::cast_possible_wrap)] +#[no_mangle] +pub unsafe extern "C" fn range_proof_get( + unblinded_output: *mut TariUnblindedOutput, + error_out: *mut c_int, +) -> *mut TariRangeProof { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if unblinded_output.is_null() { + error = LibWalletError::from(InterfaceError::NullError("output_with_proof".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + Box::into_raw(Box::new((*unblinded_output).clone().range_proof.unwrap_or_default())) +} + +/// Creates a TariRangeProof from a ByteVector +/// +/// ## Arguments +/// `bytes` - The pointer to a ByteVector +/// `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 +/// `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() +/// if bytes is null or if there was an error creating the TariRangeProof from bytes +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_from_bytes( + bytes_ptr: *mut ByteVector, + error_out: *mut c_int, +) -> *mut TariRangeProof { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + if bytes_ptr.is_null() { + error = LibWalletError::from(InterfaceError::NullError("bytes".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } + let v = (*bytes_ptr).0.clone(); + let range_proof = TariRangeProof::from_canonical_bytes(&v); + match range_proof { + Ok(proof) => Box::into_raw(Box::new(proof)), + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + ptr::null_mut() + }, + } +} + +/// Creates a TariRangeProof from a char array +/// +/// ## Arguments +/// `char_ptr` - The pointer to a char array which is hex encoded +/// `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 +/// `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() +/// if proof is null or if there was an error creating the TariRangeProof from proof +/// +/// # Safety +/// The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_from_hex(char_ptr: *const c_char, error_out: *mut c_int) -> *mut TariRangeProof { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let proof_hex; + if char_ptr.is_null() { + error = LibWalletError::from(InterfaceError::NullError("proof".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else { + match CStr::from_ptr(char_ptr).to_str() { Ok(v) => { - key_str = v.to_owned(); + proof_hex = v.to_owned(); }, _ => { - error = LibWalletError::from(InterfaceError::PointerError("key".to_string())).code; + error = LibWalletError::from(InterfaceError::PointerError("proof".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); }, }; } - let secret_key = TariPrivateKey::from_hex(key_str.as_str()); + let range_proof = TariRangeProof::from_hex(proof_hex.as_str()); - match secret_key { - Ok(secret_key) => Box::into_raw(Box::new(secret_key)), + match range_proof { + Ok(proof) => Box::into_raw(Box::new(proof)), Err(e) => { - error!(target: LOG_TARGET, "Error creating a Public Key from Hex: {:?}", e); + error!(target: LOG_TARGET, "Error creating a range proof from Hex: {:?}", e); error = LibWalletError::from(e).code; ptr::swap(error_out, &mut error as *mut c_int); @@ -2105,6 +2454,54 @@ pub unsafe extern "C" fn private_key_from_hex(key: *const c_char, error_out: *mu } } +/// Gets a ByteVector from a TariRangeProof +/// +/// ## Arguments +/// `proof` - The pointer to a TariRangeProof +/// `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 +/// `*mut ByteVectror` - Returns a pointer to a ByteVector. Note that it returns ptr::null_mut() +/// if pk is null +/// +/// # Safety +/// The ```byte_vector_destroy``` must be called when finished with a ByteVector to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn range_proof_get_bytes( + proof_ptr: *mut TariRangeProof, + error_out: *mut c_int, +) -> *mut ByteVector { + let mut error = 0; + ptr::swap(error_out, &mut error as *mut c_int); + let mut bytes = ByteVector(Vec::new()); + if proof_ptr.is_null() { + error = LibWalletError::from(InterfaceError::NullError("pk".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + } else { + bytes.0 = (*proof_ptr).to_vec(); + } + Box::into_raw(Box::new(bytes)) +} + +/// Frees memory for a TariRangeProof +/// +/// ## Arguments +/// `proof` - The pointer to a TariRangeProof +/// +/// ## Returns +/// `()` - Does not return a value, equivalent to void in C +/// +/// # Safety +/// None +#[no_mangle] +pub unsafe extern "C" fn range_proof_destroy(proof_ptr: *mut TariRangeProof) { + if !proof_ptr.is_null() { + drop(Box::from_raw(proof_ptr)) + } +} + /// -------------------------------------------------------------------------------------------- /// /// --------------------------------------- Covenant --------------------------------------------/// @@ -2877,7 +3274,7 @@ pub unsafe extern "C" fn contacts_get_length(contacts: *mut TariContacts, error_ /// /// # Safety /// The ```contact_destroy``` method must be called when finished with a TariContact to prevent a memory leak -// converting between here is fine as its used to clamp the the array to length +// converting between here is fine as its used to clamp the array to length #[allow(clippy::cast_possible_wrap)] #[no_mangle] pub unsafe extern "C" fn contacts_get_at( @@ -3178,7 +3575,7 @@ pub unsafe extern "C" fn completed_transactions_get_length( /// # Safety /// The ```completed_transaction_destroy``` method must be called when finished with a TariCompletedTransaction to /// prevent a memory leak -// converting between here is fine as its used to clamp the the array to length +// converting between here is fine as its used to clamp the array to length #[allow(clippy::cast_possible_wrap)] #[no_mangle] pub unsafe extern "C" fn completed_transactions_get_at( @@ -3271,7 +3668,7 @@ pub unsafe extern "C" fn pending_outbound_transactions_get_length( /// # Safety /// The ```pending_outbound_transaction_destroy``` method must be called when finished with a /// TariPendingOutboundTransaction to prevent a memory leak -// converting between here is fine as its used to clamp the the array to length +// converting between here is fine as its used to clamp the array to length #[allow(clippy::cast_possible_wrap)] #[no_mangle] pub unsafe extern "C" fn pending_outbound_transactions_get_at( @@ -3363,7 +3760,7 @@ pub unsafe extern "C" fn pending_inbound_transactions_get_length( /// # Safety /// The ```pending_inbound_transaction_destroy``` method must be called when finished with a /// TariPendingOutboundTransaction to prevent a memory leak -// converting between here is fine as its used to clamp the the array to length +// converting between here is fine as its used to clamp the array to length #[allow(clippy::cast_possible_wrap)] #[no_mangle] pub unsafe extern "C" fn pending_inbound_transactions_get_at( @@ -4738,7 +5135,6 @@ pub unsafe extern "C" fn transport_config_destroy(transport: *mut TariTransportC /// `database_path` - The database path char array pointer which. This is the folder path where the /// database files will be created and the application has write access to /// `discovery_timeout_in_secs`: specify how long the Discovery Timeout for the wallet is. -/// `network`: name of network to connect to. Valid values are: esmeralda, dibbler, igor, localnet, mainnet, stagenet /// `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. /// @@ -4844,6 +5240,9 @@ pub unsafe extern "C" fn comms_config_create( max_concurrent_inbound_tasks: 25, max_concurrent_outbound_tasks: 50, dht: DhtConfig { + num_neighbouring_nodes: 5, + num_random_nodes: 1, + minimize_connections: true, discovery_request_timeout: Duration::from_secs(discovery_timeout_in_secs), database_url: DbConnectionUrl::File(dht_database_path), auto_join: true, @@ -4853,15 +5252,24 @@ pub unsafe extern "C" fn comms_config_create( auto_request: true, ..Default::default() }, + network_discovery: NetworkDiscoveryConfig { + min_desired_peers: 16, + initial_peer_sync_delay: Some(Duration::from_secs(25)), + ..Default::default() + }, + connectivity: DhtConnectivityConfig { + update_interval: Duration::from_secs(5 * 60), + minimum_desired_tcpv4_node_ratio: 0.0, + ..Default::default() + }, ..Default::default() }, allow_test_addresses: true, listener_liveness_allowlist_cidrs: StringList::new(), listener_liveness_max_sessions: 0, - user_agent: format!("tari/mobile_wallet/{}", env!("CARGO_PKG_VERSION")), rpc_max_simultaneous_sessions: 0, rpc_max_sessions_per_peer: 0, - listener_liveness_check_interval: None, + listener_self_liveness_check_interval: None, }; Box::into_raw(Box::new(config)) @@ -5510,6 +5918,7 @@ pub unsafe extern "C" fn wallet_create( }, }; + let user_agent = format!("tari/wallet_ffi/{}", env!("CARGO_PKG_VERSION")); let w = runtime.block_on(Wallet::start( wallet_config, peer_seeds, @@ -5525,12 +5934,20 @@ pub unsafe extern "C" fn wallet_create( key_manager_backend, shutdown.to_signal(), master_seed, - WalletType::Software, + Some(WalletType::default()), + user_agent, )); match w { Ok(w) => { - let wallet_address = TariAddress::new(w.comms.node_identity().public_key().clone(), w.network.as_network()); + let wallet_address = match runtime.block_on(async { w.get_wallet_interactive_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; // Start Callback Handler let callback_handler = CallbackHandler::new( @@ -5700,9 +6117,9 @@ pub unsafe extern "C" fn wallet_get_balance(wallet: *mut TariWallet, error_out: /// * `page_size` - A number of items per page, /// * `sorting` - An enum representing desired sorting, /// * `dust_threshold` - A value filtering threshold. Outputs whose values are <= `dust_threshold` are not listed in the -/// result. +/// result. /// * `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. +/// Functions as an out parameter. /// /// ## Returns /// `*mut TariVector` - Returns a struct with an array pointer, length and capacity (needed for proper destruction @@ -5789,11 +6206,11 @@ pub unsafe extern "C" fn wallet_get_utxos( /// ## Arguments /// * `wallet` - The TariWallet pointer, /// * `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. +/// Functions as an out parameter. /// /// ## Returns /// `*mut TariVector` - Returns a struct with an array pointer, length and capacity (needed for proper destruction -/// after use). +/// after use). /// /// ## States /// 0 - Unspent @@ -5863,7 +6280,7 @@ pub unsafe extern "C" fn wallet_get_all_utxos(wallet: *mut TariWallet, error_ptr /// * `number_of_splits` - The number of times to split the amount /// * `fee_per_gram` - The transaction fee /// * `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. +/// Functions as an out parameter. /// /// ## Returns /// `c_ulonglong` - Returns the transaction id. @@ -5933,7 +6350,7 @@ pub unsafe extern "C" fn wallet_coin_split( /// (see `Commitment::to_hex()`) /// * `fee_per_gram` - The transaction fee /// * `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. +/// Functions as an out parameter. /// /// ## Returns /// `TariVector` - Returns the transaction id. @@ -6668,6 +7085,7 @@ pub unsafe extern "C" fn wallet_send_transaction( fee_per_gram: c_ulonglong, message: *const c_char, one_sided: bool, + payment_id_string: *const c_char, error_out: *mut c_int, ) -> c_ulonglong { let mut error = 0; @@ -6713,16 +7131,27 @@ pub unsafe extern "C" fn wallet_send_transaction( _ => { error = LibWalletError::from(InterfaceError::NullError("message".to_string())).code; ptr::swap(error_out, &mut error as *mut c_int); - message_string = CString::new("") - .expect("Blank CString will not fail") - .to_str() - .expect("CString.to_str() will not fail") - .to_owned(); + return 0; }, } }; if one_sided { + let payment_id = if payment_id_string.is_null() { + PaymentId::Empty + } else { + match CStr::from_ptr(payment_id_string).to_str() { + Ok(v) => { + let bytes = v.as_bytes().to_vec(); + PaymentId::Open(bytes) + }, + _ => { + error = LibWalletError::from(InterfaceError::NullError("payment_id".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return 0; + }, + } + }; match (*wallet).runtime.block_on( (*wallet) .wallet @@ -6734,6 +7163,7 @@ pub unsafe extern "C" fn wallet_send_transaction( OutputFeatures::default(), MicroMinotari::from(fee_per_gram), message_string, + payment_id, ), ) { Ok(tx_id) => tx_id.as_u64(), @@ -7209,10 +7639,22 @@ pub unsafe extern "C" fn wallet_get_cancelled_transactions( for tx in completed_transactions.values() { completed.push(tx.clone()); } - let wallet_address = TariAddress::new( - (*wallet).wallet.comms.node_identity().public_key().clone(), - (*wallet).wallet.network.as_network(), - ); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + let wallet_address = match runtime.block_on(async { (*wallet).wallet.get_wallet_interactive_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; for tx in inbound_transactions.values() { let mut inbound_tx = CompletedTransaction::from(tx.clone()); inbound_tx.destination_address = wallet_address.clone(); @@ -7491,8 +7933,22 @@ pub unsafe extern "C" fn wallet_get_cancelled_transaction_by_id( return ptr::null_mut(); }, }; - let network = (*wallet).wallet.network.as_network(); - let address = TariWalletAddress::new((*wallet).wallet.comms.node_identity().public_key().clone(), network); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + let address = match runtime.block_on(async { (*wallet).wallet.get_wallet_interactive_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; if let Some(tx) = outbound_transactions.remove(&transaction_id) { let mut outbound_tx = CompletedTransaction::from(tx); outbound_tx.source_address = address; @@ -7560,9 +8016,22 @@ pub unsafe extern "C" fn wallet_get_tari_address( ptr::swap(error_out, &mut error as *mut c_int); return ptr::null_mut(); } - let network = (*wallet).wallet.network.as_network(); - let pk = (*wallet).wallet.comms.node_identity().public_key().clone(); - let address = TariWalletAddress::new(pk, network); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; + let address = match runtime.block_on(async { (*wallet).wallet.get_wallet_interactive_address().await }) { + Ok(address) => address, + Err(e) => { + error = LibWalletError::from(e).code; + ptr::swap(error_out, &mut error as *mut c_int); + return ptr::null_mut(); + }, + }; Box::into_raw(Box::new(address)) } @@ -8164,11 +8633,28 @@ pub unsafe extern "C" fn wallet_start_recovery( }; recovery_task_builder.with_recovery_message(message_str); } - - let mut recovery_task = recovery_task_builder - .with_peers(peer_public_keys) - .with_retry_limit(10) - .build_with_wallet(&(*wallet).wallet, shutdown_signal); + let runtime = match Runtime::new() { + Ok(r) => r, + Err(e) => { + error = LibWalletError::from(InterfaceError::TokioError(e.to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + }; + let mut recovery_task = match runtime.block_on(async { + recovery_task_builder + .with_peers(peer_public_keys) + .with_retry_limit(10) + .build_with_wallet(&(*wallet).wallet, shutdown_signal) + .await + }) { + Ok(v) => v, + Err(e) => { + error = LibWalletError::from(WalletError::KeyManagerServiceError(e)).code; + ptr::swap(error_out, &mut error as *mut c_int); + return false; + }, + }; let event_stream = recovery_task.get_event_receiver(); let recovery_join_handle = (*wallet).runtime.spawn(recovery_task.run()); @@ -8722,20 +9208,14 @@ pub unsafe extern "C" fn contacts_handle_destroy(contacts_handle: *mut ContactsS /// ------------------------------------------------------------------------------------------ /// #[cfg(test)] mod test { - use std::{ - ffi::CString, - path::Path, - str::{from_utf8, FromStr}, - sync::Mutex, - }; + use std::{path::Path, str::from_utf8, sync::Mutex}; - use libc::{c_char, c_uchar, c_uint}; use minotari_wallet::{ storage::sqlite_utilities::run_migration_and_create_sqlite_connection, transaction_service::handle::TransactionSendStatus, }; use once_cell::sync::Lazy; - use tari_common_types::{emoji, transaction::TransactionStatus, types::PrivateKey}; + use tari_common_types::{emoji, tari_address::TariAddressFeatures, types::PrivateKey}; use tari_comms::peer_manager::PeerFeatures; use tari_contacts::contacts_service::types::{Direction, Message, MessageMetadata}; use tari_core::{ @@ -8745,7 +9225,7 @@ mod test { test_helpers::{create_test_input, create_wallet_output_with_data, TestParams}, }, }; - use tari_key_manager::{mnemonic::MnemonicLanguage, mnemonic_wordlists}; + use tari_key_manager::mnemonic_wordlists; use tari_p2p::initialization::MESSAGING_PROTOCOL_ID; use tari_script::script; use tari_test_utils::random; @@ -8914,27 +9394,32 @@ mod test { type_of((*tx).clone()), std::any::type_name::() ); - assert_eq!((*tx).status, TransactionStatus::OneSidedUnconfirmed); - let mut lock = CALLBACK_STATE_FFI.lock().unwrap(); - lock.scanned_tx_unconfirmed_callback_called = true; - let mut error = 0; - let error_ptr = &mut error as *mut c_int; - let kernel = completed_transaction_get_transaction_kernel(tx, error_ptr); - let excess_hex_ptr = transaction_kernel_get_excess_hex(kernel, error_ptr); - let excess_hex = CString::from_raw(excess_hex_ptr).to_str().unwrap().to_owned(); - assert!(!excess_hex.is_empty()); - let nonce_hex_ptr = transaction_kernel_get_excess_public_nonce_hex(kernel, error_ptr); - let nonce_hex = CString::from_raw(nonce_hex_ptr).to_str().unwrap().to_owned(); - assert!(!nonce_hex.is_empty()); - let sig_hex_ptr = transaction_kernel_get_excess_signature_hex(kernel, error_ptr); - let sig_hex = CString::from_raw(sig_hex_ptr).to_str().unwrap().to_owned(); - assert!(!sig_hex.is_empty()); - string_destroy(excess_hex_ptr as *mut c_char); - string_destroy(sig_hex_ptr as *mut c_char); - string_destroy(nonce_hex_ptr); - transaction_kernel_destroy(kernel); - drop(lock); - completed_transaction_destroy(tx); + match (*tx).status { + TransactionStatus::Imported => {}, + TransactionStatus::OneSidedUnconfirmed => { + let mut lock = CALLBACK_STATE_FFI.lock().unwrap(); + lock.scanned_tx_unconfirmed_callback_called = true; + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + let kernel = completed_transaction_get_transaction_kernel(tx, error_ptr); + let excess_hex_ptr = transaction_kernel_get_excess_hex(kernel, error_ptr); + let excess_hex = CString::from_raw(excess_hex_ptr).to_str().unwrap().to_owned(); + assert!(!excess_hex.is_empty()); + let nonce_hex_ptr = transaction_kernel_get_excess_public_nonce_hex(kernel, error_ptr); + let nonce_hex = CString::from_raw(nonce_hex_ptr).to_str().unwrap().to_owned(); + assert!(!nonce_hex.is_empty()); + let sig_hex_ptr = transaction_kernel_get_excess_signature_hex(kernel, error_ptr); + let sig_hex = CString::from_raw(sig_hex_ptr).to_str().unwrap().to_owned(); + assert!(!sig_hex.is_empty()); + string_destroy(excess_hex_ptr as *mut c_char); + string_destroy(sig_hex_ptr as *mut c_char); + string_destroy(nonce_hex_ptr); + transaction_kernel_destroy(kernel); + drop(lock); + completed_transaction_destroy(tx); + }, + _ => panic!("Invalid transaction status"), + } } unsafe extern "C" fn transaction_send_result_callback(_tx_id: c_ulonglong, status: *mut TransactionSendStatus) { @@ -9029,6 +9514,62 @@ mod test { } } + #[test] + fn test_emoji_convert() { + unsafe { + let byte = 0u8; + let emoji_ptr = byte_to_emoji(byte); + let emoji = CStr::from_ptr(emoji_ptr); + + assert_eq!(emoji.to_str().unwrap(), EMOJI[0].to_string()); + + let byte = 50u8; + let emoji_ptr = byte_to_emoji(byte); + let emoji = CStr::from_ptr(emoji_ptr); + + assert_eq!(emoji.to_str().unwrap(), EMOJI[50].to_string()); + + let byte = 125u8; + let emoji_ptr = byte_to_emoji(byte); + let emoji = CStr::from_ptr(emoji_ptr); + + assert_eq!(emoji.to_str().unwrap(), EMOJI[125].to_string()); + } + } + + #[test] + fn test_address_getters() { + unsafe { + let mut rng = rand::thread_rng(); + let view_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + let spend_key = PublicKey::from_secret_key(&PrivateKey::random(&mut rng)); + + let address = TariAddress::new_dual_address( + view_key.clone(), + spend_key.clone(), + Network::Esmeralda, + TariAddressFeatures::create_one_sided_only(), + ); + let test_address = Box::into_raw(Box::new(address.clone())); + + let mut error = 0; + let error_ptr = &mut error as *mut c_int; + let ffi_features = tari_address_features_u8(test_address, error_ptr); + assert_eq!(address.features().as_u8(), ffi_features); + assert_eq!(*error_ptr, 0, "No error expected"); + + let ffi_checksum = tari_address_checksum_u8(test_address, error_ptr); + assert_eq!(address.calculate_checksum(), ffi_checksum); + assert_eq!(*error_ptr, 0, "No error expected"); + + let ffi_network = tari_address_network_u8(test_address, error_ptr); + assert_eq!(address.network() as u8, ffi_network); + assert_eq!(*error_ptr, 0, "No error expected"); + + tari_address_destroy(test_address); + } + } + #[test] fn test_emoji_set() { unsafe { @@ -9214,36 +9755,29 @@ mod test { let private_key = private_key_generate(); let public_key = public_key_from_private_key(private_key, error_ptr); assert_eq!(error, 0); - let address = tari_address_from_private_key(private_key, 0x26, error_ptr); - assert_eq!(error, 0); let private_bytes = private_key_get_bytes(private_key, error_ptr); assert_eq!(error, 0); let public_bytes = public_key_get_bytes(public_key, error_ptr); assert_eq!(error, 0); - let address_bytes = tari_address_get_bytes(address, error_ptr); - assert_eq!(error, 0); let private_key_length = byte_vector_get_length(private_bytes, error_ptr); assert_eq!(error, 0); let public_key_length = byte_vector_get_length(public_bytes, error_ptr); assert_eq!(error, 0); - let tari_address_length = byte_vector_get_length(address_bytes, error_ptr); + let public_key_emoji = public_key_get_emoji_encoding(public_key, error_ptr); assert_eq!(error, 0); + let emoji = CStr::from_ptr(public_key_emoji); + let rust_string = emoji.to_str().unwrap().to_string(); + let chars = rust_string.chars().collect::>(); + + assert_eq!(chars.len(), 32); + assert_eq!(private_key_length, 32); assert_eq!(public_key_length, 32); - assert_eq!(tari_address_length, 33); assert_ne!((*private_bytes), (*public_bytes)); - let emoji = tari_address_to_emoji_id(address, error_ptr) as *mut c_char; - let emoji_str = CStr::from_ptr(emoji).to_str().unwrap(); - assert!(TariAddress::from_emoji_string(emoji_str).is_ok()); - let address_emoji = emoji_id_to_tari_address(emoji, error_ptr); - assert_eq!((*address), (*address_emoji)); private_key_destroy(private_key); public_key_destroy(public_key); - tari_address_destroy(address_emoji); - tari_address_destroy(address); byte_vector_destroy(public_bytes); byte_vector_destroy(private_bytes); - byte_vector_destroy(address_bytes); } } @@ -9301,6 +9835,8 @@ mod test { #[test] fn test_encrypted_data_filled() { + use tari_common_types::types::PrivateKey; + unsafe { let mut error = 0; let error_ptr = &mut error as *mut c_int; @@ -9309,8 +9845,14 @@ mod test { let commitment = Commitment::from_public_key(&PublicKey::from_secret_key(&spending_key)); let encryption_key = PrivateKey::random(&mut OsRng); let amount = MicroMinotari::from(123456); - let encrypted_data = - TariEncryptedOpenings::encrypt_data(&encryption_key, &commitment, amount, &spending_key).unwrap(); + let encrypted_data = TariEncryptedOpenings::encrypt_data( + &encryption_key, + &commitment, + amount, + &spending_key, + PaymentId::Empty, + ) + .unwrap(); let encrypted_data_bytes = encrypted_data.to_byte_vec(); let encrypted_data_1 = Box::into_raw(Box::new(encrypted_data)); @@ -9455,7 +9997,11 @@ mod test { let mut error = 0; let error_ptr = &mut error as *mut c_int; let test_contact_private_key = private_key_generate(); - let test_address = tari_address_from_private_key(test_contact_private_key, 0x10, error_ptr); + let key = PublicKey::from_secret_key(&(*test_contact_private_key)); + let test_address = Box::into_raw(Box::new(TariWalletAddress::new_single_address_with_interactive_only( + key, + Network::default(), + ))); let test_str = "Test Contact"; let test_contact_str = CString::new(test_str).unwrap(); let test_contact_alias: *const c_char = CString::into_raw(test_contact_str) as *const c_char; @@ -9468,7 +10014,7 @@ mod test { let contact_address = contact_get_tari_address(test_contact, error_ptr); let contact_key_bytes = tari_address_get_bytes(contact_address, error_ptr); let contact_bytes_len = byte_vector_get_length(contact_key_bytes, error_ptr); - assert_eq!(contact_bytes_len, 33); + assert_eq!(contact_bytes_len, 35); contact_destroy(test_contact); tari_address_destroy(test_address); private_key_destroy(test_contact_private_key); @@ -9483,7 +10029,10 @@ mod test { let mut error = 0; let error_ptr = &mut error as *mut c_int; let test_contact_private_key = private_key_generate(); - let test_contact_address = tari_address_from_private_key(test_contact_private_key, 0x00, error_ptr); + let key = PublicKey::from_secret_key(&(*test_contact_private_key)); + let test_contact_address = Box::into_raw(Box::new( + TariWalletAddress::new_single_address_with_interactive_only(key, Network::default()), + )); let test_str = "Test Contact"; let test_contact_str = CString::new(test_str).unwrap(); let test_contact_alias: *const c_char = CString::into_raw(test_contact_str) as *const c_char; @@ -10188,7 +10737,10 @@ mod test { .unwrap(); assert_eq!(output.value.as_u64(), utxo.value); assert_eq!(output.features.maturity, utxo.lock_height); - assert_eq!(output.features.coinbase_extra, utxo.coinbase_extra); + assert_eq!( + output.features.coinbase_extra.to_hex(), + CStr::from_ptr(utxo.coinbase_extra).to_str().unwrap() + ); } println!(); destroy_tari_vector(outputs); @@ -11015,7 +11567,7 @@ mod test { let mut error = 0; let error_ptr = &mut error as *mut c_int; // Test the consistent features case - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let utxo_1 = runtime .block_on(create_wallet_output_with_data( script!(Nop), @@ -11033,6 +11585,7 @@ mod test { .block_on(key_manager.get_private_key(&utxo_1.script_key_id)) .unwrap(); let spending_key_ptr = Box::into_raw(Box::new(spending_key)); + let range_proof_ptr = Box::into_raw(Box::new(utxo_1.range_proof.clone().unwrap_or_default())); let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); 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())); @@ -11056,6 +11609,7 @@ mod test { encrypted_data_ptr, minimum_value_promise, 0, + range_proof_ptr, error_ptr, ); @@ -11161,7 +11715,10 @@ mod test { error_ptr, ); - // Test the consistent features case + let source_address_ptr = Box::into_raw(Box::default()); + let message_ptr = CString::into_raw(CString::new("For my friend").unwrap()) as *const c_char; + + // Test import with bulletproof range proof let utxo_1 = runtime .block_on(create_wallet_output_with_data( script!(Nop), @@ -11171,70 +11728,200 @@ mod test { key_manager, )) .unwrap(); - let amount = utxo_1.value.as_u64(); + // Test all range proof methods; convenient because we have the data + { + // - Range proof from hex + let proof_char_ptr = + CString::into_raw(CString::new(utxo_1.range_proof.as_ref().unwrap().to_hex()).unwrap()) + as *const c_char; + let ptr_a = range_proof_from_hex(proof_char_ptr, error_ptr); + // - Range proof from bytes + let proof_bytes_ptr = + Box::into_raw(Box::new(ByteVector(utxo_1.range_proof.as_ref().unwrap().to_vec()))); + let ptr_b = range_proof_from_bytes(proof_bytes_ptr, error_ptr); + // - Verify + let ptr_a_bytes = range_proof_get_bytes(ptr_a, error_ptr); + let ptr_b_bytes = range_proof_get_bytes(ptr_b, error_ptr); + for i in 0..utxo_1.range_proof.as_ref().unwrap().0.len() { + let byte_a = byte_vector_get_at(ptr_a_bytes, i.try_into().unwrap(), error_ptr); + let byte_b = byte_vector_get_at(ptr_b_bytes, i.try_into().unwrap(), error_ptr); + assert_eq!(byte_a, byte_b); + } + // - Cleanup + string_destroy(proof_char_ptr as *mut c_char); + byte_vector_destroy(proof_bytes_ptr); + byte_vector_destroy(ptr_a_bytes); + byte_vector_destroy(ptr_b_bytes); + range_proof_destroy(ptr_a); + range_proof_destroy(ptr_b); + }; + let amount = utxo_1.value.as_u64(); let spending_key = runtime .block_on(key_manager.get_private_key(&utxo_1.spending_key_id)) .unwrap(); let script_private_key = runtime .block_on(key_manager.get_private_key(&utxo_1.script_key_id)) .unwrap(); - let spending_key_ptr = Box::into_raw(Box::new(spending_key)); - let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); - 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(script_private_key)); - let covenant_ptr = Box::into_raw(Box::new(utxo_1.covenant.clone())); - let encrypted_data_ptr = Box::into_raw(Box::new(utxo_1.encrypted_data)); + let spending_key_ptr_1 = Box::into_raw(Box::new(spending_key)); + let proof_ptr_1 = Box::into_raw(Box::new(utxo_1.range_proof.clone().unwrap_or_default())); + let features_ptr_1 = Box::into_raw(Box::new(utxo_1.features.clone())); + let metadata_signature_ptr_1 = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); + let sender_offset_public_key_ptr_1 = Box::into_raw(Box::new(utxo_1.sender_offset_public_key.clone())); + let script_private_key_ptr_1 = Box::into_raw(Box::new(script_private_key)); + let covenant_ptr_1 = Box::into_raw(Box::new(utxo_1.covenant.clone())); + let encrypted_data_ptr_1 = Box::into_raw(Box::new(utxo_1.encrypted_data)); let minimum_value_promise = utxo_1.minimum_value_promise.as_u64(); - let message_ptr = CString::into_raw(CString::new("For my friend").unwrap()) as *const c_char; - let script_ptr = CString::into_raw(CString::new(script!(Nop).to_hex()).unwrap()) as *const c_char; - let input_data_ptr = CString::into_raw(CString::new(utxo_1.input_data.to_hex()).unwrap()) as *const c_char; + let script_ptr_1 = CString::into_raw(CString::new(script!(Nop).to_hex()).unwrap()) as *const c_char; + let input_data_ptr_1 = + CString::into_raw(CString::new(utxo_1.input_data.to_hex()).unwrap()) as *const c_char; - let tari_utxo = create_tari_unblinded_output( + let tari_utxo_ptr_1 = create_tari_unblinded_output( amount, - spending_key_ptr, - features_ptr, - script_ptr, - input_data_ptr, - metadata_signature_ptr, - sender_offset_public_key_ptr, - script_private_key_ptr, - covenant_ptr, - encrypted_data_ptr, + spending_key_ptr_1, + features_ptr_1, + script_ptr_1, + input_data_ptr_1, + metadata_signature_ptr_1, + sender_offset_public_key_ptr_1, + script_private_key_ptr_1, + covenant_ptr_1, + encrypted_data_ptr_1, + minimum_value_promise, + 0, + proof_ptr_1, + error_ptr, + ); + let tx_id_1 = wallet_import_external_utxo_as_non_rewindable( + wallet_ptr, + tari_utxo_ptr_1, + source_address_ptr, + message_ptr, + error_ptr, + ); + + assert_eq!(error, 0); + assert!(tx_id_1 > 0); + + // Test import with revealed value range proof + let features = OutputFeatures { + version: OutputFeaturesVersion::V0, + output_type: Default::default(), + maturity: 0, + coinbase_extra: vec![], + sidechain_feature: None, + range_proof_type: RangeProofType::RevealedValue, + }; + let utxo_2 = runtime + .block_on(create_wallet_output_with_data( + script!(Nop), + features, + &runtime.block_on(TestParams::new(key_manager)), + MicroMinotari(12345u64), + key_manager, + )) + .unwrap(); + + let amount = utxo_2.value.as_u64(); + let spending_key = runtime + .block_on(key_manager.get_private_key(&utxo_2.spending_key_id)) + .unwrap(); + let script_private_key = runtime + .block_on(key_manager.get_private_key(&utxo_2.script_key_id)) + .unwrap(); + let spending_key_ptr_2 = Box::into_raw(Box::new(spending_key)); + let features_ptr_2 = Box::into_raw(Box::new(utxo_2.features.clone())); + let proof_ptr_2 = range_proof_default(); + let metadata_signature_ptr_2 = Box::into_raw(Box::new(utxo_2.metadata_signature.clone())); + let sender_offset_public_key_ptr_2 = Box::into_raw(Box::new(utxo_2.sender_offset_public_key.clone())); + let script_private_key_ptr_2 = Box::into_raw(Box::new(script_private_key)); + let covenant_ptr_2 = Box::into_raw(Box::new(utxo_2.covenant.clone())); + let encrypted_data_ptr_2 = Box::into_raw(Box::new(utxo_2.encrypted_data)); + let minimum_value_promise = utxo_2.minimum_value_promise.as_u64(); + let script_ptr_2 = CString::into_raw(CString::new(script!(Nop).to_hex()).unwrap()) as *const c_char; + let input_data_ptr_2 = + CString::into_raw(CString::new(utxo_2.input_data.to_hex()).unwrap()) as *const c_char; + + let tari_utxo_ptr_2 = create_tari_unblinded_output( + amount, + spending_key_ptr_2, + features_ptr_2, + script_ptr_2, + input_data_ptr_2, + metadata_signature_ptr_2, + sender_offset_public_key_ptr_2, + script_private_key_ptr_2, + covenant_ptr_2, + encrypted_data_ptr_2, minimum_value_promise, 0, + proof_ptr_2, error_ptr, ); - let tx_id = wallet_import_external_utxo_as_non_rewindable( + let tx_id_2 = wallet_import_external_utxo_as_non_rewindable( wallet_ptr, - tari_utxo, + tari_utxo_ptr_2, source_address_ptr, message_ptr, error_ptr, ); assert_eq!(error, 0); - assert!(tx_id > 0); + assert!(tx_id_2 > 0); + + let outputs_vec = wallet_get_all_utxos(wallet_ptr, error_ptr); + let outputs = (*outputs_vec).to_utxo_vec().unwrap(); + assert_eq!(outputs.len(), 2); + + let unspent_outputs_ptr = wallet_get_unspent_outputs(wallet_ptr, error_ptr); + let unblinded_output_ptr_1 = unblinded_outputs_get_at(unspent_outputs_ptr, 0, error_ptr); + let range_proof_ptr_1 = range_proof_get(unblinded_output_ptr_1, error_ptr); + let unblinded_output_ptr_2 = unblinded_outputs_get_at(unspent_outputs_ptr, 1, error_ptr); + let range_proof_ptr_2 = range_proof_get(unblinded_output_ptr_2, error_ptr); - let outputs = wallet_get_unspent_outputs(wallet_ptr, error_ptr); - assert_eq!((*outputs).0.len(), 0); - assert_eq!(unblinded_outputs_get_length(outputs, error_ptr), 0); + assert_eq!((*tari_utxo_ptr_1).spending_key, (*unblinded_output_ptr_1).spending_key); + assert_eq!( + (*tari_utxo_ptr_1).encrypted_data, + (*unblinded_output_ptr_1).encrypted_data + ); + assert_eq!((*proof_ptr_1).0, (*range_proof_ptr_1).0); + assert_eq!((*tari_utxo_ptr_2).spending_key, (*unblinded_output_ptr_2).spending_key); + assert_eq!( + (*tari_utxo_ptr_2).encrypted_data, + (*unblinded_output_ptr_2).encrypted_data + ); + assert_eq!((*proof_ptr_2).0, (*range_proof_ptr_2).0); // Cleanup - tari_unblinded_output_destroy(tari_utxo); - unblinded_outputs_destroy(outputs); + string_destroy(script_ptr_1 as *mut c_char); + string_destroy(input_data_ptr_1 as *mut c_char); + let _covenant = Box::from_raw(covenant_ptr_1); + let _script_private_key = Box::from_raw(script_private_key_ptr_1); + let _sender_offset_public_key = Box::from_raw(sender_offset_public_key_ptr_1); + let _metadata_signature = Box::from_raw(metadata_signature_ptr_1); + let _features = Box::from_raw(features_ptr_1); + range_proof_destroy(proof_ptr_1); + let _spending_key = Box::from_raw(spending_key_ptr_1); + tari_unblinded_output_destroy(tari_utxo_ptr_1); + range_proof_destroy(range_proof_ptr_1); + tari_unblinded_output_destroy(unblinded_output_ptr_1); + + string_destroy(script_ptr_2 as *mut c_char); + string_destroy(input_data_ptr_2 as *mut c_char); + let _covenant = Box::from_raw(covenant_ptr_2); + let _script_private_key = Box::from_raw(script_private_key_ptr_2); + let _sender_offset_public_key = Box::from_raw(sender_offset_public_key_ptr_2); + let _metadata_signature = Box::from_raw(metadata_signature_ptr_2); + let _features = Box::from_raw(features_ptr_2); + range_proof_destroy(proof_ptr_2); + let _spending_key = Box::from_raw(spending_key_ptr_2); + tari_unblinded_output_destroy(tari_utxo_ptr_2); + range_proof_destroy(range_proof_ptr_2); + tari_unblinded_output_destroy(unblinded_output_ptr_2); + string_destroy(message_ptr as *mut c_char); - string_destroy(script_ptr as *mut c_char); - string_destroy(input_data_ptr as *mut c_char); - let _covenant = Box::from_raw(covenant_ptr); - let _script_private_key = Box::from_raw(script_private_key_ptr); - let _sender_offset_public_key = Box::from_raw(sender_offset_public_key_ptr); - let _metadata_signature = Box::from_raw(metadata_signature_ptr); - let _features = Box::from_raw(features_ptr); let _source_address = Box::from_raw(source_address_ptr); - let _spending_key = Box::from_raw(spending_key_ptr); + unblinded_outputs_destroy(unspent_outputs_ptr); let _base_node_peer_public_key = Box::from_raw(base_node_peer_public_key_ptr); string_destroy(base_node_peer_address_ptr as *mut c_char); @@ -11257,7 +11944,7 @@ mod test { let mut error = 0; let error_ptr = &mut error as *mut c_int; - let key_manager = create_memory_db_key_manager(); + let key_manager = create_memory_db_key_manager().unwrap(); let utxo_1 = runtime .block_on(create_wallet_output_with_data( script!(Nop), @@ -11275,6 +11962,7 @@ mod test { .block_on(key_manager.get_private_key(&utxo_1.script_key_id)) .unwrap(); let spending_key_ptr = Box::into_raw(Box::new(spending_key)); + let proof_ptr_1 = Box::into_raw(Box::new(utxo_1.range_proof.clone().unwrap_or_default())); let features_ptr = Box::into_raw(Box::new(utxo_1.features.clone())); let source_address_ptr = Box::into_raw(Box::::default()); let metadata_signature_ptr = Box::into_raw(Box::new(utxo_1.metadata_signature.clone())); @@ -11300,6 +11988,7 @@ mod test { encrypted_data_ptr, minimum_value_promise, 0, + proof_ptr_1, error_ptr, ); let json_string = tari_unblinded_output_to_json(tari_utxo, error_ptr); @@ -11493,7 +12182,10 @@ mod test { // Add some contacts // - Contact for Alice - let bob_wallet_address = TariWalletAddress::new(bob_node_identity.public_key().clone(), Network::LocalNet); + let bob_wallet_address = TariWalletAddress::new_single_address_with_interactive_only( + bob_node_identity.public_key().clone(), + Network::LocalNet, + ); let alice_contact_alias_ptr: *const c_char = CString::into_raw(CString::new("bob").unwrap()) as *const c_char; let alice_contact_address_ptr = Box::into_raw(Box::new(bob_wallet_address.clone())); @@ -11502,8 +12194,10 @@ mod test { assert!(wallet_upsert_contact(alice_wallet_ptr, alice_contact_ptr, error_ptr)); contact_destroy(alice_contact_ptr); // - Contact for Bob - let alice_wallet_address = - TariWalletAddress::new(alice_node_identity.public_key().clone(), Network::LocalNet); + let alice_wallet_address = TariWalletAddress::new_single_address_with_interactive_only( + alice_node_identity.public_key().clone(), + Network::LocalNet, + ); let bob_contact_alias_ptr: *const c_char = CString::into_raw(CString::new("alice").unwrap()) as *const c_char; let bob_contact_address_ptr = Box::into_raw(Box::new(alice_wallet_address.clone())); @@ -11559,9 +12253,11 @@ mod test { alice_wallet_runtime.block_on(alice_wallet_contacts_service.send_message(Message { body: vec![i], metadata: vec![MessageMetadata::default()], - address: bob_wallet_address.clone(), + receiver_address: alice_wallet_address.clone(), + sender_address: bob_wallet_address.clone(), direction: Direction::Outbound, stored_at: u64::from(i), + sent_at: u64::from(i), delivery_confirmation_at: None, read_confirmation_at: None, message_id: vec![i], @@ -11575,9 +12271,11 @@ mod test { bob_wallet_runtime.block_on(bob_wallet_contacts_service.send_message(Message { body: vec![i], metadata: vec![MessageMetadata::default()], - address: alice_wallet_address.clone(), + sender_address: alice_wallet_address.clone(), + receiver_address: bob_wallet_address.clone(), direction: Direction::Outbound, stored_at: u64::from(i), + sent_at: u64::from(i), delivery_confirmation_at: None, read_confirmation_at: None, message_id: vec![i], diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 4588864b98..576a0cc4bf 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -33,6 +33,8 @@ enum TariUtxoSort { */ struct Balance; +struct BulletRangeProof; + struct ByteVector; /** @@ -196,8 +198,6 @@ struct TransportConfig; */ struct UnblindedOutput; -struct Vec_u8; - /** * -------------------------------- Vector ------------------------------------------------ /// */ @@ -320,6 +320,13 @@ typedef struct Covenant TariCovenant; typedef struct EncryptedData TariEncryptedOpenings; +/** + * Specify the range proof + */ +typedef struct BulletRangeProof RangeProof; + +typedef RangeProof TariRangeProof; + typedef struct Contact TariContact; typedef struct ContactsLivenessData TariContactsLivenessData; @@ -349,7 +356,8 @@ struct TariUtxo { uint64_t mined_timestamp; uint64_t lock_height; uint8_t status; - struct Vec_u8 coinbase_extra; + const char *coinbase_extra; + const char *payment_id; }; #ifdef __cplusplus @@ -631,6 +639,24 @@ void public_keys_destroy(struct TariPublicKeys *pks); struct ByteVector *public_key_get_bytes(TariPublicKey *pk, int *error_out); +/** + * Converts public key to emoji encoding + * + * ## Arguments + * `pk` - The pointer to a TariPublicKey + * `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 + * `*mut c_char` - Returns a pointer to a char array. Note that it returns empty + * if emoji is null or if there was an error creating the emoji string from public key + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +char *public_key_get_emoji_encoding(TariPublicKey *pk, + int *error_out); + /** * Creates a TariPublicKey from a TariPrivateKey * @@ -717,25 +743,6 @@ void tari_address_destroy(TariWalletAddress *address); struct ByteVector *tari_address_get_bytes(TariWalletAddress *address, int *error_out); -/** - * Creates a TariWalletAddress from a TariPrivateKey - * - * ## Arguments - * `secret_key` - The pointer to a TariPrivateKey - * `network` - an u8 indicating the network - * `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 - * `*mut TariWalletAddress` - Returns a pointer to a TariWalletAddress - * - * # Safety - * The ```private_key_destroy``` method must be called when finished with a private key to prevent a memory leak - */ -TariWalletAddress *tari_address_from_private_key(TariPrivateKey *secret_key, - unsigned int network, - int *error_out); - /** * Creates a TariWalletAddress from a char array * @@ -751,8 +758,8 @@ TariWalletAddress *tari_address_from_private_key(TariPrivateKey *secret_key, * # Safety * The ```public_key_destroy``` method must be called when finished with a TariWalletAddress to prevent a memory leak */ -TariWalletAddress *tari_address_from_hex(const char *address, - int *error_out); +TariWalletAddress *tari_address_from_base58(const char *address, + int *error_out); /** * Creates a char array from a TariWalletAddress in emoji format @@ -790,6 +797,109 @@ char *tari_address_to_emoji_id(TariWalletAddress *address, char *tari_address_network(TariWalletAddress *address, int *error_out); +/** + * Returns the u8 representation of a TariWalletAddress's network + * + * ## Arguments + * `address` - The pointer to a TariWalletAddress + * `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 + * `u8` - Returns u8 representing the network. On failure, returns 0. This may be valid so always check the error out + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +uint8_t tari_address_network_u8(TariWalletAddress *address, + int *error_out); + +/** + * Returns the u8 representation of a TariWalletAddress's checksum + * + * ## Arguments + * `address` - The pointer to a TariWalletAddress + * `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 + * `u8` - Returns u8 representing the checksum.. On failure, returns 0. This may be valid so always check the error out + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +uint8_t tari_address_checksum_u8(TariWalletAddress *address, + int *error_out); + +/** + * Creates a char array from a TariWalletAddress's features + * + * ## Arguments + * `address` - The pointer to a TariWalletAddress + * `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 + * `*mut c_char` - Returns a pointer to a char array. Note that it returns empty + * if there was an error from TariWalletAddress + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +char *tari_address_features(TariWalletAddress *address, + int *error_out); + +/** + * Creates a char array from a TariWalletAddress's features + * + * ## Arguments + * `address` - The pointer to a TariWalletAddress + * `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 + * u8` - Returns u8 representing the features. On failure, returns 0. This may be valid so always check the error out + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +uint8_t tari_address_features_u8(TariWalletAddress *address, + int *error_out); + +/** + * Creates a public key from a TariWalletAddress's view key + * + * ## Arguments + * `address` - The pointer to a TariWalletAddress + * `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 + * `*mut TariPublicKey` - Returns a pointer to a TariPublicKey. Note that it returns null if there is no key present + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +TariPublicKey *tari_address_view_key(TariWalletAddress *address, + int *error_out); + +/** + * Creates a public key from a TariWalletAddress's spend key + * + * ## Arguments + * `address` - The pointer to a TariWalletAddress + * `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 + * `*mut TariPublicKey` - Returns a pointer to a TariPublicKey. Note that it returns null + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +TariPublicKey *tari_address_spend_key(TariWalletAddress *address, + int *error_out); + /** * Creates a TariWalletAddress from a char array in emoji format * @@ -807,6 +917,21 @@ char *tari_address_network(TariWalletAddress *address, TariWalletAddress *emoji_id_to_tari_address(const char *emoji, int *error_out); +/** + * Does a lookup of the emoji character for a byte, using the emoji encoding of tari + * + * ## Arguments + * `byte` - u8 byte to lookup the emoji for + * + * ## Returns + * `*mut c_char` - Returns a pointer to a char array. Note that it returns empty + * if emoji is null or if there was an error creating the emoji string from TariWalletAddress + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +char *byte_to_emoji(uint8_t byte); + /** * -------------------------------------------------------------------------------------------- /// * @@ -892,6 +1017,7 @@ TariUnblindedOutput *create_tari_unblinded_output(unsigned long long amount, TariEncryptedOpenings *encrypted_data, unsigned long long minimum_value_promise, unsigned long long script_lock_height, + TariRangeProof *range_proof, int *error_out); /** @@ -997,56 +1123,50 @@ TariUnblindedOutput *unblinded_outputs_get_at(struct TariUnblindedOutputs *outpu void unblinded_outputs_destroy(struct TariUnblindedOutputs *outputs); /** - * Import an external UTXO into the wallet as a non-rewindable (i.e. non-recoverable) output. This will add a spendable - * UTXO (as EncumberedToBeReceived) and create a faux completed transaction to record the event. + * Get the TariUnblindedOutputs from a TariWallet * * ## Arguments * `wallet` - The TariWallet pointer - * `amount` - The value of the UTXO in MicroMinotari - * `spending_key` - The private spending key - * `source_address` - The tari address of the source of the transaction - * `features` - Options for an output's structure or use - * `metadata_signature` - UTXO signature with the script offset private key, k_O - * `sender_offset_public_key` - Tari script offset pubkey, K_O - * `script_private_key` - Tari script private key, k_S, is used to create the script signature - * `covenant` - The covenant that will be executed when spending this output - * `message` - The message that the transaction will have - * `encrypted_data` - Encrypted data. - * `minimum_value_promise` - The minimum value of the commitment that is proven by the range proof * `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 - * `c_ulonglong` - Returns the TransactionID of the generated transaction, note that it will be zero if the - * transaction is null + * `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if + * wallet is null * * # Safety - * None + * The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a + * memory leak */ -unsigned long long wallet_import_external_utxo_as_non_rewindable(struct TariWallet *wallet, - TariUnblindedOutput *output, - TariWalletAddress *source_address, - const char *message, - int *error_out); +struct TariUnblindedOutputs *wallet_get_unspent_outputs(struct TariWallet *wallet, + int *error_out); /** - * Get the TariUnblindedOutputs from a TariWallet + * Import an external UTXO into the wallet as a non-rewindable (i.e. non-recoverable) output. This will add a spendable + * UTXO (as EncumberedToBeReceived) and create a faux completed transaction to record the event. * * ## Arguments * `wallet` - The TariWallet pointer + * `output` - The pointer to a TariUnblindedOutput + * `range_proof` - The pointer to a TariRangeProof. If the 'range_proof_type' is 'RevealedValue', a default range proof + * can be provided. + * `source_address` - The tari address of the source of the transaction + * `message` - The message that the transaction will have * `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 - * `*mut TariUnblindedOutputs` - returns the unspent unblinded outputs, note that it returns ptr::null_mut() if - * wallet is null + * `c_ulonglong` - Returns the TransactionID of the generated transaction, note that it will be zero if the + * transaction is null * * # Safety - * The ```unblinded_outputs_destroy``` method must be called when finished with a TariUnblindedOutput to prevent a - * memory leak + * None */ -struct TariUnblindedOutputs *wallet_get_unspent_outputs(struct TariWallet *wallet, - int *error_out); +unsigned long long wallet_import_external_utxo_as_non_rewindable(struct TariWallet *wallet, + TariUnblindedOutput *output, + TariWalletAddress *source_address, + const char *message, + int *error_out); /** * -------------------------------------------------------------------------------------------- /// @@ -1132,6 +1252,109 @@ TariPrivateKey *private_key_generate(void); TariPrivateKey *private_key_from_hex(const char *key, int *error_out); +/** + * -------------------------------------------------------------------------------------------- /// + * -------------------------------- Range Proof ----------------------------------------------- /// + * Creates a default TariRangeProof + * + * ## Arguments + * None. + * + * ## Returns + * `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() + * if bytes is null or if there was an error creating the TariRangeProof from bytes + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_default(void); + +/** + * Gets a TariRangeProof from a TariUnblindedOutput + * + * ## Arguments + * `unblinded_output` - The pointer to a TariUnblindedOutput + * `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 + * `*mut TariRangeProof` - Returns a TariRangeProof, note that it returns ptr::null_mut() + * if TariUnblindedOutput is null or position is invalid + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_get(TariUnblindedOutput *unblinded_output, + int *error_out); + +/** + * Creates a TariRangeProof from a ByteVector + * + * ## Arguments + * `bytes` - The pointer to a ByteVector + * `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 + * `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() + * if bytes is null or if there was an error creating the TariRangeProof from bytes + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_from_bytes(struct ByteVector *bytes_ptr, + int *error_out); + +/** + * Creates a TariRangeProof from a char array + * + * ## Arguments + * `char_ptr` - The pointer to a char array which is hex encoded + * `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 + * `*mut TariRangeProof` - Returns a pointer to a TariRangeProof. Note that it returns ptr::null_mut() + * if proof is null or if there was an error creating the TariRangeProof from proof + * + * # Safety + * The ```range_proof_destroy``` method must be called when finished with a TariRangeProof to prevent a memory leak + */ +TariRangeProof *range_proof_from_hex(const char *char_ptr, + int *error_out); + +/** + * Gets a ByteVector from a TariRangeProof + * + * ## Arguments + * `proof` - The pointer to a TariRangeProof + * `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 + * `*mut ByteVectror` - Returns a pointer to a ByteVector. Note that it returns ptr::null_mut() + * if pk is null + * + * # Safety + * The ```byte_vector_destroy``` must be called when finished with a ByteVector to prevent a memory leak + */ +struct ByteVector *range_proof_get_bytes(TariRangeProof *proof_ptr, + int *error_out); + +/** + * Frees memory for a TariRangeProof + * + * ## Arguments + * `proof` - The pointer to a TariRangeProof + * + * ## Returns + * `()` - Does not return a value, equivalent to void in C + * + * # Safety + * None + */ +void range_proof_destroy(TariRangeProof *proof_ptr); + /** * -------------------------------------------------------------------------------------------- /// * --------------------------------------- Covenant --------------------------------------------/// @@ -2517,7 +2740,6 @@ void transport_config_destroy(TariTransportConfig *transport); * `database_path` - The database path char array pointer which. This is the folder path where the * database files will be created and the application has write access to * `discovery_timeout_in_secs`: specify how long the Discovery Timeout for the wallet is. - * `network`: name of network to connect to. Valid values are: esmeralda, dibbler, igor, localnet, mainnet, stagenet * `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. * @@ -2793,9 +3015,9 @@ TariBalance *wallet_get_balance(struct TariWallet *wallet, * * `page_size` - A number of items per page, * * `sorting` - An enum representing desired sorting, * * `dust_threshold` - A value filtering threshold. Outputs whose values are <= `dust_threshold` are not listed in the - * result. + * result. * * `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. + * Functions as an out parameter. * * ## Returns * `*mut TariVector` - Returns a struct with an array pointer, length and capacity (needed for proper destruction @@ -2820,11 +3042,11 @@ struct TariVector *wallet_get_utxos(struct TariWallet *wallet, * ## Arguments * * `wallet` - The TariWallet pointer, * * `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. + * Functions as an out parameter. * * ## Returns * `*mut TariVector` - Returns a struct with an array pointer, length and capacity (needed for proper destruction - * after use). + * after use). * * ## States * 0 - Unspent @@ -2858,7 +3080,7 @@ struct TariVector *wallet_get_all_utxos(struct TariWallet *wallet, * * `number_of_splits` - The number of times to split the amount * * `fee_per_gram` - The transaction fee * * `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. + * Functions as an out parameter. * * ## Returns * `c_ulonglong` - Returns the transaction id. @@ -2882,7 +3104,7 @@ uint64_t wallet_coin_split(struct TariWallet *wallet, * (see `Commitment::to_hex()`) * * `fee_per_gram` - The transaction fee * * `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. + * Functions as an out parameter. * * ## Returns * `TariVector` - Returns the transaction id. @@ -3169,6 +3391,7 @@ unsigned long long wallet_send_transaction(struct TariWallet *wallet, unsigned long long fee_per_gram, const char *message, bool one_sided, + const char *payment_id_string, int *error_out); /** diff --git a/buildtools/build-notes.md b/buildtools/build-notes.md index db5714d74b..8157265f97 100644 --- a/buildtools/build-notes.md +++ b/buildtools/build-notes.md @@ -6,40 +6,76 @@ Build options: - Virtualised - Emulated -# Building Linux x86_64 & ARM64 +# Building for Linux x86_64 & ARM64 Using Vagrant and VirtualBox has a baseline for building needs, including tools, libs and testing Linux ARM64 can be built using Vagrant and VirtualBox or Docker and cross -# Prep Ubuntu for development +# Using Docker as a temporary guest for targeting Linux build options +```bash +docker run -it --rm \ + ubuntu:18.04 bash +``` + +# Testing on OSX (x86_64/arm64) with docker targeting Linux build +Setup a temp folder and tari folder, provide ssh access, with network ports exposed +```bash +docker run -it --rm \ + -v /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock \ + -e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock \ + -v ${PWD}/../temp/root:/root \ + -v ${PWD}/../tari:/work \ + -w /work \ + -p 0.0.0.0:1230-1240:1230-1240 \ + -u root \ + --platform linux/arm64 \ + ubuntu:18.04 bash +``` + +# Prep Ubuntu v18.04 for development # From - https://github.com/tari-project/tari/blob/development/scripts/install_ubuntu_dependencies.sh ```bash sudo apt-get update -sudo apt-get install \ +sudo apt-get install --no-install-recommends --assume-yes \ + apt-transport-https \ + ca-certificates \ + curl \ + gpg \ + bash \ + less \ openssl \ libssl-dev \ pkg-config \ libsqlite3-dev \ - clang-10 \ + libsqlite3-0 \ + libreadline-dev \ git \ cmake \ dh-autoreconf \ + clang \ + g++ \ libc++-dev \ libc++abi-dev \ libprotobuf-dev \ protobuf-compiler \ libncurses5-dev \ libncursesw5-dev \ + libudev-dev \ + libhidapi-dev \ zip ``` -# Prep Ubuntu for cross-compile aarch64/arm64 on x86_64 +# Prep Ubuntu for a different CPU architecture than native CPU +an example from arm64 build to x86_64 ```bash -sudo apt-get install \ - pkg-config-aarch64-linux-gnu \ - gcc-aarch64-linux-gnu \ - g++-aarch64-linux-gnu +export CROSS_DEB_ARCH=amd64 +bash ./scripts/cross_compile_ubuntu_18-pre-build.sh x86_64-unknown-linux-gnu +``` +an example from x86_64 build to arm64 +```bash +export CROSS_DEB_ARCH=arm64 +bash ./scripts/cross_compile_ubuntu_18-pre-build.sh aarch64-unknown-linux-gnu ``` # Install rust @@ -52,13 +88,14 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y ``` ```bash +export PATH="$HOME/.cargo/bin:$PATH" source "$HOME/.cargo/env" ``` # Prep rust for cross-compile aarch64/arm64 on x86_64 ```bash rustup target add aarch64-unknown-linux-gnu -rustup toolchain install stable-aarch64-unknown-linux-gnu +rustup toolchain install stable-aarch64-unknown-linux-gnu --force-non-host ``` # Check was tools chains rust has in place @@ -125,16 +162,20 @@ cargo build --locked \ --target aarch64-unknown-linux-gnu ``` -# Using a single command line build using Docker +# Using a single command line build using cross-rs ```bash cross build --locked \ --release --features safe \ --target aarch64-unknown-linux-gnu ``` +or +# Building Raspberry Pi Linux compatible binaries on your faster x86_64 or aarch64 computer ```bash cross build --locked \ --release --features safe \ --target aarch64-unknown-linux-gnu \ - --bin minotari_node --bin minotari_console_wallet \ - --bin minotari_merge_mining_proxy --bin minotari_miner + --bin minotari_node \ + --bin minotari_console_wallet \ + --bin minotari_merge_mining_proxy \ + --bin minotari_miner ``` diff --git a/buildtools/docker/base_node.Dockerfile b/buildtools/docker/base_node.Dockerfile index e5aae31e56..79635730cf 100644 --- a/buildtools/docker/base_node.Dockerfile +++ b/buildtools/docker/base_node.Dockerfile @@ -1,13 +1,13 @@ # syntax=docker/dockerfile:1 #FROM rust:1.42.0 as builder -FROM quay.io/tarilabs/rust_tari-build-with-deps:nightly-2024-02-04 as builder +FROM quay.io/tarilabs/rust_tari-build-with-deps:nightly-2024-07-07 as builder # Copy the dependency lists #ADD Cargo.toml ./ ADD . /minotari_node WORKDIR /minotari_node -# RUN rustup component add rustfmt --toolchain nightly-2024-02-04-x86_64-unknown-linux-gnu +# RUN rustup component add rustfmt --toolchain nightly-2024-07-07-x86_64-unknown-linux-gnu #ARG TBN_ARCH=native ARG TBN_ARCH=x86-64 #ARG TBN_FEATURES=avx2 diff --git a/changelog-development.md b/changelog-development.md index 4cc803f3e6..66daa1061e 100644 --- a/changelog-development.md +++ b/changelog-development.md @@ -2,6 +2,155 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.0.0-pre.16](https://github.com/tari-project/tari/compare/v1.0.0-pre.15...v1.0.0-pre.16) (2024-07-12) + +### Features + +* add ffi features ([#6390](https://github.com/tari-project/tari/issues/6390)) ([c0c27a5](https://github.com/tari-project/tari/commit/c0c27a53f4b176db9991c097ab03589e9b1f430b)) +* **miner:** add SHA P2Pool mining related configuration and changes ([#6370](https://github.com/tari-project/tari/issues/6370)) ([cb1eb63](https://github.com/tari-project/tari/commit/cb1eb6383a0887c95f869146b94c0da00f3faffd)) +* simplify leader sig generation ([#6399](https://github.com/tari-project/tari/issues/6399)) ([22c8e33](https://github.com/tari-project/tari/commit/22c8e332d169723ced70811b08ac8cce5a4d0575)) + + +### Bug Fixes + +* claim n of m faucet ([#6389](https://github.com/tari-project/tari/issues/6389)) ([2700a46](https://github.com/tari-project/tari/commit/2700a467e3444df60fb7df71efdd54d50769a7ce)) +* script dependance on party order ([#6398](https://github.com/tari-project/tari/issues/6398)) ([2b22c1a](https://github.com/tari-project/tari/commit/2b22c1ad8c70350e838db979d008f1369552fbc4)) + +## [1.0.0-pre.15](https://github.com/tari-project/tari/compare/v1.0.0-pre.14...v1.0.0-pre.15) (2024-07-02) + + +### ⚠ BREAKING CHANGES + + +* faucet and new esme gen block (#6384) +* add accessor functions for tari address in FFI (#6385) +* make stealth one-sided tx's look the same as all other tx's (#6376) +* change tar address to use base58 and not hex (#6372) +* export unblinded outputs (#6361) + +### Features + +* add accessor functions for tari address in FFI ([#6385](https://github.com/tari-project/tari/issues/6385)) ([df5b057](https://github.com/tari-project/tari/commit/df5b05765df984e94be7097b60ab828c76cfd1d8)) +* change tari address to use base58 and not hex ([#6372](https://github.com/tari-project/tari/issues/6372)) ([f42a838](https://github.com/tari-project/tari/commit/f42a83882c8a824bf72c800c1018b4167c1a7d8c)) +* enlarge console wallet display ([#6382](https://github.com/tari-project/tari/issues/6382)) ([d62ab99](https://github.com/tari-project/tari/commit/d62ab999cda834af7c38d594cf06da24988bba5f)) +* export unblinded outputs ([#6361](https://github.com/tari-project/tari/issues/6361)) ([c444b4c](https://github.com/tari-project/tari/commit/c444b4c2cc326b6fe3ca37b10bf2d3728cdd9aca)) +* faucet and new esme gen block ([#6384](https://github.com/tari-project/tari/issues/6384)) ([075b1d4](https://github.com/tari-project/tari/commit/075b1d4ca5917cca6232ffde14d7dc51b797be1a)) +* ledger recovery ([#6383](https://github.com/tari-project/tari/issues/6383)) ([fb2de35](https://github.com/tari-project/tari/commit/fb2de353907402eb9fb8e56fc046f1a837fd8497)) +* m of n scripting poc ([#6377](https://github.com/tari-project/tari/issues/6377)) ([fc744d0](https://github.com/tari-project/tari/commit/fc744d0806e8fe15b657450c2a99ae9a488f14f6)) +* make console wallet ledger default ([#6362](https://github.com/tari-project/tari/issues/6362)) ([4ace36c](https://github.com/tari-project/tari/commit/4ace36c91d0955429f4350f9d4b9371de319984e)) +* make stealth one-sided tx's look the same as all other tx's ([#6376](https://github.com/tari-project/tari/issues/6376)) ([2bd58c2](https://github.com/tari-project/tari/commit/2bd58c235e6d89f5d836c66cc3e2e056fc40543d)) +* remove ablity to send 1-sided tx ([#6367](https://github.com/tari-project/tari/issues/6367)) ([b773173](https://github.com/tari-project/tari/commit/b773173decf6065de0f52dfa5b377c973fd97498)) + + +### Bug Fixes + +* improve key scanning ([#6374](https://github.com/tari-project/tari/issues/6374)) ([43b2317](https://github.com/tari-project/tari/commit/43b2317e8fea146d9446e150b97b09cfe41bea4d)) +* keep alpha key constant ([#6375](https://github.com/tari-project/tari/issues/6375)) ([f5e88e9](https://github.com/tari-project/tari/commit/f5e88e9e65d5253305691dcc1e9306185c2c3c1b)) +* make tx id random ([#6380](https://github.com/tari-project/tari/issues/6380)) ([59a3440](https://github.com/tari-project/tari/commit/59a344004e198eda77c9883d82dbc137741cebf4)) +* update curve library ([#6381](https://github.com/tari-project/tari/issues/6381)) ([498816d](https://github.com/tari-project/tari/commit/498816d8a8748af10e84122d39012b3a4d6b4a63)) + +## [1.0.0-pre.14](https://github.com/tari-project/tari/compare/v1.0.0-pre.13...v1.0.0-pre.14) (2024-06-03) + + +### ⚠ BREAKING CHANGES + +* add payment id (#6340) +* new tari address scheme (#6353) +* change position calculation in MTparams for mergemining (#6339) +* monero merkle tree params (#6336) +* update key manager hasher labels (#6329) + +### Features + +* add default message tag ([#6355](https://github.com/tari-project/tari/issues/6355)) ([ef387d7](https://github.com/tari-project/tari/commit/ef387d720ae1b092d0ebe4459fafc27a241368f8)) +* add payment id ([#6340](https://github.com/tari-project/tari/issues/6340)) ([ec4e5e9](https://github.com/tari-project/tari/commit/ec4e5e953b86aa3e99e65f428997682674707d4a)) +* adjust block sync timeouts ([#6342](https://github.com/tari-project/tari/issues/6342)) ([16ca4b5](https://github.com/tari-project/tari/commit/16ca4b5dc8b5e02629dd02d6839edad189d23956)) +* chat ffi find by message ([#6354](https://github.com/tari-project/tari/issues/6354)) ([28c7659](https://github.com/tari-project/tari/commit/28c76596920be88eb7b2077e6bd0645e4c5ae4a3)) +* fix base node console display ([#6341](https://github.com/tari-project/tari/issues/6341)) ([df0d801](https://github.com/tari-project/tari/commit/df0d8011d9f55f087478b0795ed0c1657b3552af)) +* generate script challenge on the ledger ([#6344](https://github.com/tari-project/tari/issues/6344)) ([34db82d](https://github.com/tari-project/tari/commit/34db82dcccc2c4bb3c9a999518cd2f5381314b07)) +* improve initial connection times ([#6343](https://github.com/tari-project/tari/issues/6343)) ([64e650b](https://github.com/tari-project/tari/commit/64e650bd61762df2b014b1dbcd7e689cd72e0c62)) +* ledger blind sign ([#6264](https://github.com/tari-project/tari/issues/6264)) ([301ea00](https://github.com/tari-project/tari/commit/301ea00fcc0664f00428828ce710bebc9491350a)) +* limit script max size ([#6364](https://github.com/tari-project/tari/issues/6364)) ([0fd2efe](https://github.com/tari-project/tari/commit/0fd2efe69298e981eec7839df5d7d0e5135d7386)) +* limit wallet base node peer outbound connections ([#6307](https://github.com/tari-project/tari/issues/6307)) ([79fcd03](https://github.com/tari-project/tari/commit/79fcd03a36f1bd81f252b1db52acc5ecc9152e61)) +* new tari address scheme ([#6353](https://github.com/tari-project/tari/issues/6353)) ([4c0ce46](https://github.com/tari-project/tari/commit/4c0ce46a3988d7d1de84f9b449a4e2c8a364ce45)) +* remove chunking from rpc ([#6345](https://github.com/tari-project/tari/issues/6345)) ([82f0d6a](https://github.com/tari-project/tari/commit/82f0d6af77841fae088f07fc1b89005377571ed8)) +* remove wallet type from config ([#6357](https://github.com/tari-project/tari/issues/6357)) ([f927d69](https://github.com/tari-project/tari/commit/f927d69ac475c5b4a66f107bcdb7a57b7c8db75c)) +* split message to have dedicated to and from fields ([#6358](https://github.com/tari-project/tari/issues/6358)) ([c24cc15](https://github.com/tari-project/tari/commit/c24cc1515d3e8980cf67564b11a78a5fabc30b08)) +* update key manager hasher labels ([#6329](https://github.com/tari-project/tari/issues/6329)) ([ae63bab](https://github.com/tari-project/tari/commit/ae63babfde7f4e4528ed63b1ffec96425da79593)) + + +### Bug Fixes + +* monero merkle tree params ([#6336](https://github.com/tari-project/tari/issues/6336)) ([9920916](https://github.com/tari-project/tari/commit/9920916f891fbd26759f7cb4912701189b7579a1)) +* atomic swap ([#6360](https://github.com/tari-project/tari/issues/6360)) ([01f93ab](https://github.com/tari-project/tari/commit/01f93ab62aa468869d51ed1afa17e103cf948a8f)) +* change position calculation in MTparams for mergemining ([#6339](https://github.com/tari-project/tari/issues/6339)) ([1d6e0d8](https://github.com/tari-project/tari/commit/1d6e0d84c9553fbb3479e2605e6122d9dd1791db)) +* new monero release for mr support in monero ([#6335](https://github.com/tari-project/tari/issues/6335)) ([3c58600](https://github.com/tari-project/tari/commit/3c58600fcf43030d7a75343a51f6aa945e8b04d1)) + +## [1.0.0-pre.13](https://github.com/tari-project/tari/compare/v1.0.0-pre.12...v1.0.0-pre.13) (2024-05-07) + + +### ⚠ BREAKING CHANGES + +* remove user agent config option (#6320) +* update emojis to match yat emojis (#6288) + +### Features + +* add check to verify mempool state ([#6316](https://github.com/tari-project/tari/issues/6316)) ([925d29a](https://github.com/tari-project/tari/commit/925d29a20cf3e9e1dc7d32b512e2113c70b110fb)) +* add sent at timestamp to chat messages ([#6314](https://github.com/tari-project/tari/issues/6314)) ([4adcb26](https://github.com/tari-project/tari/commit/4adcb26544e897ade7c02b5d9d1e432b1ae69df5)) +* esmeralda testnet reset ([#6311](https://github.com/tari-project/tari/issues/6311)) ([b7b0ea3](https://github.com/tari-project/tari/commit/b7b0ea3b0c25638fc3e0addb0d2f7f0979545ed2)) +* remove user agent config option ([#6320](https://github.com/tari-project/tari/issues/6320)) ([6b21e05](https://github.com/tari-project/tari/commit/6b21e0592412c632db774e0f1292f302f91cd3c9)) +* update emojis to match yat emojis ([#6288](https://github.com/tari-project/tari/issues/6288)) ([cf579f5](https://github.com/tari-project/tari/commit/cf579f527c31912c0a1105fa3b51aa2b63c29407)) +* updates the emoji ID API to be more idiomatic ([#6287](https://github.com/tari-project/tari/issues/6287)) ([f538714](https://github.com/tari-project/tari/commit/f538714801f6ab61f20a297c8714385a27e1aca2)) + + +### Bug Fixes + +* change mmproxy to select new monerod on error ([#6321](https://github.com/tari-project/tari/issues/6321)) ([2a9250b](https://github.com/tari-project/tari/commit/2a9250b91a1134df675203ea5c7c43deb2abdc61)) +* **comms/core:** upgrade to yamux 0.13 ([#6317](https://github.com/tari-project/tari/issues/6317)) ([1b5e217](https://github.com/tari-project/tari/commit/1b5e21757cfc99e8876c3a4c6dc20c85e886bb72)) +* potential overflow of coinbase calc ([#6306](https://github.com/tari-project/tari/issues/6306)) ([030d389](https://github.com/tari-project/tari/commit/030d389768f6e79a1e2319eb13d44ccb4392be55)) +* reload old wallets ([#6308](https://github.com/tari-project/tari/issues/6308)) ([4cc082d](https://github.com/tari-project/tari/commit/4cc082debdfc0e284a92b57f318165a04d61ce07)) + +## [1.0.0-pre.12](https://github.com/tari-project/tari/compare/v1.0.0-pre.11a...v1.0.0-pre.12) (2024-04-22) + + +### ⚠ BREAKING CHANGES + +* update new block proto to support multiple coinbases (#6266) +* avoid `Encryptable` domain collisions (#6275) +* change grpc deny to allow (#6218) + +### Features + +* add bad block reason ([#6235](https://github.com/tari-project/tari/issues/6235)) ([6dbfd79](https://github.com/tari-project/tari/commit/6dbfd798ee60bbaac8d8fa33301ff52075a191ec)) +* add dynamic growth to lmdb ([#6231](https://github.com/tari-project/tari/issues/6231)) ([f842c76](https://github.com/tari-project/tari/commit/f842c7698fc4758b14be208be6577267312989e7)) +* add monerod detection as an option to the merge mining proxy ([#6248](https://github.com/tari-project/tari/issues/6248)) ([93e8099](https://github.com/tari-project/tari/commit/93e80990922f17e4b0444f7cf7cf6168d423c676)) +* add validation for zero confirmation block sync ([#6237](https://github.com/tari-project/tari/issues/6237)) ([55077ce](https://github.com/tari-project/tari/commit/55077ce064ff75e366de375307097f100705c3c8)) +* allow wallet type from db to have preference ([#6245](https://github.com/tari-project/tari/issues/6245)) ([70319cd](https://github.com/tari-project/tari/commit/70319cd7c45eb991600955e7c3108c8486443554)) +* cache new block template ([#6222](https://github.com/tari-project/tari/issues/6222)) ([e0ad342](https://github.com/tari-project/tari/commit/e0ad342bffd929fdf2540a63c0e8cf90f2868ac3)) +* improve lmdb dynamic growth ([#6242](https://github.com/tari-project/tari/issues/6242)) ([b48a830](https://github.com/tari-project/tari/commit/b48a830ed40ff2194e919dcee6126e9353edffbf)) +* improve node and wallet connection times ([#6284](https://github.com/tari-project/tari/issues/6284)) ([fc55bf9](https://github.com/tari-project/tari/commit/fc55bf932324beaf501f5ff80388cc40b2a44206)) +* improve wallet sql queries ([#6232](https://github.com/tari-project/tari/issues/6232)) ([0290204](https://github.com/tari-project/tari/commit/02902041d0f6d8f614897ec7d0f4149f01c214b8)) +* keep smt memory ([#6265](https://github.com/tari-project/tari/issues/6265)) ([7e79380](https://github.com/tari-project/tari/commit/7e79380a01f0a802c13c16a3aa1c6c7953c2d9bb)) +* ledger key manager interface ([#5644](https://github.com/tari-project/tari/issues/5644)) ([0d66126](https://github.com/tari-project/tari/commit/0d661260cc5767d0caae067cd1825ef772abbe9f)) +* new template with coinbase call ([#6226](https://github.com/tari-project/tari/issues/6226)) ([148e398](https://github.com/tari-project/tari/commit/148e398ddd7c6a232f4246e80b6f938080163023)) +* optimize transaction validation db queries ([#6196](https://github.com/tari-project/tari/issues/6196)) ([213a885](https://github.com/tari-project/tari/commit/213a8858518332d204e6f2ad34815eba2d69bde6)) +* prevent mempool panic ([#6239](https://github.com/tari-project/tari/issues/6239)) ([5380e1f](https://github.com/tari-project/tari/commit/5380e1face2799f762145bfa238f1c719d831f2e)) +* remove template blocking call ([#6220](https://github.com/tari-project/tari/issues/6220)) ([01d79e0](https://github.com/tari-project/tari/commit/01d79e09aed7e3cf256b1f8a5caad391736ea78c)) +* show warning when GRPC method is disallowed ([#6246](https://github.com/tari-project/tari/issues/6246)) ([0019c11](https://github.com/tari-project/tari/commit/0019c1128db5dfaea1a63c2807b9c845d5e0ef09)) +* update new block proto to support multiple coinbases ([#6266](https://github.com/tari-project/tari/issues/6266)) ([f58ef12](https://github.com/tari-project/tari/commit/f58ef129177b653bf4b40d675b8977537a720235)) + + +### Bug Fixes + +* avoid `Encryptable` domain collisions ([#6275](https://github.com/tari-project/tari/issues/6275)) ([39a3fba](https://github.com/tari-project/tari/commit/39a3fbacf28142c8fd2615d4e07bdd2d489b1da4)) +* base node write tor address ([#6210](https://github.com/tari-project/tari/issues/6210)) ([019a909](https://github.com/tari-project/tari/commit/019a9093c450157d3a00661b0330c21f394c5a5f)) +* change grpc deny to allow ([#6218](https://github.com/tari-project/tari/issues/6218)) ([7665067](https://github.com/tari-project/tari/commit/7665067ef99893fc0d33996f5acbaa8fa164e844)) +* **chat:** metadata panic ([#6247](https://github.com/tari-project/tari/issues/6247)) ([492e00e](https://github.com/tari-project/tari/commit/492e00ecd3ea4400d1ca31e4169c6e7452a1b6e9)) +* improve wallet connection response time ([#6286](https://github.com/tari-project/tari/issues/6286)) ([8f1eac6](https://github.com/tari-project/tari/commit/8f1eac60598f16a4839f594d50d8fc6fbf25e92d)) +* potential in panic message_vector_get_at ([#6233](https://github.com/tari-project/tari/issues/6233)) ([2867454](https://github.com/tari-project/tari/commit/2867454f7480edd6b23e1430efa9af57d6b43c61)) +* wallet ffi type incorrect ([#6290](https://github.com/tari-project/tari/issues/6290)) ([b5bda7c](https://github.com/tari-project/tari/commit/b5bda7ccffd569f627ed41cb65d060032a58510f)) + ## [1.0.0-pre.11a](https://github.com/tari-project/tari/compare/v1.0.0-pre.11...v1.0.0-pre.11a) (2024-03-12) diff --git a/changelog-nextnet.md b/changelog-nextnet.md index 9813a06969..ab11e34341 100644 --- a/changelog-nextnet.md +++ b/changelog-nextnet.md @@ -2,6 +2,42 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.0.0-rc.7](https://github.com/tari-project/tari/compare/v1.0.0-rc.6a...v1.0.0-rc.7) (2024-04-22) + + +### ⚠ BREAKING CHANGES + +* update new block proto to support multiple coinbases (#6266) + +### Features + +* add bad block reason ([#6235](https://github.com/tari-project/tari/issues/6235)) ([6dbfd79](https://github.com/tari-project/tari/commit/6dbfd798ee60bbaac8d8fa33301ff52075a191ec)) +* add dynamic growth to lmdb ([#6231](https://github.com/tari-project/tari/issues/6231)) ([f842c76](https://github.com/tari-project/tari/commit/f842c7698fc4758b14be208be6577267312989e7)) +* add monerod detection as an option to the merge mining proxy ([#6248](https://github.com/tari-project/tari/issues/6248)) ([93e8099](https://github.com/tari-project/tari/commit/93e80990922f17e4b0444f7cf7cf6168d423c676)) +* add validation for zero confirmation block sync ([#6237](https://github.com/tari-project/tari/issues/6237)) ([55077ce](https://github.com/tari-project/tari/commit/55077ce064ff75e366de375307097f100705c3c8)) +* allow wallet type from db to have preference ([#6245](https://github.com/tari-project/tari/issues/6245)) ([70319cd](https://github.com/tari-project/tari/commit/70319cd7c45eb991600955e7c3108c8486443554)) +* cache new block template ([#6222](https://github.com/tari-project/tari/issues/6222)) ([e0ad342](https://github.com/tari-project/tari/commit/e0ad342bffd929fdf2540a63c0e8cf90f2868ac3)) +* improve lmdb dynamic growth ([#6242](https://github.com/tari-project/tari/issues/6242)) ([b48a830](https://github.com/tari-project/tari/commit/b48a830ed40ff2194e919dcee6126e9353edffbf)) +* improve node and wallet connection times ([#6284](https://github.com/tari-project/tari/issues/6284)) ([fc55bf9](https://github.com/tari-project/tari/commit/fc55bf932324beaf501f5ff80388cc40b2a44206)) +* improve wallet sql queries ([#6232](https://github.com/tari-project/tari/issues/6232)) ([0290204](https://github.com/tari-project/tari/commit/02902041d0f6d8f614897ec7d0f4149f01c214b8)) +* keep smt memory ([#6265](https://github.com/tari-project/tari/issues/6265)) ([7e79380](https://github.com/tari-project/tari/commit/7e79380a01f0a802c13c16a3aa1c6c7953c2d9bb)) +* ledger key manager interface ([#5644](https://github.com/tari-project/tari/issues/5644)) ([0d66126](https://github.com/tari-project/tari/commit/0d661260cc5767d0caae067cd1825ef772abbe9f)) +* new template with coinbase call ([#6226](https://github.com/tari-project/tari/issues/6226)) ([148e398](https://github.com/tari-project/tari/commit/148e398ddd7c6a232f4246e80b6f938080163023)) +* optimize transaction validation db queries ([#6196](https://github.com/tari-project/tari/issues/6196)) ([213a885](https://github.com/tari-project/tari/commit/213a8858518332d204e6f2ad34815eba2d69bde6)) +* prevent mempool panic ([#6239](https://github.com/tari-project/tari/issues/6239)) ([5380e1f](https://github.com/tari-project/tari/commit/5380e1face2799f762145bfa238f1c719d831f2e)) +* remove template blocking call ([#6220](https://github.com/tari-project/tari/issues/6220)) ([01d79e0](https://github.com/tari-project/tari/commit/01d79e09aed7e3cf256b1f8a5caad391736ea78c)) +* show warning when GRPC method is disallowed ([#6246](https://github.com/tari-project/tari/issues/6246)) ([0019c11](https://github.com/tari-project/tari/commit/0019c1128db5dfaea1a63c2807b9c845d5e0ef09)) +* update new block proto to support multiple coinbases ([#6266](https://github.com/tari-project/tari/issues/6266)) ([f58ef12](https://github.com/tari-project/tari/commit/f58ef129177b653bf4b40d675b8977537a720235)) + + +### Bug Fixes + +* base node write tor address ([#6210](https://github.com/tari-project/tari/issues/6210)) ([019a909](https://github.com/tari-project/tari/commit/019a9093c450157d3a00661b0330c21f394c5a5f)) +* **chat:** metadata panic ([#6247](https://github.com/tari-project/tari/issues/6247)) ([492e00e](https://github.com/tari-project/tari/commit/492e00ecd3ea4400d1ca31e4169c6e7452a1b6e9)) +* improve wallet connection response time ([#6286](https://github.com/tari-project/tari/issues/6286)) ([8f1eac6](https://github.com/tari-project/tari/commit/8f1eac60598f16a4839f594d50d8fc6fbf25e92d)) +* potential in panic message_vector_get_at ([#6233](https://github.com/tari-project/tari/issues/6233)) ([2867454](https://github.com/tari-project/tari/commit/2867454f7480edd6b23e1430efa9af57d6b43c61)) +* wallet ffi type incorrect ([#6290](https://github.com/tari-project/tari/issues/6290)) ([b5bda7c](https://github.com/tari-project/tari/commit/b5bda7ccffd569f627ed41cb65d060032a58510f)) + ## [1.0.0-rc.6a](https://github.com/tari-project/tari/compare/v1.0.0-rc.6...v1.0.0-rc.6a) (2024-03-12) diff --git a/clients/nodejs/wallet_grpc_client/index.js b/clients/nodejs/wallet_grpc_client/index.js index e88f42b22d..ba24f4c996 100644 --- a/clients/nodejs/wallet_grpc_client/index.js +++ b/clients/nodejs/wallet_grpc_client/index.js @@ -37,7 +37,7 @@ function Client(address) { "getCompletedTransactions", "getTransactionInfo", "getVersion", - "identify", + "getAddress", "transfer", "importUtxos", "listConnectedPeers", diff --git a/common/Cargo.toml b/common/Cargo.toml index 5a600f4b90..981f1eb9a2 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -11,6 +11,7 @@ edition.workspace = true [features] build = ["toml", "prost-build"] +static-application-info = ["git2"] [dependencies] tari_crypto = { workspace = true } @@ -18,18 +19,20 @@ tari_features = { workspace = true } anyhow = "1.0.53" blake2 = "0.10" -config = { version = "0.14.0", default_features = false, features = ["toml"] } +config = { version = "0.14.0", default-features = false, features = ["toml"] } +cargo_toml = { version = "0.20.4" } dirs-next = "1.0.2" +git2 = { version = "0.18", default-features = false, optional = true } log = "0.4.8" -log4rs = { version = "1.3.0", default_features = false, features = ["config_parsing", "threshold_filter", "yaml_format"] } +log4rs = { version = "1.3.0", default-features = false, features = ["config_parsing", "threshold_filter", "yaml_format"] } multiaddr = { version = "0.14.0" } path-clean = "0.1.0" prost-build = { version = "0.11.9", optional = true } -serde = { version = "1.0.106", default_features = false } +serde = { version = "1.0.106", default-features = false } serde_json = "1.0.51" serde_yaml = "0.9.17" sha2 = "0.10" -structopt = { version = "0.3.13", default_features = false } +structopt = { version = "0.3.13", default-features = false } tempfile = "3.1.0" thiserror = "1.0.29" toml = { version = "0.5", optional = true } @@ -40,3 +43,6 @@ toml = "0.5.8" [build-dependencies] tari_features = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tari_target_network_mainnet)', 'cfg(tari_target_network_nextnet)'] } diff --git a/common/config/presets/c_base_node_c.toml b/common/config/presets/c_base_node_c.toml index 259caa461c..61b9650adc 100644 --- a/common/config/presets/c_base_node_c.toml +++ b/common/config/presets/c_base_node_c.toml @@ -83,20 +83,25 @@ track_reorgs = true #service.block_sync_trigger = 5 [base_node.state_machine] -# The initial max sync latency. If a peer fails to stream a header/block within this deadline another sync peer will be -# selected. If there are no further peers the sync will be restarted with an increased by `max_latency_increase`. -#blockchain_sync_config.initial_max_sync_latency = 15 -# If all sync peers exceed latency increase allowed latency by this value -#blockchain_sync_config.max_latency_increase =2 -# Longer ban period for potentially malicious infractions (protocol violations etc.) +# The initial max sync latency (seconds). If a peer fails to stream a header/block within this deadline another sync +# peer will be selected. If there are no further peers the sync will be restarted with an increased by +# `max_latency_increase`. [default = 240] +blockchain_sync_config.initial_max_sync_latency = 240 +# If all sync peers exceed latency increase allowed latency by this value (seconds) [default = 10] +blockchain_sync_config.max_latency_increase = 10 +# Longer ban period (seconds) for potentially malicious infractions (protocol violations etc.) [default = 2 hours] #blockchain_sync_config.ban_period = 7_200 # 2 * 60 * 60 -# Short ban period for infractions that are likely not malicious (slow to respond spotty connections etc) +# Short ban period (seconds) for infractions that are likely not malicious (slow to respond spotty connections etc) +# [default = 4 minutes] #blockchain_sync_config.short_ban_period = 240 # An allowlist of sync peers from which to sync. No other peers will be selected for sync. If empty sync peers -# are chosen based on their advertised chain metadata. +# are chosen based on their advertised chain metadata. [default = []] #blockchain_sync_config.forced_sync_peers = [] -# Number of threads to use for validation +# Number of threads to use for validation [default = 6] #blockchain_sync_config.validation_concurrency = 6 +# The RPC deadline to set on sync clients. If this deadline is reached, a new sync peer will be selected for sync. +# [default = 240] +blockchain_sync_config.rpc_deadline = 240 # The maximum amount of VMs that RandomX will be use (default = 0) #max_randomx_vms = 0 @@ -148,10 +153,7 @@ track_reorgs = true # CIDR for addresses allowed to enter into liveness check mode on the listener. #listener_liveness_allowlist_cidrs = [] # Enables periodic socket-level liveness checks. Default: Disabled -listener_liveness_check_interval = 15 - -# User agent string for this node -#user_agent = "" +listener_self_liveness_check_interval = 15 # The maximum simultaneous comms RPC sessions allowed (default value = 100). Setting this to -1 will allow unlimited # sessions. @@ -192,7 +194,7 @@ listener_liveness_check_interval = 15 # When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for # better privacy #tor.proxy_bypass_for_outbound_tcp = false -# If set, instructs tor to forward traffic the the provided address. (e.g. "/dns4/my-base-node/tcp/32123") (default = OS-assigned port) +# If set, instructs tor to forward traffic the provided address. (e.g. "/dns4/my-base-node/tcp/32123") (default = OS-assigned port) #tor.forward_address = # If set, the listener will bind to this address instead of the forward_address. You need to make sure that this listener is connectable from the forward_address. #tor.listener_address_override = @@ -216,7 +218,9 @@ database_url = "data/base_node/dht.db" # The maximum number of peer nodes that a message has to be closer to, to be considered a neighbour. Default: 8 #num_neighbouring_nodes = 8 # Number of random peers to include. Default: 4 -#num_random_nodes= 4 +#num_random_nodes = 4 +# Connections above the configured number of neighbouring and random nodes will be removed (default: false) +#minimize_connections = false # Send to this many peers when using the broadcast strategy. Default: 8 #broadcast_factor = 8 # Send to this many peers when using the propagate strategy. Default: 4 @@ -288,6 +292,10 @@ database_url = "data/base_node/dht.db" # The maximum number of sync peer to select for each round. The selection strategy varies depending on the current state. # Default: 5 #network_discovery.max_sync_peers = 5 +# The maximum number of peers we allow per round of sync. (Default: 500) +#network_discovery.max_peers_to_sync_per_round = 500 +# Initial refresh sync peers delay period, when a configured connection needs preference. (Default: Disabled) +#network_discovery.initial_peer_sync_delay = 0 # Length of time to ban a peer if the peer misbehaves at the DHT-level. Default: 6 hrs #ban_duration = 21_600 # 6 * 60 * 60 diff --git a/common/config/presets/d_console_wallet.toml b/common/config/presets/d_console_wallet.toml index d54d355e8d..950aca3b0e 100644 --- a/common/config/presets/d_console_wallet.toml +++ b/common/config/presets/d_console_wallet.toml @@ -198,7 +198,7 @@ event_channel_size = 3500 # CIDR for addresses allowed to enter into liveness check mode on the listener. #listener_liveness_allowlist_cidrs = [] # Enables periodic socket-level liveness checks. Default: Disabled -# listener_liveness_check_interval = 15 +# listener_self_liveness_check_interval = 15 # User agent string for this node #user_agent = "" @@ -206,6 +206,8 @@ event_channel_size = 3500 # The maximum simultaneous comms RPC sessions allowed (default value = 100). Setting this to -1 will allow unlimited # sessions. #rpc_max_simultaneous_sessions = 100 +# The maximum comms RPC sessions allowed per peer (default value = 10). +#rpc_max_sessions_per_peer = 10 [wallet.p2p.transport] # -------------- Transport configuration -------------- @@ -240,7 +242,7 @@ event_channel_size = 3500 # When using the tor transport and set to true, outbound TCP connections bypass the tor proxy. Defaults to false for # better privacy #tor.proxy_bypass_for_outbound_tcp = false -# If set, instructs tor to forward traffic the the provided address. (e.g. "/ip4/127.0.0.1/tcp/0") (default = ) +# If set, instructs tor to forward traffic the provided address. (e.g. "/ip4/127.0.0.1/tcp/0") (default = ) #tor.forward_address = # Use a SOCKS5 proxy transport. This transport recognises any addresses supported by the proxy. @@ -260,9 +262,11 @@ database_url = "data/wallet/dht.db" # The size of the buffer (channel) which holds pending outbound message requests. Default: 20 #outbound_buffer_size = 20 # The maximum number of peer nodes that a message has to be closer to, to be considered a neighbour. Default: 8 -#num_neighbouring_nodes = 8 +num_neighbouring_nodes = 5 # Number of random peers to include. Default: 4 -#num_random_nodes= 4 +num_random_nodes = 1 +# Connections above the configured number of neighbouring and random nodes will be removed (default: false) +minimize_connections = true # Send to this many peers when using the broadcast strategy. Default: 8 #broadcast_factor = 8 # Send to this many peers when using the propagate strategy. Default: 4 @@ -309,7 +313,7 @@ database_url = "data/wallet/dht.db" #join_cooldown_interval = 120 # 10 * 60 # The interval to update the neighbouring and random pools, if necessary. Default: 2 minutes -#connectivity.update_interval = 120 # 2 * 60 +connectivity.update_interval = 300 # 2 * 60 # The interval to change the random pool peers. Default = 2 hours #connectivity.random_pool_refresh_interval = 7_200 # 2 * 60 * 60 # Length of cooldown when high connection failure rates are encountered. Default: 45s @@ -317,13 +321,13 @@ database_url = "data/wallet/dht.db" # The minimum desired ratio of TCPv4 to Tor connections. TCPv4 addresses have some significant cost to create, # making sybil attacks costly. This setting does not guarantee this ratio is maintained. # Currently, it only emits a warning if the ratio is below this setting. Default: 0.1 (10%) -#connectivity.minimum_desired_tcpv4_node_ratio = 0.1 +connectivity.minimum_desired_tcpv4_node_ratio = 0.0 # True to enable network discovery, false to disable it. Default: true #network_discovery.enabled = true # A threshold for the minimum number of peers this node should ideally be aware of. If below this threshold a # more "aggressive" strategy is employed. Default: 50 -#network_discovery.min_desired_peers = 50 +network_discovery.min_desired_peers = 16 # The period to wait once the number of rounds given by `idle_after_num_rounds` has completed. Default: 30 mins #network_discovery.idle_period = 1_800 # 30 * 60 # The minimum number of network discovery rounds to perform before idling (going to sleep). If there are less @@ -334,6 +338,10 @@ database_url = "data/wallet/dht.db" # The maximum number of sync peer to select for each round. The selection strategy varies depending on the current state. # Default: 5 #network_discovery.max_sync_peers = 5 +# The maximum number of peers we allow per round of sync. (Default: 500) +#network_discovery.max_peers_to_sync_per_round = 500 +# Initial refresh sync peers delay period, when a configured connection needs preference. (Default: Disabled) +network_discovery.initial_peer_sync_delay = 25 # Length of time to ban a peer if the peer misbehaves at the DHT-level. Default: 6 hrs #ban_duration = 21_600 # 6 * 60 * 60 diff --git a/common/config/presets/f_merge_mining_proxy.toml b/common/config/presets/f_merge_mining_proxy.toml index fdaccccf53..ebbd8fd698 100644 --- a/common/config/presets/f_merge_mining_proxy.toml +++ b/common/config/presets/f_merge_mining_proxy.toml @@ -85,7 +85,5 @@ monerod_url = [ # mainnet # The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned # e.g. "78e724f466d202abdee0f23c261289074e4a2fc9eb61e83e0179eead76ce2d3f17" #wallet_payment_address = "YOUR_WALLET_TARI_ADDRESS" -# Stealth payment yes or no (default: true) -#stealth_payment = true # Range proof type - revealed_value or bullet_proof_plus: (default = "revealed_value") #range_proof_type = "revealed_value" diff --git a/common/config/presets/g_miner.toml b/common/config/presets/g_miner.toml index 45e473b9c9..dcec594718 100644 --- a/common/config/presets/g_miner.toml +++ b/common/config/presets/g_miner.toml @@ -45,7 +45,5 @@ # The Tari wallet address (valid address in hex) where the mining funds will be sent to - must be assigned # e.g. "78e724f466d202abdee0f23c261289074e4a2fc9eb61e83e0179eead76ce2d3f17" #wallet_payment_address = "YOUR_WALLET_TARI_ADDRESS" -# Stealth payment yes or no (default: true) -#stealth_payment = true # Range proof type - revealed_value or bullet_proof_plus: (default = "revealed_value") #range_proof_type = "revealed_value" diff --git a/common/src/build/application.rs b/common/src/build/application.rs new file mode 100644 index 0000000000..33b57b9f61 --- /dev/null +++ b/common/src/build/application.rs @@ -0,0 +1,148 @@ +// Copyright 2024. 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::{ + env, + fmt, + fs, + io::Write, + panic, + path::{Path, PathBuf}, +}; + +use cargo_toml::Manifest; + +pub struct StaticApplicationInfo { + manifest: Manifest, + commit: String, +} + +impl StaticApplicationInfo { + pub fn initialize() -> Result { + let git_root = find_git_root()?; + let manifest = extract_manifest(&git_root)?; + let commit = get_commit(&git_root).unwrap_or_else(|e| { + emit_cargo_warn(e); + "NoGitRepository".to_string() + }); + Ok(Self { manifest, commit }) + } + + /// Writes the consts file to the given file in the OUT_DIR. Returns the written file path. + /// This will overwrite existing files + pub fn write_consts_to_outdir>(&self, filename: P) -> Result { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let out_path = Path::new(&out_dir).join(filename); + let mut file = fs::File::create(&out_path)?; + writeln!( + file, + r#"#[allow(dead_code)] pub const APP_VERSION: &str = "{}";"#, + self.get_full_version() + )?; + writeln!( + file, + r#"#[allow(dead_code)] pub const APP_VERSION_NUMBER: &str = "{}";"#, + self.get_version_number() + )?; + writeln!( + file, + r#"#[allow(dead_code)] pub const APP_AUTHORS: &str = "{}";"#, + self.manifest + .workspace + .as_ref() + .and_then(|w| w.package.as_ref()) + .and_then(|p| p.authors.as_ref()) + .map(|a| a.join(",")) + .unwrap_or_default() + )?; + Ok(out_path) + } + + /// Add the git version commit and built type to the version number + /// The final output looks like 0.1.2-fc435c-release + fn get_full_version(&self) -> String { + let build = env::var("PROFILE").unwrap_or_else(|e| { + emit_cargo_warn(e); + "Unknown".to_string() + }); + format!("{}-{}-{}", self.get_version_number(), self.commit, build) + } + + /// Get the version number only + /// The final output looks like 0.1.2 + fn get_version_number(&self) -> String { + self.manifest + .workspace + .as_ref() + .and_then(|w| w.package.as_ref()) + .and_then(|p| p.version.clone()) + .unwrap_or_default() + } +} + +fn extract_manifest>(git_root: P) -> Result { + let cargo_path = git_root.as_ref().join("Cargo.toml"); + let cargo = fs::read(cargo_path)?; + let cargo = std::str::from_utf8(&cargo)?; + let manifest = toml::from_str(cargo)?; + Ok(manifest) +} + +fn find_git_root() -> Result { + let manifest = env::var("CARGO_MANIFEST_DIR")?; + let mut path = PathBuf::from(manifest); + + let mut loop_count = 0; + while !path.join(".git").exists() { + path = path.join(".."); + if loop_count == 10 { + return Err(anyhow::anyhow!( + "Not a git repository or CARGO_MANIFEST_DIR nested deeper than 10 from the root" + )); + } + loop_count += 1; + } + + Ok(path) +} + +fn get_commit>(git_root: P) -> Result { + let repo = git2::Repository::open(git_root)?; + let head = repo.revparse_single("HEAD")?; + let id = format!("{:?}", head.id()); + let result = panic::catch_unwind(|| id.split_at(7)); + let id = match result { + Ok((first, _)) => first.to_string(), + Err(_) => return Err(anyhow::anyhow!("invalid utf8 in commit id")), + }; + + // replace after stable 1.80 release + // id.split_at_checked(7) + // .ok_or(anyhow::anyhow!("invalid utf8 in commit id"))? + // .0 + // .to_string(); + Ok(id) +} + +fn emit_cargo_warn(e: T) { + println!("cargo:warning=Could not open repo: {}", e); +} diff --git a/common/src/build/mod.rs b/common/src/build/mod.rs index 7d2588703c..b685a67030 100644 --- a/common/src/build/mod.rs +++ b/common/src/build/mod.rs @@ -20,6 +20,11 @@ // 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. +#[cfg(feature = "static-application-info")] +mod application; +#[cfg(feature = "static-application-info")] +pub use application::StaticApplicationInfo; + #[cfg(feature = "build")] mod protobuf; #[cfg(feature = "build")] diff --git a/common/src/configuration/loader.rs b/common/src/configuration/loader.rs index 3e82b495be..0d42499e5b 100644 --- a/common/src/configuration/loader.rs +++ b/common/src/configuration/loader.rs @@ -217,12 +217,12 @@ impl ConfigPath for C { /// ``` pub trait ConfigLoader: ConfigPath + Sized { /// Try to load configuration from supplied Config by `main_key_prefix()` - /// with values overloaded from `overload_key_prefix()`. + /// with values overloaded from `overload_key_prefix()`. For automated inheritance of Default values use + /// DefaultConfigLoader /// /// Default values will be taken from /// - `#[serde(default="value")]` field attribute /// - value defined in Config::set_default() - /// For automated inheritance of Default values use DefaultConfigLoader. fn load_from(config: &Config) -> Result; } @@ -367,6 +367,7 @@ mod test { use super::*; // test SubConfigPath both with Default and without Default + #[allow(dead_code)] #[derive(Serialize, Deserialize)] struct SubTari { monero: String, @@ -378,6 +379,8 @@ mod test { } } } + + #[allow(dead_code)] #[derive(Default, Serialize, Deserialize)] struct SuperTari { #[serde(flatten)] @@ -386,6 +389,7 @@ mod test { #[serde(default = "serde_default_string")] bitcoin: String, } + #[allow(dead_code)] fn serde_default_string() -> String { "ispublic".into() } diff --git a/common/src/configuration/name_server.rs b/common/src/configuration/name_server.rs index 55fff6b575..d432d0c405 100644 --- a/common/src/configuration/name_server.rs +++ b/common/src/configuration/name_server.rs @@ -63,7 +63,7 @@ impl FromStr for DnsNameServer { #[cfg(test)] mod test { - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::net::{IpAddr, Ipv4Addr}; use super::*; diff --git a/common_sqlite/src/connection_options.rs b/common_sqlite/src/connection_options.rs index 5fdbb3f98b..e80e027302 100644 --- a/common_sqlite/src/connection_options.rs +++ b/common_sqlite/src/connection_options.rs @@ -20,11 +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. -use core::{ - option::{Option, Option::Some}, - result::{Result, Result::Ok}, - time::Duration, -}; +use core::time::Duration; use diesel::{connection::SimpleConnection, SqliteConnection}; diff --git a/common_sqlite/src/error.rs b/common_sqlite/src/error.rs index 7bf0d6f9e8..de5875786b 100644 --- a/common_sqlite/src/error.rs +++ b/common_sqlite/src/error.rs @@ -22,7 +22,7 @@ use std::num::TryFromIntError; -use diesel::{self, r2d2}; +use diesel::r2d2; use tari_utilities::message_format::MessageFormatError; use thiserror::Error; use tokio::task; diff --git a/common_sqlite/src/sqlite_connection_pool.rs b/common_sqlite/src/sqlite_connection_pool.rs index 1c70fbca93..95ecf1b246 100644 --- a/common_sqlite/src/sqlite_connection_pool.rs +++ b/common_sqlite/src/sqlite_connection_pool.rs @@ -20,14 +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. -use core::{ - option::Option::{None, Some}, - result::{ - Result, - Result::{Err, Ok}, - }, - time::Duration, -}; +use core::time::Duration; use std::convert::TryFrom; use diesel::{ diff --git a/comms/core/Cargo.toml b/comms/core/Cargo.toml index fb958ea7cd..7dd33a3158 100644 --- a/comms/core/Cargo.toml +++ b/comms/core/Cargo.toml @@ -46,7 +46,7 @@ tokio-stream = { version = "0.1.9", features = ["sync"] } tokio-util = { version = "0.6.7", features = ["codec", "compat"] } tower = { version = "0.4", features = ["util"] } tracing = "0.1.26" -yamux = "=0.10.2" +yamux = "0.13.2" zeroize = "1" [dev-dependencies] diff --git a/comms/core/examples/stress/node.rs b/comms/core/examples/stress/node.rs index 28c871b4ac..6c651c26aa 100644 --- a/comms/core/examples/stress/node.rs +++ b/comms/core/examples/stress/node.rs @@ -90,9 +90,8 @@ pub async fn create( .parse::() .unwrap(); let node_identity = node_identity - .map(|ni| { + .inspect(|ni| { ni.add_public_address(public_addr.clone()); - ni }) .unwrap_or_else(|| Arc::new(NodeIdentity::random(&mut OsRng, public_addr, Default::default()))); diff --git a/comms/core/examples/tor.rs b/comms/core/examples/tor.rs index cf3b6ef1d9..202072f993 100644 --- a/comms/core/examples/tor.rs +++ b/comms/core/examples/tor.rs @@ -253,9 +253,8 @@ async fn start_ping_ponger( id.parse::() .ok() .and_then(|id_num| inflight_pings.remove(&id_num)) - .map(|latency| { + .inspect(|&latency| { println!("Latency: {}ms", latency); - latency }); println!("-----------------------------------"); diff --git a/comms/core/src/bounded_executor.rs b/comms/core/src/bounded_executor.rs index 2ce811083d..492d128913 100644 --- a/comms/core/src/bounded_executor.rs +++ b/comms/core/src/bounded_executor.rs @@ -156,10 +156,7 @@ impl BoundedExecutor { #[cfg(test)] mod test { use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + sync::atomic::{AtomicBool, Ordering}, time::Duration, }; diff --git a/comms/core/src/builder/comms_node.rs b/comms/core/src/builder/comms_node.rs index b9bd002a98..39252642d5 100644 --- a/comms/core/src/builder/comms_node.rs +++ b/comms/core/src/builder/comms_node.rs @@ -20,7 +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. -use std::{iter, sync::Arc, time::Duration}; +use std::{iter, sync::Arc}; use log::*; use tari_shutdown::ShutdownSignal; @@ -36,8 +36,8 @@ use crate::{ ConnectionManagerEvent, ConnectionManagerRequest, ConnectionManagerRequester, - LivenessCheck, - LivenessStatus, + SelfLivenessCheck, + SelfLivenessStatus, }, connectivity::{ConnectivityEventRx, ConnectivityManager, ConnectivityRequest, ConnectivityRequester}, multiaddr::Multiaddr, @@ -125,12 +125,6 @@ impl UnspawnedCommsNode { self } - /// Set to true to enable self liveness checking for the configured public address - pub fn set_liveness_check(mut self, interval: Option) -> Self { - self.builder = self.builder.set_liveness_check(interval); - self - } - /// Spawn a new node using the specified [Transport](crate::transports::Transport). #[allow(clippy::too_many_lines)] pub async fn spawn_with_transport(self, transport: TTransport) -> Result @@ -228,12 +222,14 @@ impl UnspawnedCommsNode { // Spawn liveness check now that we have the final address let public_addresses = node_identity.public_addresses(); let liveness_watch = if public_addresses.is_empty() { - watch::channel(LivenessStatus::Disabled).1 + watch::channel(SelfLivenessStatus::Disabled).1 } else { connection_manager_config - .liveness_self_check_interval - .map(|interval| LivenessCheck::spawn(transport, public_addresses, interval, shutdown_signal.clone())) - .unwrap_or_else(|| watch::channel(LivenessStatus::Disabled).1) + .self_liveness_self_check_interval + .map(|interval| { + SelfLivenessCheck::spawn(transport, public_addresses, interval, shutdown_signal.clone()) + }) + .unwrap_or_else(|| watch::channel(SelfLivenessStatus::Disabled).1) }; Ok(CommsNode { @@ -285,7 +281,7 @@ pub struct CommsNode { /// Shared PeerManager instance peer_manager: Arc, /// Current liveness status - liveness_watch: watch::Receiver, + liveness_watch: watch::Receiver, /// The 'reciprocal' shutdown signals for each comms service complete_signals: Vec, } @@ -321,7 +317,7 @@ impl CommsNode { } /// Returns the current liveness status - pub fn liveness_status(&self) -> LivenessStatus { + pub fn liveness_status(&self) -> SelfLivenessStatus { *self.liveness_watch.borrow() } diff --git a/comms/core/src/builder/mod.rs b/comms/core/src/builder/mod.rs index 4048905377..43b78874b0 100644 --- a/comms/core/src/builder/mod.rs +++ b/comms/core/src/builder/mod.rs @@ -70,6 +70,7 @@ use crate::{ /// # #[tokio::main] /// # async fn main() { /// use std::env::temp_dir; +/// use tari_comms::connectivity::ConnectivityConfig; /// /// use tari_storage::{ /// lmdb_store::{LMDBBuilder, LMDBConfig}, @@ -125,8 +126,8 @@ pub struct CommsBuilder { hidden_service_ctl: Option, connection_manager_config: ConnectionManagerConfig, connectivity_config: ConnectivityConfig, - shutdown_signal: Option, + maintain_n_closest_connections_only: Option, } impl Default for CommsBuilder { @@ -140,6 +141,7 @@ impl Default for CommsBuilder { connection_manager_config: ConnectionManagerConfig::default(), connectivity_config: ConnectivityConfig::default(), shutdown_signal: None, + maintain_n_closest_connections_only: None, } } } @@ -288,8 +290,19 @@ impl CommsBuilder { } /// Enable and set interval for self-liveness checks, or None to disable it (default) - pub fn set_liveness_check(mut self, check_interval: Option) -> Self { - self.connection_manager_config.liveness_self_check_interval = check_interval; + pub fn set_self_liveness_check(mut self, check_interval: Option) -> Self { + self.connection_manager_config.self_liveness_self_check_interval = check_interval; + self + } + + /// The closest number of peer connections to maintain; connections above the threshold will be removed + pub fn with_minimize_connections(mut self, connections: Option) -> Self { + self.maintain_n_closest_connections_only = connections; + self.connectivity_config.maintain_n_closest_connections_only = connections; + if let Some(val) = connections { + self.connectivity_config.reaper_min_connection_threshold = val; + } + self.connectivity_config.connection_pool_refresh_interval = Duration::from_secs(180); self } diff --git a/comms/core/src/connection_manager/common.rs b/comms/core/src/connection_manager/common.rs index eedcb607a2..0a95a51d8f 100644 --- a/comms/core/src/connection_manager/common.rs +++ b/comms/core/src/connection_manager/common.rs @@ -190,6 +190,7 @@ pub(super) fn create_or_update_peer_from_validated_peer_identity( known_peer: Option, authenticated_public_key: CommsPublicKey, peer_identity: &ValidatedPeerIdentityExchange, + latency: Duration, ) -> Peer { let peer_node_id = NodeId::from_public_key(&authenticated_public_key); @@ -206,8 +207,9 @@ pub(super) fn create_or_update_peer_from_validated_peer_identity( peer_identity_claim: peer_identity.claim.clone(), }); + // For inbound connections we cannot distinguish between the peer's addresses, so we mark all as seen peer.addresses - .mark_all_addresses_as_last_seen_now(&peer_identity.claim.addresses); + .mark_all_addresses_as_last_seen_now_with_latency(&peer_identity.claim.addresses, latency); peer.features = peer_identity.claim.features; peer.supported_protocols = peer_identity.metadata.supported_protocols.clone(); @@ -221,15 +223,18 @@ pub(super) fn create_or_update_peer_from_validated_peer_identity( "Peer '{}' does not exist in peer list. Adding.", peer_node_id.short_str() ); + let mut addresses = MultiaddressesWithStats::from_addresses_with_source( + peer_identity.claim.addresses.clone(), + &PeerAddressSource::FromPeerConnection { + peer_identity_claim: peer_identity.claim.clone(), + }, + ); + // For inbound connections we cannot distinguish between the peer's addresses, so we mark all as seen + addresses.mark_all_addresses_as_last_seen_now_with_latency(&peer_identity.claim.addresses, latency); Peer::new( authenticated_public_key, peer_node_id, - MultiaddressesWithStats::from_addresses_with_source( - peer_identity.claim.addresses.clone(), - &PeerAddressSource::FromPeerConnection { - peer_identity_claim: peer_identity.claim.clone(), - }, - ), + addresses, PeerFlags::empty(), peer_identity.peer_features(), peer_identity.supported_protocols().to_vec(), diff --git a/comms/core/src/connection_manager/dialer.rs b/comms/core/src/connection_manager/dialer.rs index 8226eab9d5..0451d8b5b4 100644 --- a/comms/core/src/connection_manager/dialer.rs +++ b/comms/core/src/connection_manager/dialer.rs @@ -39,7 +39,7 @@ use tokio::{ time, }; use tokio_stream::StreamExt; -use tracing::{self, span, Instrument, Level}; +use tracing::{span, Instrument, Level}; use super::{direction::ConnectionDirection, error::ConnectionManagerError, peer_connection::PeerConnection}; #[cfg(feature = "metrics")] @@ -491,7 +491,11 @@ where config: &ConnectionManagerConfig, ) -> (DialState, DialResult) { // Container for dial - let mut dial_state = Some(dial_state); + let mut dial_state = { + let mut temp_state = dial_state; + temp_state.peer_mut().addresses.reset_connection_attempts(); + Some(temp_state) + }; let mut transport = Some(transport); loop { @@ -539,7 +543,7 @@ where } } - /// Attempts to dial a peer sequentially on all addresses. + /// Attempts to dial a peer sequentially on all addresses; if connections are to be minimized only. /// Returns ownership of the given `DialState` and a success or failure result for the dial, /// or None if the dial was cancelled inflight async fn dial_peer( @@ -552,6 +556,18 @@ where Result<(NoiseSocket, Multiaddr), ConnectionManagerError>, ) { let addresses = dial_state.peer().addresses.clone().into_vec(); + if addresses.is_empty() { + let node_id_hex = dial_state.peer().node_id.clone().to_hex(); + debug!( + target: LOG_TARGET, + "No more contactable addresses for peer '{}'", + node_id_hex + ); + return ( + dial_state, + Err(ConnectionManagerError::NoContactableAddressesForPeer(node_id_hex)), + ); + } let cancel_signal = dial_state.get_cancel_signal(); for address in addresses { debug!( @@ -598,7 +614,7 @@ where let noise_upgrade_time = timer.elapsed(); debug!( - "Dial - upgraded noise: {} on address: {} on tcp after: {}", + "Dial - upgraded noise: {} on address: {} on tcp after: {} ms", node_id.short_str(), moved_address, timer.elapsed().as_millis() diff --git a/comms/core/src/connection_manager/error.rs b/comms/core/src/connection_manager/error.rs index 80cddff827..7d2bb4af76 100644 --- a/comms/core/src/connection_manager/error.rs +++ b/comms/core/src/connection_manager/error.rs @@ -74,7 +74,7 @@ pub enum ConnectionManagerError { IdentityProtocolError(#[from] IdentityProtocolError), #[error("The dial was cancelled")] DialCancelled, - #[error("Invalid multiaddr: {0}")] + #[error("Invalid multiaddr")] InvalidMultiaddr(String), #[error("Failed to send wire format byte")] WireFormatSendFailed, @@ -82,6 +82,8 @@ pub enum ConnectionManagerError { ListenerOneshotCancelled, #[error("Peer validation error: {0}")] PeerValidationError(#[from] PeerValidatorError), + #[error("No contactable addresses for peer {0} left")] + NoContactableAddressesForPeer(String), } impl From for ConnectionManagerError { diff --git a/comms/core/src/connection_manager/listener.rs b/comms/core/src/connection_manager/listener.rs index c777742ea0..937b1d9f8d 100644 --- a/comms/core/src/connection_manager/listener.rs +++ b/comms/core/src/connection_manager/listener.rs @@ -55,7 +55,7 @@ use crate::connection_manager::metrics; use crate::{ bounded_executor::BoundedExecutor, connection_manager::{ - liveness::LivenessSession, + self_liveness::SelfLivenessSession, wire_mode::{WireMode, LIVENESS_WIRE_MODE}, }, multiaddr::Multiaddr, @@ -89,7 +89,7 @@ pub struct PeerListener { impl PeerListener where TTransport: Transport + Clone + Send + Sync + 'static, - TTransport::Output: AsyncRead + AsyncWrite + Send + Unpin + 'static, + TTransport::Output: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static, { pub fn new( config: ConnectionManagerConfig, @@ -221,7 +221,7 @@ where shutdown_signal: ShutdownSignal, ) { permit.fetch_sub(1, Ordering::SeqCst); - let liveness = LivenessSession::new(socket); + let liveness = SelfLivenessSession::new(socket); debug!(target: LOG_TARGET, "Started liveness session"); tokio::spawn(async move { future::select(liveness.run(), shutdown_signal).await; @@ -296,7 +296,7 @@ where let _result = socket.shutdown().await; }, Ok(WireMode::Liveness) => { - if config.liveness_self_check_interval.is_some() || + if config.self_liveness_self_check_interval.is_some() || (liveness_session_count.load(Ordering::SeqCst) > 0 && Self::is_address_in_liveness_cidr_range(&peer_addr, &config.liveness_cidr_allowlist)) { @@ -359,11 +359,12 @@ where let authenticated_public_key = noise_socket .get_remote_public_key() .ok_or(ConnectionManagerError::InvalidStaticPublicKey)?; + let latency = timer.elapsed(); debug!( target: LOG_TARGET, "Noise socket upgrade completed in {:.2?} with public key '{}'", - timer.elapsed(), + latency, authenticated_public_key ); @@ -399,6 +400,7 @@ where known_peer, authenticated_public_key, &valid_peer_identity, + latency, ); let muxer = Yamux::upgrade_connection(noise_socket, CONNECTION_DIRECTION) diff --git a/comms/core/src/connection_manager/manager.rs b/comms/core/src/connection_manager/manager.rs index 42b64d4338..ff88f17501 100644 --- a/comms/core/src/connection_manager/manager.rs +++ b/comms/core/src/connection_manager/manager.rs @@ -54,6 +54,7 @@ use crate::{ peer_validator::PeerValidatorConfig, protocol::{NodeNetworkInfo, ProtocolEvent, ProtocolId, Protocols}, transports::{TcpTransport, Transport}, + Minimized, PeerManager, }; @@ -67,7 +68,7 @@ const DIALER_REQUEST_CHANNEL_SIZE: usize = 32; pub enum ConnectionManagerEvent { // Peer connection PeerConnected(Box), - PeerDisconnected(ConnectionId, NodeId), + PeerDisconnected(ConnectionId, NodeId, Minimized), PeerConnectFailed(NodeId, ConnectionManagerError), PeerInboundConnectFailed(ConnectionManagerError), @@ -84,7 +85,9 @@ impl fmt::Display for ConnectionManagerEvent { use ConnectionManagerEvent::*; match self { PeerConnected(conn) => write!(f, "PeerConnected({})", conn), - PeerDisconnected(id, node_id) => write!(f, "PeerDisconnected({}, {})", id, node_id.short_str()), + PeerDisconnected(id, node_id, minimized) => { + write!(f, "PeerDisconnected({}, {}, {:?})", id, node_id.short_str(), minimized) + }, PeerConnectFailed(node_id, err) => write!(f, "PeerConnectFailed({}, {:?})", node_id.short_str(), err), PeerInboundConnectFailed(err) => write!(f, "PeerInboundConnectFailed({:?})", err), NewInboundSubstream(node_id, protocol, _) => write!( @@ -124,7 +127,7 @@ pub struct ConnectionManagerConfig { /// CIDR blocks that allowlist liveness checks. Default: Localhost only (127.0.0.1/32) pub liveness_cidr_allowlist: Vec, /// Interval to perform self-liveness ping-pong tests. Default: None/disabled - pub liveness_self_check_interval: Option, + pub self_liveness_self_check_interval: Option, /// If set, an additional TCP-only p2p listener will be started. This is useful for local wallet connections. /// Default: None (disabled) pub auxiliary_tcp_listener_address: Option, @@ -147,7 +150,7 @@ impl Default for ConnectionManagerConfig { liveness_max_sessions: 1, time_to_first_byte: Duration::from_secs(6), liveness_cidr_allowlist: vec![cidr::AnyIpCidr::V4("127.0.0.1/32".parse().unwrap())], - liveness_self_check_interval: None, + self_liveness_self_check_interval: None, auxiliary_tcp_listener_address: None, peer_validation_config: PeerValidatorConfig::default(), noise_handshake_recv_timeout: Duration::from_secs(6), @@ -229,7 +232,7 @@ where info!(target: LOG_TARGET, "Starting auxiliary listener on {}", addr); let aux_config = ConnectionManagerConfig { // Disable liveness checks on the auxiliary listener - liveness_self_check_interval: None, + self_liveness_self_check_interval: None, ..config.clone() }; PeerListener::new( diff --git a/comms/core/src/connection_manager/mod.rs b/comms/core/src/connection_manager/mod.rs index 2b3469a3d3..3ca92fe339 100644 --- a/comms/core/src/connection_manager/mod.rs +++ b/comms/core/src/connection_manager/mod.rs @@ -51,9 +51,9 @@ pub use error::{ConnectionManagerError, PeerConnectionError}; mod peer_connection; pub use peer_connection::{ConnectionId, NegotiatedSubstream, PeerConnection, PeerConnectionRequest}; -mod liveness; -pub(crate) use liveness::LivenessCheck; -pub use liveness::LivenessStatus; +mod self_liveness; +pub(crate) use self_liveness::SelfLivenessCheck; +pub use self_liveness::SelfLivenessStatus; mod wire_mode; diff --git a/comms/core/src/connection_manager/peer_connection.rs b/comms/core/src/connection_manager/peer_connection.rs index c8e86f2394..01fe95c3e8 100644 --- a/comms/core/src/connection_manager/peer_connection.rs +++ b/comms/core/src/connection_manager/peer_connection.rs @@ -38,7 +38,7 @@ use tokio::{ time, }; use tokio_stream::StreamExt; -use tracing::{self, span, Instrument, Level}; +use tracing::{span, Instrument, Level}; use super::{direction::ConnectionDirection, error::PeerConnectionError, manager::ConnectionManagerEvent}; #[cfg(feature = "rpc")] @@ -58,6 +58,7 @@ use crate::{ peer_manager::{NodeId, PeerFeatures}, protocol::{ProtocolId, ProtocolNegotiation}, utils::atomic_ref_counter::AtomicRefCounter, + Minimized, }; const LOG_TARGET: &str = "comms::connection_manager::peer_connection"; @@ -118,7 +119,7 @@ pub enum PeerConnectionRequest { reply_tx: oneshot::Sender, PeerConnectionError>>, }, /// Disconnect all substreams and close the transport connection - Disconnect(bool, oneshot::Sender>), + Disconnect(bool, oneshot::Sender>, Minimized), } /// ID type for peer connections @@ -276,20 +277,20 @@ impl PeerConnection { /// Immediately disconnects the peer connection. This can only fail if the peer connection worker /// is shut down (and the peer is already disconnected) - pub async fn disconnect(&mut self) -> Result<(), PeerConnectionError> { + pub async fn disconnect(&mut self, minimized: Minimized) -> Result<(), PeerConnectionError> { let (reply_tx, reply_rx) = oneshot::channel(); self.request_tx - .send(PeerConnectionRequest::Disconnect(false, reply_tx)) + .send(PeerConnectionRequest::Disconnect(false, reply_tx, minimized)) .await?; reply_rx .await .map_err(|_| PeerConnectionError::InternalReplyCancelled)? } - pub(crate) async fn disconnect_silent(&mut self) -> Result<(), PeerConnectionError> { + pub(crate) async fn disconnect_silent(&mut self, minimized: Minimized) -> Result<(), PeerConnectionError> { let (reply_tx, reply_rx) = oneshot::channel(); self.request_tx - .send(PeerConnectionRequest::Disconnect(true, reply_tx)) + .send(PeerConnectionRequest::Disconnect(true, reply_tx, minimized)) .await?; reply_rx .await @@ -388,7 +389,7 @@ impl PeerConnectionActor { } } - if let Err(err) = self.disconnect(false).await { + if let Err(err) = self.disconnect(false, Minimized::No).await { warn!( target: LOG_TARGET, "[{}] Failed to politely close connection to peer '{}' because '{}'", @@ -413,7 +414,7 @@ impl PeerConnectionActor { "Reply oneshot closed when sending reply", ); }, - Disconnect(silent, reply_tx) => { + Disconnect(silent, reply_tx, minimized) => { debug!( target: LOG_TARGET, "[{}] Disconnect{}requested for {} connection to peer '{}'", @@ -422,7 +423,7 @@ impl PeerConnectionActor { self.direction, self.peer_node_id.short_str() ); - let _result = reply_tx.send(self.disconnect(silent).await); + let _result = reply_tx.send(self.disconnect(silent, minimized).await); }, } } @@ -518,7 +519,7 @@ impl PeerConnectionActor { /// # Arguments /// /// silent - true to suppress the PeerDisconnected event, false to publish the event - async fn disconnect(&mut self, silent: bool) -> Result<(), PeerConnectionError> { + async fn disconnect(&mut self, silent: bool, minimized: Minimized) -> Result<(), PeerConnectionError> { self.request_rx.close(); match self.control.close().await { Err(yamux::ConnectionError::Closed) => { @@ -536,6 +537,7 @@ impl PeerConnectionActor { self.notify_event(ConnectionManagerEvent::PeerDisconnected( self.id, self.peer_node_id.clone(), + minimized, )) .await; } diff --git a/comms/core/src/connection_manager/liveness.rs b/comms/core/src/connection_manager/self_liveness.rs similarity index 82% rename from comms/core/src/connection_manager/liveness.rs rename to comms/core/src/connection_manager/self_liveness.rs index f5dd83e08a..77ccd34b62 100644 --- a/comms/core/src/connection_manager/liveness.rs +++ b/comms/core/src/connection_manager/self_liveness.rs @@ -40,14 +40,14 @@ use crate::{connection_manager::wire_mode::WireMode, transports::Transport}; /// Max line length accepted by the liveness session. const MAX_LINE_LENGTH: usize = 50; -const LOG_TARGET: &str = "comms::connection_manager::liveness"; +const LOG_TARGET: &str = "comms::connection_manager::self_liveness"; /// Echo server for liveness checks -pub struct LivenessSession { +pub struct SelfLivenessSession { framed: Framed, } -impl LivenessSession +impl SelfLivenessSession where TSocket: AsyncRead + AsyncWrite + Unpin { pub fn new(socket: TSocket) -> Self { @@ -63,22 +63,22 @@ where TSocket: AsyncRead + AsyncWrite + Unpin } #[derive(Debug, Clone, Copy)] -pub enum LivenessStatus { +pub enum SelfLivenessStatus { Disabled, Checking, Unreachable, Live(Duration), } -pub struct LivenessCheck { +pub struct SelfLivenessCheck { transport: TTransport, addresses: Vec, interval: Duration, - tx_watch: watch::Sender, + tx_watch: watch::Sender, shutdown_signal: ShutdownSignal, } -impl LivenessCheck +impl SelfLivenessCheck where TTransport: Transport + Send + Sync + 'static, TTransport::Output: AsyncRead + AsyncWrite + Unpin + Send, @@ -88,8 +88,8 @@ where addresses: Vec, interval: Duration, shutdown_signal: ShutdownSignal, - ) -> watch::Receiver { - let (tx_watch, rx_watch) = watch::channel(LivenessStatus::Checking); + ) -> watch::Receiver { + let (tx_watch, rx_watch) = watch::channel(SelfLivenessStatus::Checking); let check = Self { transport, addresses, @@ -120,36 +120,36 @@ where let mut current_address_idx = 0; loop { let timer = Instant::now(); - let _ = self.tx_watch.send(LivenessStatus::Checking); + let _ = self.tx_watch.send(SelfLivenessStatus::Checking); let address = self.addresses[current_address_idx].clone(); match self.transport.dial(&address).await { Ok(mut socket) => { debug!( target: LOG_TARGET, - "🔌 liveness dial ({}) took {:.2?}", + "🔌 self liveness dial ({}) took {:.2?}", address, timer.elapsed() ); if let Err(err) = socket.write(&[WireMode::Liveness.as_byte()]).await { - warn!(target: LOG_TARGET, "🔌️ liveness failed to write byte: {}", err); - self.tx_watch.send_replace(LivenessStatus::Unreachable); + warn!(target: LOG_TARGET, "🔌️ self liveness failed to write byte: {}", err); + self.tx_watch.send_replace(SelfLivenessStatus::Unreachable); continue; } let mut framed = Framed::new(socket, LinesCodec::new_with_max_length(MAX_LINE_LENGTH)); loop { match self.ping_pong(&mut framed).await { Ok(Some(latency)) => { - debug!(target: LOG_TARGET, "⚡️️ liveness check latency {:.2?}", latency); - self.tx_watch.send_replace(LivenessStatus::Live(latency)); + debug!(target: LOG_TARGET, "⚡️️ self liveness check latency {:.2?}", latency); + self.tx_watch.send_replace(SelfLivenessStatus::Live(latency)); }, Ok(None) => { - info!(target: LOG_TARGET, "🔌️ liveness connection closed"); - self.tx_watch.send_replace(LivenessStatus::Unreachable); + info!(target: LOG_TARGET, "🔌️ self liveness connection closed"); + self.tx_watch.send_replace(SelfLivenessStatus::Unreachable); break; }, Err(err) => { - warn!(target: LOG_TARGET, "🔌️ ping pong failed: {}", err); - self.tx_watch.send_replace(LivenessStatus::Unreachable); + warn!(target: LOG_TARGET, "🔌️ self liveness ping pong failed: {}", err); + self.tx_watch.send_replace(SelfLivenessStatus::Unreachable); // let _ = framed.close().await; break; }, @@ -160,10 +160,10 @@ where }, Err(err) => { current_address_idx = (current_address_idx + 1) % self.addresses.len(); - self.tx_watch.send_replace(LivenessStatus::Unreachable); + self.tx_watch.send_replace(SelfLivenessStatus::Unreachable); warn!( target: LOG_TARGET, - "🔌️ Failed to dial public address {} for self check: {}", address, err + "🔌️ Failed to dial own public address {} for self check: {}", address, err ); }, } @@ -190,8 +190,6 @@ where #[cfg(test)] mod test { - use futures::SinkExt; - use tokio::{time, time::Duration}; use tokio_stream::StreamExt; use super::*; @@ -200,7 +198,7 @@ mod test { #[tokio::test] async fn echos() { let (inbound, outbound) = MemorySocket::new_pair(); - let liveness = LivenessSession::new(inbound); + let liveness = SelfLivenessSession::new(inbound); let join_handle = tokio::spawn(liveness.run()); let mut outbound = Framed::new(outbound, LinesCodec::new()); for _ in 0..10usize { diff --git a/comms/core/src/connection_manager/tests/listener_dialer.rs b/comms/core/src/connection_manager/tests/listener_dialer.rs index fa632a6fad..03686dec83 100644 --- a/comms/core/src/connection_manager/tests/listener_dialer.rs +++ b/comms/core/src/connection_manager/tests/listener_dialer.rs @@ -46,6 +46,7 @@ use crate::{ protocol::ProtocolId, test_utils::{build_peer_manager, node_identity::build_node_identity}, transports::MemoryTransport, + Minimized, }; #[tokio::test] @@ -161,7 +162,7 @@ async fn smoke() { assert_eq!(buf, *b"HELLO"); } - conn1.disconnect().await.unwrap(); + conn1.disconnect(Minimized::No).await.unwrap(); shutdown.trigger(); diff --git a/comms/core/src/connectivity/config.rs b/comms/core/src/connectivity/config.rs index 2ebc47fe91..02a65b3c7d 100644 --- a/comms/core/src/connectivity/config.rs +++ b/comms/core/src/connectivity/config.rs @@ -49,6 +49,9 @@ pub struct ConnectivityConfig { /// next connection attempt. /// Default: 24 hours pub expire_peer_last_seen_duration: Duration, + /// The closest number of peer connections to maintain; connections above the threshold will be removed + /// (default: disabled) + pub maintain_n_closest_connections_only: Option, } impl Default for ConnectivityConfig { @@ -62,6 +65,7 @@ impl Default for ConnectivityConfig { max_failures_mark_offline: 1, connection_tie_break_linger: Duration::from_secs(2), expire_peer_last_seen_duration: Duration::from_secs(24 * 60 * 60), + maintain_n_closest_connections_only: None, } } } diff --git a/comms/core/src/connectivity/connection_pool.rs b/comms/core/src/connectivity/connection_pool.rs index fb8fe017c5..6242ad3379 100644 --- a/comms/core/src/connectivity/connection_pool.rs +++ b/comms/core/src/connectivity/connection_pool.rs @@ -24,7 +24,7 @@ use std::{collections::HashMap, fmt, time::Duration}; use nom::lib::std::collections::hash_map::Entry; -use crate::{peer_manager::NodeId, PeerConnection}; +use crate::{peer_manager::NodeId, Minimized, PeerConnection}; /// Status type for connections #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -34,7 +34,7 @@ pub enum ConnectionStatus { Connected, Retrying, Failed, - Disconnected, + Disconnected(Minimized), } impl fmt::Display for ConnectionStatus { @@ -124,7 +124,7 @@ impl ConnectionPool { entry_mut.status = if conn.is_connected() { ConnectionStatus::Connected } else { - ConnectionStatus::Disconnected + ConnectionStatus::Disconnected(Minimized::No) }; entry_mut.set_connection(conn); entry_mut.status @@ -237,7 +237,7 @@ impl ConnectionPool { } pub fn count_disconnected(&self) -> usize { - self.count_filtered(|c| c.status() == ConnectionStatus::Disconnected) + self.count_filtered(|c| matches!(c.status(), ConnectionStatus::Disconnected(_))) } pub fn count_entries(&self) -> usize { diff --git a/comms/core/src/connectivity/manager.rs b/comms/core/src/connectivity/manager.rs index e55e557b6d..3ccd1a7d85 100644 --- a/comms/core/src/connectivity/manager.rs +++ b/comms/core/src/connectivity/manager.rs @@ -55,6 +55,7 @@ use crate::{ }, peer_manager::NodeId, utils::datetime::format_duration, + Minimized, NodeIdentity, PeerConnection, PeerManager, @@ -226,7 +227,7 @@ impl ConnectivityManagerActor { let tracing_id = tracing::Span::current().id(); let span = span!(Level::TRACE, "handle_dial_peer"); span.follows_from(tracing_id); - self.handle_dial_peer(node_id, reply_tx).instrument(span).await; + self.handle_dial_peer(node_id.clone(), reply_tx).instrument(span).await; }, SelectConnections(selection, reply) => { let _result = reply.send(self.select_connections(selection).await); @@ -255,6 +256,10 @@ impl ConnectivityManagerActor { let states = self.pool.all().into_iter().cloned().collect(); let _result = reply.send(states); }, + GetMinimizeConnectionsThreshold(reply) => { + let minimize_connections_threshold = self.config.maintain_n_closest_connections_only; + let _result = reply.send(minimize_connections_threshold); + }, BanPeer(node_id, duration, reason) => { if self.allow_list.contains(&node_id) { info!( @@ -269,7 +274,7 @@ impl ConnectivityManagerActor { }, AddPeerToAllowList(node_id) => { if !self.allow_list.contains(&node_id) { - self.allow_list.push(node_id) + self.allow_list.push(node_id.clone()); } }, RemovePeerFromAllowList(node_id) => { @@ -277,6 +282,10 @@ impl ConnectivityManagerActor { self.allow_list.remove(index); } }, + GetAllowList(reply) => { + let allow_list = self.allow_list.clone(); + let _result = reply.send(allow_list); + }, GetActiveConnections(reply) => { let _result = reply.send( self.pool @@ -286,6 +295,10 @@ impl ConnectivityManagerActor { .collect(), ); }, + GetNodeIdentity(reply) => { + let identity = self.node_identity.as_ref(); + let _result = reply.send(identity.clone()); + }, } } @@ -352,7 +365,7 @@ impl ConnectivityManagerActor { if !conn.is_connected() { continue; } - match conn.disconnect_silent().await { + match conn.disconnect_silent(Minimized::No).await { Ok(_) => { node_ids.push(conn.peer_node_id().clone()); }, @@ -369,7 +382,7 @@ impl ConnectivityManagerActor { } for node_id in node_ids { - self.publish_event(ConnectivityEvent::PeerDisconnected(node_id)); + self.publish_event(ConnectivityEvent::PeerDisconnected(node_id, Minimized::No)); } } @@ -389,11 +402,67 @@ impl ConnectivityManagerActor { if self.config.is_connection_reaping_enabled { self.reap_inactive_connections().await; } + if let Some(threshold) = self.config.maintain_n_closest_connections_only { + self.maintain_n_closest_peer_connections_only(threshold).await; + } self.update_connectivity_status(); self.update_connectivity_metrics(); Ok(()) } + async fn maintain_n_closest_peer_connections_only(&mut self, threshold: usize) { + // Select all active peer connections (that are communication nodes) + let mut connections = match self + .select_connections(ConnectivitySelection::closest_to( + self.node_identity.node_id().clone(), + self.pool.count_connected_nodes(), + vec![], + )) + .await + { + Ok(peers) => peers, + Err(e) => { + warn!( + target: LOG_TARGET, + "Connectivity error trying to maintain {} closest peers ({:?})", + threshold, + e + ); + return; + }, + }; + let num_connections = connections.len(); + + // Remove peers that are on the allow list + connections.retain(|conn| !self.allow_list.contains(conn.peer_node_id())); + debug!( + target: LOG_TARGET, + "minimize_connections: Filtered peers: {}, Handles: {}", + connections.len(), + num_connections, + ); + + // Disconnect all remaining peers above the threshold + for conn in connections.iter_mut().skip(threshold) { + debug!( + target: LOG_TARGET, + "minimize_connections: Disconnecting '{}' because the node is not among the {} closest peers", + conn.peer_node_id(), + threshold + ); + if let Err(err) = conn.disconnect(Minimized::Yes).await { + // Already disconnected + debug!( + target: LOG_TARGET, + "Peer '{}' already disconnected. Error: {:?}", + conn.peer_node_id().short_str(), + err + ); + } + self.pool.remove(conn.peer_node_id()); + } + } + async fn reap_inactive_connections(&mut self) { let excess_connections = self .pool @@ -418,7 +487,7 @@ impl ConnectivityManagerActor { conn.peer_node_id().short_str(), conn.handle_count() ); - if let Err(err) = conn.disconnect().await { + if let Err(err) = conn.disconnect(Minimized::Yes).await { // Already disconnected debug!( target: LOG_TARGET, @@ -432,7 +501,10 @@ impl ConnectivityManagerActor { fn clean_connection_pool(&mut self) { let cleared_states = self.pool.filter_drain(|state| { - state.status() == ConnectionStatus::Failed || state.status() == ConnectionStatus::Disconnected + matches!( + state.status(), + ConnectionStatus::Failed | ConnectionStatus::Disconnected(_) + ) }); if !cleared_states.is_empty() { @@ -490,6 +562,7 @@ impl ConnectivityManagerActor { target: LOG_TARGET, "Node is offline. Ignoring connection failure event for peer '{}'.", node_id ); + self.publish_event(ConnectivityEvent::ConnectivityStateOffline); return Ok(()); } @@ -560,7 +633,7 @@ impl ConnectivityManagerActor { TieBreak::UseNew | TieBreak::None => {}, } }, - PeerDisconnected(id, node_id) => { + PeerDisconnected(id, node_id, _minimized) => { if let Some(conn) = self.pool.get_connection(node_id) { if conn.id() != *id { debug!( @@ -586,7 +659,7 @@ impl ConnectivityManagerActor { } let (node_id, mut new_status, connection) = match event { - PeerDisconnected(_, node_id) => (node_id, ConnectionStatus::Disconnected, None), + PeerDisconnected(_, node_id, minimized) => (node_id, ConnectionStatus::Disconnected(*minimized), None), PeerConnected(conn) => (conn.peer_node_id(), ConnectionStatus::Connected, Some(conn.clone())), PeerConnectFailed(node_id, ConnectionManagerError::DialCancelled) => { @@ -632,7 +705,7 @@ impl ConnectivityManagerActor { use ConnectionStatus::{Connected, Disconnected, Failed}; match (old_status, new_status) { - (_, Connected) => match self.pool.get_connection(&node_id).cloned() { + (_, Connected) => match self.pool.get_connection_mut(&node_id).cloned() { Some(conn) => { self.mark_connection_success(conn.peer_node_id().clone()); self.publish_event(ConnectivityEvent::PeerConnected(conn.into())); @@ -642,11 +715,14 @@ impl ConnectivityManagerActor { ConnectionPool::get_connection is Some" ), }, - (Connected, Disconnected) => { - self.publish_event(ConnectivityEvent::PeerDisconnected(node_id)); + (Connected, Disconnected(..)) => { + self.publish_event(ConnectivityEvent::PeerDisconnected(node_id, match new_status { + ConnectionStatus::Disconnected(reason) => reason, + _ => Minimized::No, + })); }, // Was not connected so don't broadcast event - (_, Disconnected) => {}, + (_, Disconnected(..)) => {}, (_, Failed) => { self.publish_event(ConnectivityEvent::PeerConnectFailed(node_id)); }, @@ -692,7 +768,7 @@ impl ConnectivityManagerActor { existing_conn.direction(), ); - let _result = existing_conn.disconnect_silent().await; + let _result = existing_conn.disconnect_silent(Minimized::Yes).await; self.pool.remove(existing_conn.peer_node_id()); TieBreak::UseNew } else { @@ -708,7 +784,7 @@ impl ConnectivityManagerActor { existing_conn.direction(), ); - let _result = new_conn.clone().disconnect_silent().await; + let _result = new_conn.clone().disconnect_silent(Minimized::Yes).await; TieBreak::KeepExisting } }, @@ -885,7 +961,7 @@ impl ConnectivityManagerActor { self.publish_event(ConnectivityEvent::PeerBanned(node_id.clone())); if let Some(conn) = self.pool.get_connection_mut(node_id) { - conn.disconnect().await?; + conn.disconnect(Minimized::Yes).await?; let status = self.pool.get_connection_status(node_id); debug!( target: LOG_TARGET, @@ -901,7 +977,7 @@ impl ConnectivityManagerActor { let status = self.pool.get_connection_status(node_id); if matches!( status, - ConnectionStatus::NotConnected | ConnectionStatus::Failed | ConnectionStatus::Disconnected + ConnectionStatus::NotConnected | ConnectionStatus::Failed | ConnectionStatus::Disconnected(_) ) { to_remove.push(node_id.clone()); } diff --git a/comms/core/src/connectivity/requester.rs b/comms/core/src/connectivity/requester.rs index b2eff5f35e..4b5bbf34c1 100644 --- a/comms/core/src/connectivity/requester.rs +++ b/comms/core/src/connectivity/requester.rs @@ -41,6 +41,8 @@ use super::{ use crate::{ connection_manager::ConnectionManagerError, peer_manager::{NodeId, Peer}, + Minimized, + NodeIdentity, PeerConnection, }; @@ -54,7 +56,7 @@ pub type ConnectivityEventTx = broadcast::Sender; /// Node connectivity events emitted by the ConnectivityManager. #[derive(Debug, Clone)] pub enum ConnectivityEvent { - PeerDisconnected(NodeId), + PeerDisconnected(NodeId, Minimized), PeerConnected(Box), PeerConnectFailed(NodeId), PeerBanned(NodeId), @@ -69,7 +71,7 @@ impl fmt::Display for ConnectivityEvent { #[allow(clippy::enum_glob_use)] use ConnectivityEvent::*; match self { - PeerDisconnected(node_id) => write!(f, "PeerDisconnected({})", node_id), + PeerDisconnected(node_id, minimized) => write!(f, "PeerDisconnected({}, {:?})", node_id, minimized), PeerConnected(node_id) => write!(f, "PeerConnected({})", node_id), PeerConnectFailed(node_id) => write!(f, "PeerConnectFailed({})", node_id), PeerBanned(node_id) => write!(f, "PeerBanned({})", node_id), @@ -96,11 +98,14 @@ pub enum ConnectivityRequest { ), GetConnection(NodeId, oneshot::Sender>), GetAllConnectionStates(oneshot::Sender>), + GetMinimizeConnectionsThreshold(oneshot::Sender>), GetActiveConnections(oneshot::Sender>), BanPeer(NodeId, Duration, String), AddPeerToAllowList(NodeId), RemovePeerFromAllowList(NodeId), + GetAllowList(oneshot::Sender>), GetPeerStats(NodeId, oneshot::Sender>), + GetNodeIdentity(oneshot::Sender), } /// Handle to make requests and read events from the ConnectivityManager actor. @@ -233,6 +238,16 @@ impl ConnectivityRequester { reply_rx.await.map_err(|_| ConnectivityError::ActorResponseCancelled) } + /// Get the optional minimize connections setting. + pub async fn get_minimize_connections_threshold(&mut self) -> Result, ConnectivityError> { + let (reply_tx, reply_rx) = oneshot::channel(); + self.sender + .send(ConnectivityRequest::GetMinimizeConnectionsThreshold(reply_tx)) + .await + .map_err(|_| ConnectivityError::ActorDisconnected)?; + reply_rx.await.map_err(|_| ConnectivityError::ActorResponseCancelled) + } + /// Get all currently connection [PeerConnection](crate::PeerConnection]s. pub async fn get_active_connections(&mut self) -> Result, ConnectivityError> { let (reply_tx, reply_rx) = oneshot::channel(); @@ -272,6 +287,26 @@ impl ConnectivityRequester { Ok(()) } + /// Retrieve self's allow list. + pub async fn get_allow_list(&mut self) -> Result, ConnectivityError> { + let (reply_tx, reply_rx) = oneshot::channel(); + self.sender + .send(ConnectivityRequest::GetAllowList(reply_tx)) + .await + .map_err(|_| ConnectivityError::ActorDisconnected)?; + reply_rx.await.map_err(|_| ConnectivityError::ActorResponseCancelled) + } + + /// Retrieve self's node identity. + pub async fn get_node_identity(&mut self) -> Result { + let (reply_tx, reply_rx) = oneshot::channel(); + self.sender + .send(ConnectivityRequest::GetNodeIdentity(reply_tx)) + .await + .map_err(|_| ConnectivityError::ActorDisconnected)?; + reply_rx.await.map_err(|_| ConnectivityError::ActorResponseCancelled) + } + /// Removes a peer from an allow list that prevents it from being banned. pub async fn remove_peer_from_allow_list(&mut self, node_id: NodeId) -> Result<(), ConnectivityError> { self.sender diff --git a/comms/core/src/connectivity/test.rs b/comms/core/src/connectivity/test.rs index ea984804f7..9e621b24ab 100644 --- a/comms/core/src/connectivity/test.rs +++ b/comms/core/src/connectivity/test.rs @@ -43,6 +43,7 @@ use crate::{ mocks::{create_connection_manager_mock, create_peer_connection_mock_pair, ConnectionManagerMockState}, node_identity::{build_many_node_identities, build_node_identity}, }, + Minimized, NodeIdentity, PeerManager, }; @@ -203,6 +204,7 @@ async fn online_then_offline_then_online() { cm_mock_state.publish_event(ConnectionManagerEvent::PeerDisconnected( conn.id(), conn.peer_node_id().clone(), + Minimized::No, )); } @@ -224,6 +226,7 @@ async fn online_then_offline_then_online() { cm_mock_state.publish_event(ConnectionManagerEvent::PeerDisconnected( conn.id(), conn.peer_node_id().clone(), + Minimized::No, )); } @@ -420,10 +423,11 @@ async fn pool_management() { if conn != important_connection { assert_eq!(conn.handle_count(), 2); // The peer connection mock does not "automatically" publish event to connectivity manager - conn.disconnect().await.unwrap(); + conn.disconnect(Minimized::No).await.unwrap(); cm_mock_state.publish_event(ConnectionManagerEvent::PeerDisconnected( conn.id(), conn.peer_node_id().clone(), + Minimized::No, )); } } @@ -432,7 +436,7 @@ async fn pool_management() { let events = collect_try_recv!(event_stream, take = 9, timeout = Duration::from_secs(10)); for event in events { - assert!(matches!(event, ConnectivityEvent::PeerDisconnected(_))); + unpack_enum!(ConnectivityEvent::PeerDisconnected(_a, _b) = event); } assert_eq!(important_connection.handle_count(), 2); @@ -440,15 +444,16 @@ async fn pool_management() { let conns = connectivity.get_active_connections().await.unwrap(); assert_eq!(conns.len(), 1); - important_connection.disconnect().await.unwrap(); + important_connection.disconnect(Minimized::No).await.unwrap(); cm_mock_state.publish_event(ConnectionManagerEvent::PeerDisconnected( important_connection.id(), important_connection.peer_node_id().clone(), + Minimized::No, )); drop(important_connection); let mut events = collect_try_recv!(event_stream, take = 1, timeout = Duration::from_secs(10)); - assert!(matches!(events.remove(0), ConnectivityEvent::PeerDisconnected(_node))); + unpack_enum!(ConnectivityEvent::PeerDisconnected(_a, _b) = events.remove(0)); let conns = connectivity.get_active_connections().await.unwrap(); assert!(conns.is_empty()); } diff --git a/comms/core/src/lib.rs b/comms/core/src/lib.rs index abdbb0e849..face0f13c5 100644 --- a/comms/core/src/lib.rs +++ b/comms/core/src/lib.rs @@ -21,7 +21,6 @@ pub mod connectivity; pub mod peer_manager; pub use peer_manager::{NodeIdentity, OrNotFound, PeerManager}; - pub mod framing; mod multiplexing; @@ -65,3 +64,10 @@ pub use async_trait::async_trait; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; #[cfg(feature = "rpc")] pub use tower::make::MakeService; + +/// Was the connection closed due to minimize_connections +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Minimized { + Yes, + No, +} diff --git a/comms/core/src/memsocket/mod.rs b/comms/core/src/memsocket/mod.rs index 9e408666a7..62fc050081 100644 --- a/comms/core/src/memsocket/mod.rs +++ b/comms/core/src/memsocket/mod.rs @@ -70,13 +70,13 @@ pub fn acquire_next_memsocket_port() -> NonZeroU16 { // The switchboard is full and all ports are in use assert!( - switchboard.0.len() != (std::u16::MAX - 1) as usize, + switchboard.0.len() != (u16::MAX - 1) as usize, "All memsocket addresses in use!" ); // Instead of overflowing to 0, resume searching at port 1 since port 0 isn't a // valid port to bind to. - if switchboard.1 == std::u16::MAX { + if switchboard.1 == u16::MAX { switchboard.1 = 1; } else { switchboard.1 += 1; @@ -191,13 +191,13 @@ impl MemoryListener { let port = NonZeroU16::new(switchboard.1).unwrap_or_else(|| unreachable!()); // The switchboard is full and all ports are in use - if switchboard.0.len() == (std::u16::MAX - 1) as usize { + if switchboard.0.len() == (u16::MAX - 1) as usize { return Err(ErrorKind::AddrInUse.into()); } // Instead of overflowing to 0, resume searching at port 1 since port 0 isn't a // valid port to bind to. - if switchboard.1 == std::u16::MAX { + if switchboard.1 == u16::MAX { switchboard.1 = 1; } else { switchboard.1 += 1; diff --git a/comms/core/src/multiplexing/yamux.rs b/comms/core/src/multiplexing/yamux.rs index 5e16dfc459..6c7f58aba3 100644 --- a/comms/core/src/multiplexing/yamux.rs +++ b/comms/core/src/multiplexing/yamux.rs @@ -20,15 +20,15 @@ // 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::{future::Future, io, pin::Pin, task::Poll}; +use std::{future::poll_fn, io, marker::PhantomData, pin::Pin, task::Poll}; -use futures::{task::Context, Stream}; +use futures::{channel::oneshot, task::Context, Stream}; use tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, sync::mpsc, }; use tokio_util::compat::{Compat, FuturesAsyncReadCompatExt, TokioAsyncReadCompatExt}; -use tracing::{self, debug, error}; +use tracing::{debug, error, warn}; // Reexport pub use yamux::ConnectionError; use yamux::Mode; @@ -48,30 +48,20 @@ pub struct Yamux { substream_counter: AtomicRefCounter, } -const MAX_BUFFER_SIZE: u32 = 8 * 1024 * 1024; // 8MiB -const RECEIVE_WINDOW: u32 = 5 * 1024 * 1024; // 5MiB - impl Yamux { /// Upgrade the underlying socket to use yamux pub fn upgrade_connection(socket: TSocket, direction: ConnectionDirection) -> io::Result - where TSocket: AsyncRead + AsyncWrite + Send + Unpin + 'static { + where TSocket: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static { let mode = match direction { ConnectionDirection::Inbound => Mode::Server, ConnectionDirection::Outbound => Mode::Client, }; - let mut config = yamux::Config::default(); - - config.set_window_update_mode(yamux::WindowUpdateMode::OnRead); - // Because OnRead mode increases the RTT of window update, bigger buffer size and receive - // window size perform better. - config.set_max_buffer_size(MAX_BUFFER_SIZE as usize); - config.set_receive_window(RECEIVE_WINDOW); + let config = yamux::Config::default(); let substream_counter = AtomicRefCounter::new(); let connection = yamux::Connection::new(socket.compat(), config, mode); - let control = Control::new(connection.control(), substream_counter.clone()); - let incoming = Self::spawn_incoming_stream_worker(connection, substream_counter.clone()); + let (control, incoming) = Self::spawn_incoming_stream_worker(connection, substream_counter.clone()); Ok(Self { control, @@ -85,14 +75,16 @@ impl Yamux { fn spawn_incoming_stream_worker( connection: yamux::Connection, counter: AtomicRefCounter, - ) -> IncomingSubstreams + ) -> (Control, IncomingSubstreams) where - TSocket: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static, + TSocket: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + Sync + 'static, { let (incoming_tx, incoming_rx) = mpsc::channel(10); - let incoming = IncomingWorker::new(connection, incoming_tx); - tokio::spawn(incoming.run()); - IncomingSubstreams::new(incoming_rx, counter) + let (request_tx, request_rx) = mpsc::channel(1); + let incoming = YamuxWorker::new(incoming_tx, request_rx, counter.clone()); + let control = Control::new(request_tx); + tokio::spawn(incoming.run(connection)); + (control, IncomingSubstreams::new(incoming_rx, counter)) } /// Get the yamux control struct @@ -121,42 +113,45 @@ impl Yamux { } } +#[derive(Debug)] +pub enum YamuxRequest { + OpenStream { + reply: oneshot::Sender>, + }, + Close { + reply: oneshot::Sender>, + }, +} + #[derive(Clone)] pub struct Control { - inner: yamux::Control, - substream_counter: AtomicRefCounter, + request_tx: mpsc::Sender, } impl Control { - pub fn new(inner: yamux::Control, substream_counter: AtomicRefCounter) -> Self { - Self { - inner, - substream_counter, - } + pub fn new(request_tx: mpsc::Sender) -> Self { + Self { request_tx } } /// Open a new stream to the remote. pub async fn open_stream(&mut self) -> Result { - // Ensure that this counts as used while the substream is being opened - let counter_guard = self.substream_counter.new_guard(); - let stream = self.inner.open_stream().await?; - Ok(Substream { - stream: stream.compat(), - _counter_guard: counter_guard, - }) + let (reply, reply_rx) = oneshot::channel(); + self.request_tx + .send(YamuxRequest::OpenStream { reply }) + .await + .map_err(|_| ConnectionError::Closed)?; + let stream = reply_rx.await.map_err(|_| ConnectionError::Closed)??; + Ok(stream) } /// Close the connection. - pub fn close(&mut self) -> impl Future> + '_ { - self.inner.close() - } - - pub fn substream_count(&self) -> usize { - self.substream_counter.get() - } - - pub(crate) fn substream_counter(&self) -> AtomicRefCounter { - self.substream_counter.clone() + pub async fn close(&mut self) -> Result<(), ConnectionError> { + let (reply, reply_rx) = oneshot::channel(); + self.request_tx + .send(YamuxRequest::Close { reply }) + .await + .map_err(|_| ConnectionError::Closed)?; + reply_rx.await.map_err(|_| ConnectionError::Closed)? } } @@ -240,52 +235,78 @@ impl From for stream_id::Id { } } -struct IncomingWorker { - connection: yamux::Connection, - sender: mpsc::Sender, +struct YamuxWorker { + incoming_substreams: mpsc::Sender, + request_rx: mpsc::Receiver, + counter: AtomicRefCounter, + _phantom: PhantomData, } -impl IncomingWorker -where TSocket: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static /* */ +impl YamuxWorker +where TSocket: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + Sync + 'static { - pub fn new(connection: yamux::Connection, sender: mpsc::Sender) -> Self { - Self { connection, sender } + pub fn new( + incoming_substreams: mpsc::Sender, + request_rx: mpsc::Receiver, + counter: AtomicRefCounter, + ) -> Self { + Self { + incoming_substreams, + request_rx, + counter, + _phantom: PhantomData, + } } - pub async fn run(mut self) { + async fn run(mut self, mut connection: yamux::Connection) { loop { tokio::select! { - _ = self.sender.closed() => { - self.close().await; + biased; + + _ = self.incoming_substreams.closed() => { + debug!( + target: LOG_TARGET, + "{} Incoming peer substream task is stopping because the internal stream sender channel was \ + closed", + self.counter.get() + ); + // Ignore: we already log the error variant in Self::close + let _ignore = Self::close(&mut connection).await; break }, - result = self.connection.next_stream() => { + Some(request) = self.request_rx.recv() => { + if let Err(err) = self.handle_request(&mut connection, request).await { + error!(target: LOG_TARGET, "Error handling request: {err}"); + break; + } + }, + + result = Self::next_inbound_stream(&mut connection) => { match result { - Ok(Some(stream)) => { - if self.sender.send(stream).await.is_err() { + Some(Ok(stream)) => { + if self.incoming_substreams.send(stream).await.is_err() { debug!( target: LOG_TARGET, - "{} Incoming peer substream task is stopping because the internal stream sender channel \ - was closed", - self.connection + "{} Incoming peer substream task is stopping because the internal stream sender channel was closed", + self.counter.get() ); break; } }, - Ok(None) =>{ + None =>{ debug!( target: LOG_TARGET, "{} Incoming peer substream ended.", - self.connection + self.counter.get() ); break; } - Err(err) => { + Some(Err(err)) => { error!( target: LOG_TARGET, "{} Incoming peer substream task received an error because '{}'", - self.connection, + self.counter.get(), err ); break; @@ -296,38 +317,46 @@ where TSocket: futures::AsyncRead + futures::AsyncWrite + Unpin + Send + 'static } } - async fn close(&mut self) { - let mut control = self.connection.control(); - // Sends the close message once polled, while continuing to poll the connection future - let close_fut = control.close(); - tokio::pin!(close_fut); - loop { - tokio::select! { - biased; + async fn handle_request( + &self, + connection_mut: &mut yamux::Connection, + request: YamuxRequest, + ) -> io::Result<()> { + match request { + YamuxRequest::OpenStream { reply } => { + let result = poll_fn(move |cx| connection_mut.poll_new_outbound(cx)).await; + if reply + .send(result.map(|stream| Substream { + stream: stream.compat(), + _counter_guard: self.counter.new_guard(), + })) + .is_err() + { + warn!(target: LOG_TARGET, "Request to open substream was aborted before reply was sent"); + } + }, + YamuxRequest::Close { reply } => { + if reply.send(Self::close(connection_mut).await).is_err() { + warn!(target: LOG_TARGET, "Request to close substream was aborted before reply was sent"); + } + }, + } + Ok(()) + } - result = &mut close_fut => { - match result { - Ok(_) => break, - Err(err) => { - error!(target: LOG_TARGET, "Failed to close yamux connection: {}", err); - break; - } - } - }, + async fn next_inbound_stream( + connection_mut: &mut yamux::Connection, + ) -> Option> { + poll_fn(|cx| connection_mut.poll_next_inbound(cx)).await + } - result = self.connection.next_stream() => { - match result { - Ok(Some(_)) => continue, - Ok(None) => break, - Err(err) => { - error!(target: LOG_TARGET, "Error while closing yamux connection: {}", err); - continue; - } - } - } - } + async fn close(connection: &mut yamux::Connection) -> yamux::Result<()> { + if let Err(err) = poll_fn(|cx| connection.poll_close(cx)).await { + error!(target: LOG_TARGET, "Error while closing yamux connection: {}", err); + return Err(err); } - debug!(target: LOG_TARGET, "{} Yamux connection has closed", self.connection); + debug!(target: LOG_TARGET, "Yamux connection has closed"); + Ok(()) } } @@ -356,21 +385,18 @@ mod test { let mut substream = dialer_control.open_stream().await.unwrap(); substream.write_all(msg).await.unwrap(); - substream.flush().await.unwrap(); substream.shutdown().await.unwrap(); }); - let mut listener = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound)?.into_incoming(); + let mut listener = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound)?; let mut substream = listener + .incoming .next() .await .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "no substream"))?; let mut buf = Vec::new(); - tokio::select! { - _ = substream.read_to_end(&mut buf) => {}, - _ = listener.next() => {}, - }; + substream.read_to_end(&mut buf).await?; assert_eq!(buf, msg); Ok(()) @@ -387,15 +413,21 @@ mod test { let substreams_out = tokio::spawn(async move { let mut substreams = Vec::with_capacity(NUM_SUBSTREAMS); for _ in 0..NUM_SUBSTREAMS { - substreams.push(dialer_control.open_stream().await.unwrap()); + let mut stream = dialer_control.open_stream().await.unwrap(); + // Since Yamux 0.12.0 the client does not initiate a substream unless you actually write something + stream.write_all(b"hello").await.unwrap(); + substreams.push(stream); } substreams }); - let mut listener = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound) - .unwrap() - .into_incoming(); - let substreams_in = collect_stream!(&mut listener, take = NUM_SUBSTREAMS, timeout = Duration::from_secs(10)); + let mut listener = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound).unwrap(); + + let substreams_in = collect_stream!( + &mut listener.incoming, + take = NUM_SUBSTREAMS, + timeout = Duration::from_secs(10) + ); assert_eq!(dialer.substream_count(), NUM_SUBSTREAMS); assert_eq!(listener.substream_count(), NUM_SUBSTREAMS); @@ -426,8 +458,8 @@ mod test { assert_eq!(buf, b""); }); - let mut incoming = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound)?.into_incoming(); - let mut substream = incoming.next().await.unwrap(); + let mut listener = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound)?; + let mut substream = listener.incoming.next().await.unwrap(); let mut buf = vec![0; msg.len()]; substream.read_exact(&mut buf).await?; @@ -482,12 +514,13 @@ mod test { let (dialer, listener) = MemorySocket::new_pair(); let dialer = Yamux::upgrade_connection(dialer, ConnectionDirection::Outbound)?; + let substream_counter = dialer.substream_counter(); let mut dialer_control = dialer.get_yamux_control(); tokio::spawn(async move { - assert_eq!(dialer_control.substream_count(), 0); + assert_eq!(substream_counter.get(), 0); let mut substream = dialer_control.open_stream().await.unwrap(); - assert_eq!(dialer_control.substream_count(), 1); + assert_eq!(substream_counter.get(), 1); let msg = vec![0x55u8; MSG_LEN]; substream.write_all(msg.as_slice()).await.unwrap(); @@ -500,10 +533,10 @@ mod test { assert_eq!(buf, vec![0xAAu8; MSG_LEN]); }); - let mut incoming = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound)?.into_incoming(); - assert_eq!(incoming.substream_count(), 0); - let mut substream = incoming.next().await.unwrap(); - assert_eq!(incoming.substream_count(), 1); + let mut listener = Yamux::upgrade_connection(listener, ConnectionDirection::Inbound)?; + assert_eq!(listener.substream_count(), 0); + let mut substream = listener.incoming.next().await.unwrap(); + assert_eq!(listener.substream_count(), 1); let mut buf = vec![0u8; MSG_LEN]; substream.read_exact(&mut buf).await?; @@ -514,7 +547,7 @@ mod test { substream.shutdown().await?; drop(substream); - assert_eq!(incoming.substream_count(), 0); + assert_eq!(listener.substream_count(), 0); Ok(()) } diff --git a/comms/core/src/net_address/multiaddr_with_stats.rs b/comms/core/src/net_address/multiaddr_with_stats.rs index b357168fd6..24a24823b6 100644 --- a/comms/core/src/net_address/multiaddr_with_stats.rs +++ b/comms/core/src/net_address/multiaddr_with_stats.rs @@ -3,7 +3,7 @@ use std::{ cmp, - cmp::{Ord, Ordering}, + cmp::Ordering, convert::{TryFrom, TryInto}, fmt, fmt::{Display, Formatter}, @@ -12,27 +12,29 @@ use std::{ }; use chrono::{NaiveDateTime, Utc}; +use log::trace; use multiaddr::Multiaddr; use serde::{Deserialize, Serialize}; use crate::{peer_manager::PeerIdentityClaim, types::CommsPublicKey}; +const LOG_TARGET: &str = "comms::net_address::multiaddr_with_stats"; + const MAX_LATENCY_SAMPLE_COUNT: u32 = 100; const MAX_INITIAL_DIAL_TIME_SAMPLE_COUNT: u32 = 100; -const HIGH_QUALITY_SCORE: i32 = 1000; #[derive(Debug, Eq, Clone, Deserialize, Serialize)] pub struct MultiaddrWithStats { address: Multiaddr, last_seen: Option, connection_attempts: u32, - avg_initial_dial_time: Duration, + avg_initial_dial_time: Option, initial_dial_time_sample_count: u32, - avg_latency: Duration, + avg_latency: Option, latency_sample_count: u32, last_attempted: Option, last_failed_reason: Option, - quality_score: i32, + quality_score: Option, source: PeerAddressSource, } @@ -43,13 +45,13 @@ impl MultiaddrWithStats { address, last_seen: None, connection_attempts: 0, - avg_initial_dial_time: Duration::from_secs(0), + avg_initial_dial_time: None, initial_dial_time_sample_count: 0, - avg_latency: Duration::from_millis(0), + avg_latency: None, latency_sample_count: 0, last_attempted: None, last_failed_reason: None, - quality_score: 0, + quality_score: None, source, }; addr.update_quality_score(); @@ -58,6 +60,15 @@ impl MultiaddrWithStats { pub fn merge(&mut self, other: &Self) { if self.address == other.address { + trace!( + target: LOG_TARGET, "merge: '{}, {:?}, {:?}' and '{}, {:?}, {:?}'", + self.address.to_string(), + self.last_seen, + self.quality_score, + other.address.to_string(), + other.last_seen, + other.quality_score + ); self.last_seen = cmp::max(other.last_seen, self.last_seen); self.connection_attempts = cmp::max(self.connection_attempts, other.connection_attempts); match self.latency_sample_count.cmp(&other.latency_sample_count) { @@ -122,9 +133,14 @@ impl MultiaddrWithStats { pub fn update_latency(&mut self, latency_measurement: Duration) { self.last_seen = Some(Utc::now().naive_utc()); - self.avg_latency = ((self.avg_latency.saturating_mul(self.latency_sample_count)) + self.avg_latency = Some( + ((self + .avg_latency + .unwrap_or_default() + .saturating_mul(self.latency_sample_count)) .saturating_add(latency_measurement)) / - (self.latency_sample_count + 1); + (self.latency_sample_count + 1), + ); if self.latency_sample_count < MAX_LATENCY_SAMPLE_COUNT { self.latency_sample_count += 1; } @@ -132,12 +148,19 @@ impl MultiaddrWithStats { self.update_quality_score(); } + #[cfg(test)] + fn get_averag_latency(&self) -> Option { + self.avg_latency + } + pub fn update_initial_dial_time(&mut self, initial_dial_time: Duration) { self.last_seen = Some(Utc::now().naive_utc()); - self.avg_initial_dial_time = ((self.avg_initial_dial_time * self.initial_dial_time_sample_count) + - initial_dial_time) / - (self.initial_dial_time_sample_count + 1); + self.avg_initial_dial_time = Some( + ((self.avg_initial_dial_time.unwrap_or_default() * self.initial_dial_time_sample_count) + + initial_dial_time) / + (self.initial_dial_time_sample_count + 1), + ); if self.initial_dial_time_sample_count < MAX_INITIAL_DIAL_TIME_SAMPLE_COUNT { self.initial_dial_time_sample_count += 1; } @@ -146,6 +169,10 @@ impl MultiaddrWithStats { /// Mark that a successful interaction occurred with this address pub fn mark_last_seen_now(&mut self) -> &mut Self { + trace!( + target: LOG_TARGET, "mark_last_seen_now: from {}, address '{}', previous {:?}", + self.source, self.address.to_string(), self.last_seen + ); self.last_seen = Some(Utc::now().naive_utc()); self.last_failed_reason = None; self.reset_connection_attempts(); @@ -156,6 +183,7 @@ impl MultiaddrWithStats { /// Reset the connection attempts on this net address for a later session of retries pub fn reset_connection_attempts(&mut self) { self.connection_attempts = 0; + self.last_failed_reason = None; } /// Mark that a connection could not be established with this net address @@ -184,36 +212,55 @@ impl MultiaddrWithStats { self.clone().address } - fn calculate_quality_score(&self) -> i32 { - // If we have never seen or attempted the peer, we start with a high score to ensure that + // The quality score is a measure of the reliability of the net address. It is calculated based on the following: + // - The maximum score is 'Some(1000)' points (seen within the last 1s and latency < 100ms). + // - The minimum score without any connection errors is 'Some(100)' points (seen >= 800s ago and latency >= 10s). + // - For any sort of connection error the score is 'Some(0)' points. + // - A score of `None` means it has not been tried. + fn calculate_quality_score(&self) -> Option { if self.last_seen.is_none() && self.last_attempted.is_none() { - return HIGH_QUALITY_SCORE; + return None; } - let mut score_self = 0; - - if self.avg_latency.as_millis() == 0 { - score_self += 100; - } else { - // explicitly truncate the latency to avoid casting problems - let avg_latency_millis = i32::try_from(self.avg_latency.as_millis()).unwrap_or(i32::MAX); + // The starting score + let mut score_self = 800; + + // Latency score: + // - If there is no average yet, add '100' points + // - If the average latency is + // - less than 100ms, add '100' points + // - 100ms to 10,000ms', add '99' to '1' point on a sliding scale + // - 10s or more, add '0' points + if let Some(val) = self.avg_latency { + // Explicitly truncate the latency to avoid casting problems + let avg_latency_millis = i32::try_from(val.as_millis()).unwrap_or(i32::MAX); score_self += cmp::max(0, 100i32.saturating_sub(avg_latency_millis / 100)); + } else { + score_self += 100; } + // Last seen score: + // - If the last seen time is: + // - 800s or more, subtract '700' points + // - 799s to 101s, subtract '699' to '1' point on a sliding scale + // - 100s, add or subtract nothing + // - 99s to 1s, add '1' to '99' points on a sliding scale + // - less than 1s, add '100' points let last_seen_seconds: i32 = self .last_seen .map(|x| Utc::now().naive_utc() - x) .map(|x| x.num_seconds()) - .unwrap_or(0) + .unwrap_or(i64::MAX / 2) .try_into() .unwrap_or(i32::MAX); - score_self += cmp::max(0, 100i32.saturating_sub(last_seen_seconds)); + score_self += cmp::max(-700, 100i32.saturating_sub(last_seen_seconds)); + // Any failure to connect results in a score of '0' points if self.last_failed_reason.is_some() { - score_self -= 100; + score_self = 0; } - score_self + Some(score_self) } fn update_quality_score(&mut self) { @@ -232,7 +279,7 @@ impl MultiaddrWithStats { self.connection_attempts } - pub fn avg_initial_dial_time(&self) -> Duration { + pub fn avg_initial_dial_time(&self) -> Option { self.avg_initial_dial_time } @@ -240,7 +287,7 @@ impl MultiaddrWithStats { self.initial_dial_time_sample_count } - pub fn avg_latency(&self) -> Duration { + pub fn avg_latency(&self) -> Option { self.avg_latency } @@ -256,7 +303,7 @@ impl MultiaddrWithStats { self.last_failed_reason.as_deref() } - pub fn quality_score(&self) -> i32 { + pub fn quality_score(&self) -> Option { self.quality_score } } @@ -375,8 +422,6 @@ impl PartialEq for PeerAddressSource { } #[cfg(test)] mod test { - use std::time::Duration; - use super::*; #[test] @@ -388,13 +433,13 @@ mod test { let latency_measurement3 = Duration::from_millis(60); let latency_measurement4 = Duration::from_millis(140); net_address_with_stats.update_latency(latency_measurement1); - assert_eq!(net_address_with_stats.avg_latency, latency_measurement1); + assert_eq!(net_address_with_stats.avg_latency.unwrap(), latency_measurement1); net_address_with_stats.update_latency(latency_measurement2); - assert_eq!(net_address_with_stats.avg_latency, Duration::from_millis(150)); + assert_eq!(net_address_with_stats.avg_latency.unwrap(), Duration::from_millis(150)); net_address_with_stats.update_latency(latency_measurement3); - assert_eq!(net_address_with_stats.avg_latency, Duration::from_millis(120)); + assert_eq!(net_address_with_stats.avg_latency.unwrap(), Duration::from_millis(120)); net_address_with_stats.update_latency(latency_measurement4); - assert_eq!(net_address_with_stats.avg_latency, Duration::from_millis(125)); + assert_eq!(net_address_with_stats.avg_latency.unwrap(), Duration::from_millis(125)); } #[test] @@ -423,18 +468,53 @@ mod test { #[test] fn test_calculate_quality_score() { - let address = "/ip4/123.0.0.123/tcp/8000".parse().unwrap(); - let mut address = MultiaddrWithStats::new(address, PeerAddressSource::Config); - assert_eq!(address.quality_score, 1000); + let address_raw: Multiaddr = "/ip4/123.0.0.123/tcp/8000".parse().unwrap(); + let mut address = MultiaddrWithStats::new(address_raw.clone(), PeerAddressSource::Config); + assert_eq!(address.quality_score, None); + address.mark_last_seen_now(); - assert!(address.quality_score > 100); + assert!(address.quality_score.unwrap() >= 990); // 1000 with a margin of 10s (10) delayed last seen + + let mut address = MultiaddrWithStats::new(address_raw.clone(), PeerAddressSource::Config); + address.update_latency(Duration::from_millis(1000)); + assert_eq!(address.get_averag_latency().unwrap(), Duration::from_millis(1000)); + assert!(address.quality_score.unwrap() >= 980); // 990 with a margin of 10s (10) delayed last seen + + let mut address = MultiaddrWithStats::new(address_raw.clone(), PeerAddressSource::Config); + address.update_latency(Duration::from_millis(1500)); + address.update_latency(Duration::from_millis(2500)); + address.update_latency(Duration::from_millis(3500)); + assert_eq!(address.get_averag_latency().unwrap(), Duration::from_millis(2500)); + assert!(address.quality_score.unwrap() >= 965); // 975 with a margin of 10s (10) delayed last seen + + let mut address = MultiaddrWithStats::new(address_raw.clone(), PeerAddressSource::Config); + address.update_latency(Duration::from_millis(3500)); + address.update_latency(Duration::from_millis(4500)); + address.update_latency(Duration::from_millis(5500)); + assert_eq!(address.get_averag_latency().unwrap(), Duration::from_millis(4500)); + assert!(address.quality_score.unwrap() >= 945); // 955 with a margin of 10s (10) delayed last seen + + let mut address = MultiaddrWithStats::new(address_raw.clone(), PeerAddressSource::Config); + address.update_latency(Duration::from_millis(5500)); + address.update_latency(Duration::from_millis(6500)); + address.update_latency(Duration::from_millis(7500)); + assert_eq!(address.get_averag_latency().unwrap(), Duration::from_millis(6500)); + assert!(address.quality_score.unwrap() >= 925); // 935 with a margin of 10s (10) delayed last seen + + let mut address = MultiaddrWithStats::new(address_raw.clone(), PeerAddressSource::Config); + address.update_latency(Duration::from_millis(9000)); + address.update_latency(Duration::from_millis(10000)); + address.update_latency(Duration::from_millis(11000)); + assert_eq!(address.get_averag_latency().unwrap(), Duration::from_millis(10000)); + assert!(address.quality_score.unwrap() >= 890); // 900 with a margin of 10s (10) delayed last seen + address.mark_failed_connection_attempt("Testing".to_string()); - assert!(address.quality_score <= 100); + assert_eq!(address.quality_score.unwrap(), 0); let another_addr = "/ip4/1.0.0.1/tcp/8000".parse().unwrap(); let another_addr = MultiaddrWithStats::new(another_addr, PeerAddressSource::Config); - assert_eq!(another_addr.quality_score, 1000); + assert_eq!(another_addr.quality_score, None); - assert_eq!(another_addr.cmp(&address), Ordering::Greater); + assert_eq!(another_addr.cmp(&address), Ordering::Less); } } diff --git a/comms/core/src/net_address/mutliaddresses_with_stats.rs b/comms/core/src/net_address/mutliaddresses_with_stats.rs index 9d7c9d9e4b..9e8b21a61f 100644 --- a/comms/core/src/net_address/mutliaddresses_with_stats.rs +++ b/comms/core/src/net_address/mutliaddresses_with_stats.rs @@ -9,10 +9,14 @@ use std::{ }; use chrono::NaiveDateTime; +use log::trace; use multiaddr::Multiaddr; use serde::{Deserialize, Serialize}; use crate::net_address::{multiaddr_with_stats::PeerAddressSource, MultiaddrWithStats}; + +const LOG_TARGET: &str = "comms::net_address::multiaddresses_with_stats"; + const MAX_ADDRESSES: usize = 10; /// This struct is used to store a set of different net addresses such as IPv4, IPv6, Tor or I2P for a single peer. @@ -178,15 +182,24 @@ impl MultiaddressesWithStats { } } - /// Mark all addresses as seen. Returns true if all addresses are contained in this instance, otherwise false - pub fn mark_all_addresses_as_last_seen_now(&mut self, addresses: &[Multiaddr]) -> bool { + /// Mark all addresses as seen with latency. Returns true if all addresses are contained in this instance, otherwise + /// false + pub fn mark_all_addresses_as_last_seen_now_with_latency( + &mut self, + addresses: &[Multiaddr], + latency_measurement: Duration, + ) -> bool { let mut all_exist = true; for address in addresses { match self.find_address_mut(address) { Some(addr) => { addr.mark_last_seen_now().mark_last_attempted_now(); + addr.update_latency(latency_measurement); + }, + None => { + trace!(target: LOG_TARGET, "Peer address '{}' not in claim, stats not updated", address); + all_exist = false }, - None => all_exist = false, } } self.sort_addresses(); @@ -204,7 +217,10 @@ impl MultiaddressesWithStats { self.sort_addresses(); true }, - None => false, + None => { + trace!(target: LOG_TARGET, "Peer address '{}' not in claim, stats not updated", address); + false + }, } } @@ -238,7 +254,8 @@ impl MultiaddressesWithStats { /// Sort the addresses with the greatest quality score first fn sort_addresses(&mut self) { - self.addresses.sort_by_key(|addr| cmp::Reverse(addr.quality_score())); + self.addresses + .sort_by_key(|addr| cmp::Reverse(addr.quality_score().unwrap_or_default())); self.addresses.truncate(MAX_ADDRESSES) } } @@ -291,8 +308,6 @@ impl Display for MultiaddressesWithStats { #[cfg(test)] mod test { - use multiaddr::Multiaddr; - use super::*; #[test] @@ -354,15 +369,15 @@ mod test { .mark_last_attempted_now(); assert_eq!( net_addresses.find_address_mut(&net_address1).unwrap().quality_score(), - 200 + Some(1000) ); - let other: MultiaddressesWithStats = MultiaddressesWithStats::from_addresses_with_source( + let address_12: MultiaddressesWithStats = MultiaddressesWithStats::from_addresses_with_source( vec![net_address12.clone()], &PeerAddressSource::Config, ); - net_addresses.merge(&other); - assert!(!net_addresses.contains(&net_address1)); - assert!(net_addresses.contains(&net_address12)); + net_addresses.merge(&address_12); + assert!(net_addresses.contains(&net_address1)); + assert!(!net_addresses.contains(&net_address12)); } #[test] @@ -447,12 +462,18 @@ mod test { assert!(net_addresses.mark_failed_connection_attempt(&net_address3, "error".to_string())); assert!(net_addresses.mark_failed_connection_attempt(&net_address1, "error".to_string())); - assert_eq!(net_addresses.addresses[0].connection_attempts(), 1); + assert_eq!(net_addresses.addresses[0].connection_attempts(), 2); assert_eq!(net_addresses.addresses[1].connection_attempts(), 1); - assert_eq!(net_addresses.addresses[2].connection_attempts(), 2); + assert_eq!(net_addresses.addresses[2].connection_attempts(), 1); + assert!(net_addresses.addresses[0].last_failed_reason().is_some()); + assert!(net_addresses.addresses[1].last_failed_reason().is_some()); + assert!(net_addresses.addresses[2].last_failed_reason().is_some()); net_addresses.reset_connection_attempts(); assert_eq!(net_addresses.addresses[0].connection_attempts(), 0); assert_eq!(net_addresses.addresses[1].connection_attempts(), 0); assert_eq!(net_addresses.addresses[2].connection_attempts(), 0); + assert!(net_addresses.addresses[0].last_failed_reason().is_none()); + assert!(net_addresses.addresses[1].last_failed_reason().is_none()); + assert!(net_addresses.addresses[2].last_failed_reason().is_none()); } } diff --git a/comms/core/src/noise/config.rs b/comms/core/src/noise/config.rs index 1310dfdb89..a18ab1af50 100644 --- a/comms/core/src/noise/config.rs +++ b/comms/core/src/noise/config.rs @@ -25,7 +25,7 @@ use std::{sync::Arc, time::Duration}; use log::*; -use snow::{self, params::NoiseParams}; +use snow::params::NoiseParams; use tari_utilities::ByteArray; use tokio::io::{AsyncRead, AsyncWrite}; diff --git a/comms/core/src/noise/socket.rs b/comms/core/src/noise/socket.rs index fefeb50fcd..bbe3be4b1f 100644 --- a/comms/core/src/noise/socket.rs +++ b/comms/core/src/noise/socket.rs @@ -662,8 +662,6 @@ impl From for NoiseState { #[cfg(test)] mod test { - use std::io; - use futures::future::join; use snow::{params::NoiseParams, Builder, Error, Keypair}; diff --git a/comms/core/src/peer_manager/manager.rs b/comms/core/src/peer_manager/manager.rs index a383fdf437..c1e5efc2f0 100644 --- a/comms/core/src/peer_manager/manager.rs +++ b/comms/core/src/peer_manager/manager.rs @@ -192,6 +192,26 @@ impl PeerManager { } } + pub async fn update_peer_address_latency_and_last_seen( + &self, + pubkey: &CommsPublicKey, + address: &Multiaddr, + latency: Option, + ) -> Result<(), PeerManagerError> { + match self.find_by_public_key(pubkey).await { + Ok(Some(mut peer)) => { + if let Some(val) = latency { + peer.addresses.update_latency(address, val); + } + peer.addresses.mark_last_seen_now(address); + self.add_peer(peer.clone()).await?; + Ok(()) + }, + Ok(None) => Err(PeerManagerError::PeerNotFoundError), + Err(err) => Err(err), + } + } + /// Get a peer matching the given node ID pub async fn direct_identity_node_id(&self, node_id: &NodeId) -> Result, PeerManagerError> { match self.peer_storage.read().await.direct_identity_node_id(node_id) { @@ -238,27 +258,6 @@ impl PeerManager { .closest_peers(node_id, n, excluded_peers, features) } - pub async fn mark_last_seen(&self, node_id: &NodeId, addr: &Multiaddr) -> Result<(), PeerManagerError> { - let mut lock = self.peer_storage.write().await; - let mut peer = lock - .find_by_node_id(node_id)? - .ok_or(PeerManagerError::PeerNotFoundError)?; - let source = peer - .addresses - .iter() - .find(|a| a.address() == addr) - .map(|a| a.source().clone()) - .ok_or_else(|| PeerManagerError::AddressNotFoundError { - address: addr.clone(), - node_id: node_id.clone(), - })?; - // if we have an address, update it - peer.addresses.add_address(addr, &source); - peer.addresses.mark_last_seen_now(addr); - lock.add_peer(peer)?; - Ok(()) - } - /// Fetch n random peers pub async fn random_peers(&self, n: usize, excluded: &[NodeId]) -> Result, PeerManagerError> { // Send to a random set of peers of size n that are Communication Nodes @@ -375,14 +374,6 @@ mod test { use tari_storage::HashmapDatabase; use super::*; - use crate::{ - net_address::MultiaddressesWithStats, - peer_manager::{ - node_id::NodeId, - peer::{Peer, PeerFlags}, - PeerFeatures, - }, - }; fn create_test_peer(ban_flag: bool, features: PeerFeatures) -> Peer { let (_sk, pk) = RistrettoPublicKey::random_keypair(&mut OsRng); diff --git a/comms/core/src/peer_manager/node_id.rs b/comms/core/src/peer_manager/node_id.rs index f6eca4fc68..378847bdcb 100644 --- a/comms/core/src/peer_manager/node_id.rs +++ b/comms/core/src/peer_manager/node_id.rs @@ -265,13 +265,10 @@ where D: Deserializer<'de> { #[cfg(test)] mod test { - use tari_crypto::{ - keys::{PublicKey, SecretKey}, - tari_utilities::byte_array::ByteArray, - }; + use tari_crypto::keys::{PublicKey, SecretKey}; use super::*; - use crate::types::{CommsPublicKey, CommsSecretKey}; + use crate::types::CommsSecretKey; #[test] fn display() { diff --git a/comms/core/src/peer_manager/peer.rs b/comms/core/src/peer_manager/peer.rs index 3ec13afe13..2123a6c3b6 100644 --- a/comms/core/src/peer_manager/peer.rs +++ b/comms/core/src/peer_manager/peer.rs @@ -168,6 +168,15 @@ impl Peer { .and_then(|a| a.last_attempted()) } + /// The last address used to connect to the peer + pub fn last_address_used(&self) -> Option { + self.addresses + .addresses() + .iter() + .max_by_key(|a| a.last_attempted()) + .map(|a| a.address().clone()) + } + /// Returns true if the peer is marked as offline pub fn is_offline(&self) -> bool { self.addresses.offline_at().is_some() @@ -199,6 +208,11 @@ impl Peer { self.addresses.last_seen() } + /// Provides info about the failure status of all addresses + pub fn all_addresses_failed(&self) -> bool { + self.addresses.iter().all(|a| a.last_failed_reason().is_some()) + } + /// Provides that length of time since the last successful interaction with the peer pub fn last_seen_since(&self) -> Option { self.last_seen() @@ -341,7 +355,6 @@ mod test { }; use super::*; - use crate::{net_address::MultiaddressesWithStats, peer_manager::NodeId, types::CommsPublicKey}; #[test] fn test_is_banned_and_ban_for() { @@ -362,7 +375,7 @@ mod test { Default::default(), ); assert!(!peer.is_banned()); - peer.ban_for(Duration::from_millis(std::u64::MAX), "Very long manual ban".to_string()); + peer.ban_for(Duration::from_millis(u64::MAX), "Very long manual ban".to_string()); assert_eq!(peer.reason_banned(), &"Very long manual ban".to_string()); assert!(peer.is_banned()); peer.ban_for(Duration::from_millis(0), "".to_string()); diff --git a/comms/core/src/peer_manager/peer_query.rs b/comms/core/src/peer_manager/peer_query.rs index 71f56b3fd7..9f37a9af15 100644 --- a/comms/core/src/peer_manager/peer_query.rs +++ b/comms/core/src/peer_manager/peer_query.rs @@ -215,11 +215,7 @@ mod test { use super::*; use crate::{ net_address::{MultiaddressesWithStats, PeerAddressSource}, - peer_manager::{ - node_id::NodeId, - peer::{Peer, PeerFlags}, - PeerFeatures, - }, + peer_manager::{peer::PeerFlags, PeerFeatures}, }; fn create_test_peer(ban_flag: bool) -> Peer { @@ -246,7 +242,7 @@ mod test { #[test] fn limit_query() { - // Create 20 peers were the 1st and last one is bad + // Create some good peers let db = HashmapDatabase::new(); let mut id_counter = 0; @@ -262,11 +258,7 @@ mod test { #[test] fn select_where_query() { - // Create peer manager with random peers - let mut sample_peers = Vec::new(); - // Create 20 peers were the 1st and last one is bad - let _rng = rand::rngs::OsRng; - sample_peers.push(create_test_peer(true)); + // Create some good and bad peers let db = HashmapDatabase::new(); let mut id_counter = 0; @@ -292,11 +284,7 @@ mod test { #[test] fn select_where_limit_query() { - // Create peer manager with random peers - let mut sample_peers = Vec::new(); - // Create 20 peers were the 1st and last one is bad - let _rng = rand::rngs::OsRng; - sample_peers.push(create_test_peer(true)); + // Create some good and bad peers let db = HashmapDatabase::new(); let mut id_counter = 0; @@ -333,11 +321,7 @@ mod test { #[test] fn sort_by_query() { - // Create peer manager with random peers - let mut sample_peers = Vec::new(); - // Create 20 peers were the 1st and last one is bad - let _rng = rand::rngs::OsRng; - sample_peers.push(create_test_peer(true)); + // Create some good and bad peers let db = HashmapDatabase::new(); let mut id_counter = 0; diff --git a/comms/core/src/peer_manager/peer_storage.rs b/comms/core/src/peer_manager/peer_storage.rs index 760d823cf3..43e462968a 100644 --- a/comms/core/src/peer_manager/peer_storage.rs +++ b/comms/core/src/peer_manager/peer_storage.rs @@ -538,7 +538,7 @@ mod test { use super::*; use crate::{ net_address::{MultiaddrWithStats, MultiaddressesWithStats, PeerAddressSource}, - peer_manager::{peer::PeerFlags, PeerFeatures}, + peer_manager::peer::PeerFlags, }; #[test] diff --git a/comms/core/src/peer_validator/helpers.rs b/comms/core/src/peer_validator/helpers.rs index 99e1eaa386..4b6b47f910 100644 --- a/comms/core/src/peer_validator/helpers.rs +++ b/comms/core/src/peer_validator/helpers.rs @@ -200,7 +200,6 @@ mod test { use multiaddr::multiaddr; use super::*; - use crate::peer_validator::error::PeerValidatorError; #[test] fn validate_address_strict() { diff --git a/comms/core/src/pipeline/inbound.rs b/comms/core/src/pipeline/inbound.rs index 6b0149b12e..12ed95aa7f 100644 --- a/comms/core/src/pipeline/inbound.rs +++ b/comms/core/src/pipeline/inbound.rs @@ -137,12 +137,9 @@ where #[cfg(test)] mod test { - use std::time::Duration; - use futures::future; use tari_shutdown::Shutdown; use tari_test_utils::collect_recv; - use tokio::{sync::mpsc, time}; use tower::service_fn; use super::*; diff --git a/comms/core/src/pipeline/outbound.rs b/comms/core/src/pipeline/outbound.rs index 6a8730689f..1141ca8bfe 100644 --- a/comms/core/src/pipeline/outbound.rs +++ b/comms/core/src/pipeline/outbound.rs @@ -119,11 +119,9 @@ where #[cfg(test)] mod test { - use std::time::Duration; - use bytes::Bytes; use tari_test_utils::collect_recv; - use tokio::{sync::mpsc, time}; + use tokio::sync::mpsc; use super::*; use crate::{message::OutboundMessage, pipeline::SinkService, utils}; diff --git a/comms/core/src/protocol/error.rs b/comms/core/src/protocol/error.rs index e702aa9a35..3474e0b687 100644 --- a/comms/core/src/protocol/error.rs +++ b/comms/core/src/protocol/error.rs @@ -31,7 +31,7 @@ pub enum ProtocolError { IoError(#[from] io::Error), #[error("Invalid flag: {0}")] InvalidFlag(String), - #[error("The ProtocolId was longer than {}", u8::max_value())] + #[error("The ProtocolId was longer than {}", u8::MAX)] ProtocolIdTooLong, #[error("Protocol negotiation failed because the peer did not accept any of the given protocols: {protocols}")] ProtocolOutboundNegotiationFailed { protocols: String }, diff --git a/comms/core/src/protocol/messaging/inbound.rs b/comms/core/src/protocol/messaging/inbound.rs index d0a1eeaa31..f2c393af86 100644 --- a/comms/core/src/protocol/messaging/inbound.rs +++ b/comms/core/src/protocol/messaging/inbound.rs @@ -22,8 +22,9 @@ use std::io; -use futures::StreamExt; +use futures::{future, future::Either, SinkExt, StreamExt}; use log::*; +use tari_shutdown::ShutdownSignal; use tokio::{ io::{AsyncRead, AsyncWrite}, sync::{broadcast, mpsc}, @@ -42,6 +43,7 @@ pub struct InboundMessaging { inbound_message_tx: mpsc::Sender, messaging_events_tx: broadcast::Sender, enable_message_received_event: bool, + shutdown_signal: ShutdownSignal, } impl InboundMessaging { @@ -50,16 +52,18 @@ impl InboundMessaging { inbound_message_tx: mpsc::Sender, messaging_events_tx: broadcast::Sender, enable_message_received_event: bool, + shutdown_signal: ShutdownSignal, ) -> Self { Self { peer, inbound_message_tx, messaging_events_tx, enable_message_received_event, + shutdown_signal, } } - pub async fn run(self, socket: S) + pub async fn run(mut self, socket: S) where S: AsyncRead + AsyncWrite + Unpin { let peer = &self.peer; #[cfg(feature = "metrics")] @@ -71,10 +75,9 @@ impl InboundMessaging { ); let stream = MessagingProtocol::framed(socket); - tokio::pin!(stream); - while let Some(result) = stream.next().await { + while let Either::Right((Some(result), _)) = future::select(self.shutdown_signal.wait(), stream.next()).await { match result { Ok(raw_msg) => { #[cfg(feature = "metrics")] @@ -138,6 +141,8 @@ impl InboundMessaging { } } + let _ignore = stream.close().await; + let _ignore = self .messaging_events_tx .send(MessagingEvent::InboundProtocolExited(peer.clone())); diff --git a/comms/core/src/protocol/messaging/protocol.rs b/comms/core/src/protocol/messaging/protocol.rs index ac7f69ab70..0fc6a7d5a9 100644 --- a/comms/core/src/protocol/messaging/protocol.rs +++ b/comms/core/src/protocol/messaging/protocol.rs @@ -351,6 +351,7 @@ impl MessagingProtocol { inbound_message_tx, messaging_events_tx, self.enable_message_received_event, + self.shutdown_signal.clone(), ); let handle = tokio::spawn(inbound_messaging.run(substream)); self.active_inbound.insert(peer, handle); diff --git a/comms/core/src/protocol/messaging/test.rs b/comms/core/src/protocol/messaging/test.rs index 248d33a4d2..2a27e9e7ca 100644 --- a/comms/core/src/protocol/messaging/test.rs +++ b/comms/core/src/protocol/messaging/test.rs @@ -55,6 +55,7 @@ use crate::{ }; static TEST_MSG1: Bytes = Bytes::from_static(b"TEST_MSG1"); +static TEST_MSG2: Bytes = Bytes::from_static(b"TEST_MSG2"); static MESSAGING_PROTOCOL_ID: ProtocolId = ProtocolId::from_static(b"test/msg"); @@ -128,21 +129,21 @@ async fn new_inbound_substream_handling() { // Create connected memory sockets - we use each end of the connection as if they exist on different nodes let (_, muxer_ours, mut muxer_theirs) = transport::build_multiplexed_connections().await; - // Notify the messaging protocol that a new substream has been established that wants to talk the messaging. let stream_ours = muxer_ours.get_yamux_control().open_stream().await.unwrap(); + + let mut framed_ours = MessagingProtocol::framed(stream_ours); + framed_ours.send(TEST_MSG1.clone()).await.unwrap(); + + // Notify the messaging protocol that a new substream has been established that wants to talk the messaging. + let stream_theirs = muxer_theirs.incoming_mut().next().await.unwrap(); proto_tx .send(ProtocolNotification::new( MESSAGING_PROTOCOL_ID.clone(), - ProtocolEvent::NewInboundSubstream(expected_node_id.clone(), stream_ours), + ProtocolEvent::NewInboundSubstream(expected_node_id.clone(), stream_theirs), )) .await .unwrap(); - let stream_theirs = muxer_theirs.incoming_mut().next().await.unwrap(); - let mut framed_theirs = MessagingProtocol::framed(stream_theirs); - - framed_theirs.send(TEST_MSG1.clone()).await.unwrap(); - let in_msg = time::timeout(Duration::from_secs(5), inbound_msg_rx.recv()) .await .unwrap() @@ -370,42 +371,55 @@ async fn new_inbound_substream_only_single_session_permitted() { // Create connected memory sockets - we use each end of the connection as if they exist on different nodes let (_, muxer_ours, mut muxer_theirs) = transport::build_multiplexed_connections().await; + // Spawn a task to deal with incoming substreams + tokio::spawn({ + let expected_node_id = expected_node_id.clone(); + async move { + while let Some(stream_theirs) = muxer_theirs.incoming_mut().next().await { + proto_tx + .send(ProtocolNotification::new( + MESSAGING_PROTOCOL_ID.clone(), + ProtocolEvent::NewInboundSubstream(expected_node_id.clone(), stream_theirs), + )) + .await + .unwrap(); + } + } + }); - // Notify the messaging protocol that a new substream has been established that wants to talk the messaging. + // Open first stream let stream_ours = muxer_ours.get_yamux_control().open_stream().await.unwrap(); - proto_tx - .send(ProtocolNotification::new( - MESSAGING_PROTOCOL_ID.clone(), - ProtocolEvent::NewInboundSubstream(expected_node_id.clone(), stream_ours), - )) - .await - .unwrap(); + let mut framed_ours = MessagingProtocol::framed(stream_ours); + framed_ours.send(TEST_MSG1.clone()).await.unwrap(); - // First stream is open - let stream_theirs = muxer_theirs.incoming_mut().next().await.unwrap(); - - // Open another one for messaging - let stream_ours2 = muxer_ours.get_yamux_control().open_stream().await.unwrap(); - proto_tx - .send(ProtocolNotification::new( - MESSAGING_PROTOCOL_ID.clone(), - ProtocolEvent::NewInboundSubstream(expected_node_id.clone(), stream_ours2), - )) + // Message comes through + let in_msg = time::timeout(Duration::from_secs(5), inbound_msg_rx.recv()) .await + .unwrap() .unwrap(); + assert_eq!(in_msg.source_peer, expected_node_id); + assert_eq!(in_msg.body, TEST_MSG1); // Check the second stream closes immediately - let stream_theirs2 = muxer_theirs.incoming_mut().next().await.unwrap(); - let mut framed_ours2 = MessagingProtocol::framed(stream_theirs2); - let next = framed_ours2.next().await; - // The stream is closed - assert!(next.is_none()); - - // The first stream is still active - let mut framed_theirs = MessagingProtocol::framed(stream_theirs); + let stream_ours2 = muxer_ours.get_yamux_control().open_stream().await.unwrap(); - framed_theirs.send(TEST_MSG1.clone()).await.unwrap(); + let mut framed_ours2 = MessagingProtocol::framed(stream_ours2); + // Check that it eventually exits. The first send will initiate the substream and send. Once the other side closes + // the connection it takes a few sends for that to be detected and the substream to be closed. + loop { + // This message will not go through + if let Err(e) = framed_ours2.send(TEST_MSG2.clone()).await { + assert_eq!( + e.to_string().split(':').nth(1).map(|s| s.trim()), + Some("connection is closed"), + "Expected connection to be closed but got '{e}'" + ); + break; + } + } + // First stream still open + framed_ours.send(TEST_MSG1.clone()).await.unwrap(); let in_msg = time::timeout(Duration::from_secs(5), inbound_msg_rx.recv()) .await .unwrap() @@ -414,23 +428,14 @@ async fn new_inbound_substream_only_single_session_permitted() { assert_eq!(in_msg.body, TEST_MSG1); // Close the first - framed_theirs.close().await.unwrap(); + framed_ours.close().await.unwrap(); // Open another one for messaging - let stream_ours2 = muxer_ours.get_yamux_control().open_stream().await.unwrap(); - proto_tx - .send(ProtocolNotification::new( - MESSAGING_PROTOCOL_ID.clone(), - ProtocolEvent::NewInboundSubstream(expected_node_id.clone(), stream_ours2), - )) - .await - .unwrap(); - - let stream_theirs = muxer_theirs.incoming_mut().next().await.unwrap(); - let mut framed_theirs = MessagingProtocol::framed(stream_theirs); - framed_theirs.send(TEST_MSG1.clone()).await.unwrap(); + let stream_ours = muxer_ours.get_yamux_control().open_stream().await.unwrap(); + let mut framed_ours = MessagingProtocol::framed(stream_ours); + framed_ours.send(TEST_MSG1.clone()).await.unwrap(); - // The second message comes through + // The third message comes through let in_msg = time::timeout(Duration::from_secs(5), inbound_msg_rx.recv()) .await .unwrap() diff --git a/comms/core/src/protocol/rpc/client/mod.rs b/comms/core/src/protocol/rpc/client/mod.rs index 292c397930..1995715100 100644 --- a/comms/core/src/protocol/rpc/client/mod.rs +++ b/comms/core/src/protocol/rpc/client/mod.rs @@ -75,7 +75,6 @@ use crate::{ RpcError, RpcServerError, RpcStatus, - RPC_CHUNKING_MAX_CHUNKS, }, ProtocolId, }, @@ -932,53 +931,17 @@ where TSubstream: AsyncRead + AsyncWrite + Unpin pub async fn read_response(&mut self) -> Result { let timer = Instant::now(); - let mut resp = self.next().await?; + let resp = self.next().await?; self.time_to_first_msg = Some(timer.elapsed()); self.check_response(&resp)?; - let mut chunk_count = 1; - let mut last_chunk_flags = - RpcMessageFlags::from_bits(u8::try_from(resp.flags).map_err(|_| { - RpcStatus::protocol_error(&format!("invalid message flag: must be less than {}", u8::MAX)) - })?) - .ok_or(RpcStatus::protocol_error(&format!( - "invalid message flag, does not match any flags ({})", - resp.flags - )))?; - let mut last_chunk_size = resp.payload.len(); - self.bytes_read += last_chunk_size; - loop { - trace!( - target: LOG_TARGET, - "Chunk {} received (flags={:?}, {} bytes, {} total)", - chunk_count, - last_chunk_flags, - last_chunk_size, - resp.payload.len() - ); - if !last_chunk_flags.is_more() { - return Ok(resp); - } - - if chunk_count >= RPC_CHUNKING_MAX_CHUNKS { - return Err(RpcError::RemotePeerExceededMaxChunkCount { - expected: RPC_CHUNKING_MAX_CHUNKS, - }); - } - - let msg = self.next().await?; - last_chunk_flags = RpcMessageFlags::from_bits(u8::try_from(msg.flags).map_err(|_| { - RpcStatus::protocol_error(&format!("invalid message flag: must be less than {}", u8::MAX)) - })?) - .ok_or(RpcStatus::protocol_error(&format!( - "invalid message flag, does not match any flags ({})", - resp.flags - )))?; - last_chunk_size = msg.payload.len(); - self.bytes_read += last_chunk_size; - self.check_response(&resp)?; - resp.payload.extend(msg.payload); - chunk_count += 1; - } + self.bytes_read = resp.payload.len(); + trace!( + target: LOG_TARGET, + "Received {} bytes in {:.2?}", + resp.payload.len(), + self.time_to_first_msg.unwrap_or_default() + ); + Ok(resp) } pub async fn read_ack(&mut self) -> Result { diff --git a/comms/core/src/protocol/rpc/client/tests.rs b/comms/core/src/protocol/rpc/client/tests.rs index ecd484f3dd..4f8d0a1491 100644 --- a/comms/core/src/protocol/rpc/client/tests.rs +++ b/comms/core/src/protocol/rpc/client/tests.rs @@ -76,7 +76,10 @@ async fn setup(num_concurrent_sessions: usize) -> (PeerConnection, PeerConnectio mod lazy_pool { use super::*; - use crate::protocol::rpc::client::pool::{LazyPool, RpcClientPoolError}; + use crate::{ + protocol::rpc::client::pool::{LazyPool, RpcClientPoolError}, + Minimized, + }; #[tokio::test] async fn it_connects_lazily() { @@ -168,7 +171,7 @@ mod lazy_pool { let (mut peer_conn, _, _shutdown) = setup(2).await; let mut pool = LazyPool::::new(peer_conn.clone(), 2, Default::default()); let mut _conn1 = pool.get_least_used_or_connect().await.unwrap(); - peer_conn.disconnect().await.unwrap(); + peer_conn.disconnect(Minimized::No).await.unwrap(); let err = pool.get_least_used_or_connect().await.unwrap_err(); unpack_enum!(RpcClientPoolError::PeerConnectionDropped { .. } = err); } diff --git a/comms/core/src/protocol/rpc/message.rs b/comms/core/src/protocol/rpc/message.rs index ed377fe50f..105e524359 100644 --- a/comms/core/src/protocol/rpc/message.rs +++ b/comms/core/src/protocol/rpc/message.rs @@ -24,19 +24,24 @@ use std::{convert::TryFrom, fmt, time::Duration}; use bitflags::bitflags; use bytes::Bytes; +use log::warn; use super::RpcError; use crate::{ proto, proto::rpc::rpc_session_reply::SessionResult, - protocol::rpc::{ - body::{Body, IntoBody}, - context::RequestContext, - error::HandshakeRejectReason, - RpcStatusCode, + protocol::{ + rpc, + rpc::{ + body::{Body, IntoBody}, + context::RequestContext, + error::HandshakeRejectReason, + RpcStatusCode, + }, }, }; +const LOG_TARGET: &str = "comms::rpc::message"; #[derive(Debug)] pub struct Request { pub(super) context: Option, @@ -203,8 +208,6 @@ bitflags! { const FIN = 0x01; /// Typically sent with empty contents and used to confirm a substream is alive. const ACK = 0x02; - /// Another chunk to be received - const MORE = 0x04; } } impl RpcMessageFlags { @@ -215,10 +218,6 @@ impl RpcMessageFlags { pub fn is_ack(self) -> bool { self.contains(Self::ACK) } - - pub fn is_more(self) -> bool { - self.contains(Self::MORE) - } } impl Default for RpcMessageFlags { @@ -276,6 +275,21 @@ impl RpcResponse { payload: self.payload.to_vec(), } } + + pub fn exceeded_message_size(self) -> RpcResponse { + let msg = format!( + "The response size exceeded the maximum allowed payload size. Max = {} bytes, Got = {} bytes", + rpc::max_response_payload_size() as f32, + self.payload.len() as f32, + ); + warn!(target: LOG_TARGET, "{}", msg); + RpcResponse { + request_id: self.request_id, + status: RpcStatusCode::MalformedResponse, + flags: RpcMessageFlags::FIN, + payload: msg.into_bytes().into(), + } + } } impl Default for RpcResponse { diff --git a/comms/core/src/protocol/rpc/mod.rs b/comms/core/src/protocol/rpc/mod.rs index 35b5ebda6f..23a0e6e1d4 100644 --- a/comms/core/src/protocol/rpc/mod.rs +++ b/comms/core/src/protocol/rpc/mod.rs @@ -30,23 +30,14 @@ mod test; /// Maximum frame size of each RPC message. This is enforced in tokio's length delimited codec. /// This can be thought of as the hard limit on message size. -pub const RPC_MAX_FRAME_SIZE: usize = 3 * 1024 * 1024; // 3 MiB -/// Maximum number of chunks into which a message can be broken up. -const RPC_CHUNKING_MAX_CHUNKS: usize = 16; // 16 x 256 Kib = 4 MiB max combined message size -const RPC_CHUNKING_THRESHOLD: usize = 256 * 1024; -const RPC_CHUNKING_SIZE_LIMIT: usize = 384 * 1024; +pub const RPC_MAX_FRAME_SIZE: usize = 4 * 1024 * 1024; // 4 MiB /// The maximum request payload size const fn max_request_size() -> usize { RPC_MAX_FRAME_SIZE } -/// The maximum size for a single RPC response message -const fn max_response_size() -> usize { - RPC_CHUNKING_MAX_CHUNKS * RPC_CHUNKING_THRESHOLD -} - -/// The maximum size for a single RPC response excluding overhead +/// The maximum size for a single RPC response body excluding response header overhead const fn max_response_payload_size() -> usize { // RpcResponse overhead is: // - 4 varint protobuf fields, each field ID is 1 byte @@ -54,7 +45,7 @@ const fn max_response_payload_size() -> usize { // - 1 length varint for the payload, allow for 5 bytes to be safe (max_payload_size being technically too small is // fine, being too large isn't) const MAX_HEADER_SIZE: usize = 4 + 4 * 5; - max_response_size() - MAX_HEADER_SIZE + RPC_MAX_FRAME_SIZE - MAX_HEADER_SIZE } mod body; diff --git a/comms/core/src/protocol/rpc/server/chunking.rs b/comms/core/src/protocol/rpc/server/chunking.rs deleted file mode 100644 index d721af9c99..0000000000 --- a/comms/core/src/protocol/rpc/server/chunking.rs +++ /dev/null @@ -1,272 +0,0 @@ -// 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::cmp; - -use bytes::Bytes; -use log::*; - -use super::LOG_TARGET; -use crate::{ - proto, - protocol::{ - rpc, - rpc::{ - message::{RpcMessageFlags, RpcResponse}, - RpcStatusCode, - RPC_CHUNKING_SIZE_LIMIT, - RPC_CHUNKING_THRESHOLD, - }, - }, -}; - -pub(super) struct ChunkedResponseIter { - message: RpcResponse, - initial_payload_size: usize, - has_emitted_once: bool, - num_chunks: usize, - total_chunks: usize, -} - -fn calculate_total_chunk_count(payload_len: usize) -> usize { - let mut total_chunks = payload_len / RPC_CHUNKING_THRESHOLD; - let excess = (payload_len % RPC_CHUNKING_THRESHOLD) + RPC_CHUNKING_THRESHOLD; - if total_chunks == 0 || excess > RPC_CHUNKING_SIZE_LIMIT { - // If the chunk (threshold size) + excess cannot fit in the RPC_CHUNKING_SIZE_LIMIT, then we'll emit another - // frame smaller than threshold size - total_chunks += 1; - } - - total_chunks -} - -impl ChunkedResponseIter { - pub fn new(message: RpcResponse) -> Self { - let len = message.payload.len(); - Self { - initial_payload_size: message.payload.len(), - message, - has_emitted_once: false, - num_chunks: 0, - total_chunks: calculate_total_chunk_count(len), - } - } - - fn remaining(&self) -> usize { - self.message.payload.len() - } - - fn payload_mut(&mut self) -> &mut Bytes { - &mut self.message.payload - } - - fn payload(&self) -> &Bytes { - &self.message.payload - } - - fn get_next_chunk(&mut self) -> Option { - let len = self.payload().len(); - if len == 0 { - if self.num_chunks > 1 { - debug!( - target: LOG_TARGET, - "Emitted {} chunks (Avg.Size: {} bytes, Total: {} bytes)", - self.num_chunks, - self.initial_payload_size / self.num_chunks, - self.initial_payload_size - ); - } - return None; - } - - // If the payload is within the maximum chunk size, simply return the rest of it - if len <= RPC_CHUNKING_SIZE_LIMIT { - let chunk = self.payload_mut().split_to(len); - self.num_chunks += 1; - trace!( - target: LOG_TARGET, - "Emitting chunk {}/{} ({} bytes)", - self.num_chunks, - self.total_chunks, - chunk.len() - ); - return Some(chunk); - } - - let chunk_size = cmp::min(len, RPC_CHUNKING_THRESHOLD); - let chunk = self.payload_mut().split_to(chunk_size); - - self.num_chunks += 1; - trace!( - target: LOG_TARGET, - "Emitting chunk {}/{} ({} bytes)", - self.num_chunks, - self.total_chunks, - chunk.len() - ); - Some(chunk) - } - - fn is_last_chunk(&self) -> bool { - self.num_chunks == self.total_chunks - } - - fn exceeded_message_size(&self) -> proto::rpc::RpcResponse { - const BYTES_PER_MB: f32 = 1024.0 * 1024.0; - // Precision loss is acceptable because this is for display purposes only - let msg = format!( - "The response size exceeded the maximum allowed payload size. Max = {:.4} MiB, Got = {:.4} MiB", - rpc::max_response_payload_size() as f32 / BYTES_PER_MB, - self.message.payload.len() as f32 / BYTES_PER_MB, - ); - warn!(target: LOG_TARGET, "{}", msg); - proto::rpc::RpcResponse { - request_id: self.message.request_id, - status: RpcStatusCode::MalformedResponse as u32, - flags: RpcMessageFlags::FIN.bits().into(), - payload: msg.into_bytes(), - } - } -} - -impl Iterator for ChunkedResponseIter { - type Item = proto::rpc::RpcResponse; - - fn next(&mut self) -> Option { - // Edge case: the initial message has an empty payload. - if self.initial_payload_size == 0 { - if self.has_emitted_once { - return None; - } - self.has_emitted_once = true; - return Some(self.message.to_proto()); - } - - // Edge case: the total message size cannot fit into the maximum allowed chunks - if self.remaining() > rpc::max_response_payload_size() { - if self.has_emitted_once { - return None; - } - self.has_emitted_once = true; - return Some(self.exceeded_message_size()); - } - - let request_id = self.message.request_id; - let chunk = self.get_next_chunk()?; - - // status MUST be set for the first chunked message, all subsequent chunk messages MUST have a status of 0 - let mut status = 0; - if !self.has_emitted_once { - status = self.message.status as u32; - } - self.has_emitted_once = true; - - let mut flags = self.message.flags; - if !self.is_last_chunk() { - // For all chunks except the last the MORE flag MUST be set - flags |= RpcMessageFlags::MORE; - } - let msg = proto::rpc::RpcResponse { - request_id, - status, - flags: flags.bits().into(), - payload: chunk.to_vec(), - }; - - Some(msg) - } -} - -#[cfg(test)] -mod test { - use std::{convert::TryFrom, iter}; - - use super::*; - - fn create(size: usize) -> ChunkedResponseIter { - let msg = RpcResponse { - payload: iter::repeat(0).take(size).collect(), - ..Default::default() - }; - ChunkedResponseIter::new(msg) - } - - #[test] - fn it_emits_a_zero_size_message() { - let iter = create(0); - assert_eq!(iter.total_chunks, 1); - let msgs = iter.collect::>(); - assert_eq!(msgs.len(), 1); - assert!(!RpcMessageFlags::from_bits(u8::try_from(msgs[0].flags).unwrap()) - .unwrap() - .is_more()); - } - - #[test] - fn it_emits_one_message_below_threshold() { - let iter = create(RPC_CHUNKING_THRESHOLD - 1); - assert_eq!(iter.total_chunks, 1); - let msgs = iter.collect::>(); - assert_eq!(msgs.len(), 1); - assert!(!RpcMessageFlags::from_bits(u8::try_from(msgs[0].flags).unwrap()) - .unwrap() - .is_more()); - } - - #[test] - fn it_emits_a_single_message() { - let iter = create(RPC_CHUNKING_SIZE_LIMIT - 1); - assert_eq!(iter.count(), 1); - - let iter = create(RPC_CHUNKING_SIZE_LIMIT); - assert_eq!(iter.count(), 1); - } - - #[test] - fn it_emits_an_expected_number_of_chunks() { - let iter = create(RPC_CHUNKING_THRESHOLD * 2); - assert_eq!(iter.count(), 2); - - let diff = RPC_CHUNKING_SIZE_LIMIT - RPC_CHUNKING_THRESHOLD; - let iter = create(RPC_CHUNKING_THRESHOLD * 2 + diff); - assert_eq!(iter.count(), 2); - - let iter = create(RPC_CHUNKING_THRESHOLD * 2 + diff + 1); - assert_eq!(iter.count(), 3); - } - - #[test] - fn it_sets_the_more_flag_except_last() { - use std::convert::TryFrom; - let iter = create(RPC_CHUNKING_THRESHOLD * 3); - let msgs = iter.collect::>(); - assert!(RpcMessageFlags::from_bits(u8::try_from(msgs[0].flags).unwrap()) - .unwrap() - .is_more()); - assert!(RpcMessageFlags::from_bits(u8::try_from(msgs[1].flags).unwrap()) - .unwrap() - .is_more()); - assert!(!RpcMessageFlags::from_bits(u8::try_from(msgs[2].flags).unwrap()) - .unwrap() - .is_more()); - } -} diff --git a/comms/core/src/protocol/rpc/server/mod.rs b/comms/core/src/protocol/rpc/server/mod.rs index 84996596f4..ae9cbb23ee 100644 --- a/comms/core/src/protocol/rpc/server/mod.rs +++ b/comms/core/src/protocol/rpc/server/mod.rs @@ -20,8 +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. -mod chunking; -use chunking::ChunkedResponseIter; +// mod chunking; mod error; pub use error::RpcServerError; @@ -52,7 +51,7 @@ use std::{ time::{Duration, Instant}, }; -use futures::{future, stream, stream::FuturesUnordered, SinkExt, StreamExt}; +use futures::{future, stream::FuturesUnordered, SinkExt, StreamExt}; use log::*; use prost::Message; use router::Router; @@ -79,6 +78,7 @@ use crate::{ peer_manager::NodeId, proto, protocol::{ + rpc, rpc::{ body::BodyBytes, message::{RpcMethod, RpcResponse}, @@ -749,12 +749,15 @@ where let mut stream = body .into_message() .map(|result| into_response(request_id, result)) - .flat_map(move |message| { + .map(move |mut message| { + if message.payload.len() > rpc::max_response_payload_size() { + message = message.exceeded_message_size(); + } #[cfg(feature = "metrics")] if !message.status.is_ok() { metrics::status_error_counter(&node_id, &protocol, message.status).inc(); } - stream::iter(ChunkedResponseIter::new(message)) + message.to_proto() }) .map(|resp| Bytes::from(resp.to_encoded_bytes())); diff --git a/comms/core/src/protocol/rpc/test/smoke.rs b/comms/core/src/protocol/rpc/test/smoke.rs index d486cfff6f..17452b4bd5 100644 --- a/comms/core/src/protocol/rpc/test/smoke.rs +++ b/comms/core/src/protocol/rpc/test/smoke.rs @@ -27,20 +27,24 @@ use tari_shutdown::Shutdown; use tari_test_utils::unpack_enum; use tari_utilities::hex::Hex; use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, sync::{mpsc, RwLock}, task, time, }; +use tokio_stream::Stream; use crate::{ framing, - multiplexing::Yamux, + multiplexing::{Control, Yamux}, + peer_manager::NodeId, protocol::{ rpc, rpc::{ context::RpcCommsBackend, error::HandshakeRejectReason, handshake::RpcHandshakeError, + server::NamedProtocolService, test::{ greeting_service::{ GreetingClient, @@ -114,32 +118,46 @@ pub(super) async fn setup_service( setup_service_with_builder(service_impl, builder).await } +fn spawn_inbound( + mut inbound: impl Stream + Unpin + Send + 'static, + notif_tx: mpsc::Sender>, + node_id: NodeId, +) -> task::JoinHandle<()> { + task::spawn(async move { + while let Some(stream) = inbound.next().await { + notif_tx + .send(ProtocolNotification::new( + ProtocolId::from_static(GreetingClient::PROTOCOL_NAME), + ProtocolEvent::NewInboundSubstream(node_id.clone(), stream), + )) + .await + .unwrap(); + } + }) +} + pub(super) async fn setup( service_impl: T, num_concurrent_sessions: usize, -) -> (Yamux, Yamux, task::JoinHandle<()>, Arc, Shutdown) { +) -> (Control, Yamux, task::JoinHandle<()>, Arc, Shutdown) { let (notif_tx, server_hnd, context, shutdown) = setup_service(service_impl, num_concurrent_sessions).await; let (_, inbound, outbound) = build_multiplexed_connections().await; - let substream = outbound.get_yamux_control().open_stream().await.unwrap(); + let inbound_control = inbound.get_yamux_control(); let node_identity = build_node_identity(Default::default()); + let node_id = node_identity.node_id().clone(); + spawn_inbound(inbound.into_incoming(), notif_tx.clone(), node_id); + // Notify that a peer wants to speak the greeting RPC protocol context.peer_manager().add_peer(node_identity.to_peer()).await.unwrap(); - notif_tx - .send(ProtocolNotification::new( - ProtocolId::from_static(b"/test/greeting/1.0"), - ProtocolEvent::NewInboundSubstream(node_identity.node_id().clone(), substream), - )) - .await - .unwrap(); - (inbound, outbound, server_hnd, node_identity, shutdown) + (inbound_control, outbound, server_hnd, node_identity, shutdown) } #[tokio::test] async fn request_response_errors_and_streaming() { - let (mut muxer, _outbound, server_hnd, node_identity, mut shutdown) = setup(GreetingService::default(), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, server_hnd, node_identity, mut shutdown) = setup(GreetingService::default(), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let mut client = GreetingClient::builder() @@ -221,8 +239,8 @@ async fn request_response_errors_and_streaming() { #[tokio::test] async fn concurrent_requests() { - let (mut muxer, _outbound, _, _, _shutdown) = setup(GreetingService::default(), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, _shutdown) = setup(GreetingService::default(), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let mut client = GreetingClient::builder() @@ -261,10 +279,10 @@ async fn concurrent_requests() { #[tokio::test] async fn response_too_big() { - let (mut muxer, _outbound, _, _, _shutdown) = setup(GreetingService::new(&[]), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, _shutdown) = setup(GreetingService::new(&[]), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); - let framed = framing::canonical(socket, rpc::max_response_size()); + let framed = framing::canonical(socket, rpc::max_request_size()); let mut client = GreetingClient::builder() .with_deadline(Duration::from_secs(5)) .connect(framed) @@ -273,7 +291,7 @@ async fn response_too_big() { // RPC_MAX_FRAME_SIZE bytes will always be too large because of the overhead of the RpcResponse proto message let err = client - .reply_with_msg_of_size(rpc::max_response_payload_size() as u64 + 1) + .reply_with_msg_of_size(rpc::max_response_payload_size() as u64 - 4) .await .unwrap_err(); unpack_enum!(RpcError::RequestFailed(status) = err); @@ -281,15 +299,15 @@ async fn response_too_big() { // Check that the exact frame size boundary works and that the session is still going let _string = client - .reply_with_msg_of_size(rpc::max_response_payload_size() as u64 - 9) + .reply_with_msg_of_size(rpc::max_response_payload_size() as u64 - 5) .await .unwrap(); } #[tokio::test] async fn ping_latency() { - let (mut muxer, _outbound, _, _, _shutdown) = setup(GreetingService::new(&[]), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, _shutdown) = setup(GreetingService::new(&[]), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let mut client = GreetingClient::builder().connect(framed).await.unwrap(); @@ -302,8 +320,8 @@ async fn ping_latency() { #[tokio::test] async fn server_shutdown_before_connect() { - let (mut muxer, _outbound, _, _, mut shutdown) = setup(GreetingService::new(&[]), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, mut shutdown) = setup(GreetingService::new(&[]), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); shutdown.trigger(); @@ -317,8 +335,8 @@ async fn server_shutdown_before_connect() { #[tokio::test] async fn timeout() { let delay = Arc::new(RwLock::new(Duration::from_secs(10))); - let (mut muxer, _outbound, _, _, _shutdown) = setup(SlowGreetingService::new(delay.clone()), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, _shutdown) = setup(SlowGreetingService::new(delay.clone()), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let mut client = GreetingClient::builder() .with_deadline(Duration::from_secs(1)) @@ -344,7 +362,9 @@ async fn unknown_protocol() { let (notif_tx, _, _, _shutdown) = setup_service(GreetingService::new(&[]), 1).await; let (_, inbound, mut outbound) = build_multiplexed_connections().await; - let in_substream = inbound.get_yamux_control().open_stream().await.unwrap(); + let mut in_substream = inbound.get_yamux_control().open_stream().await.unwrap(); + // To avoid having to spawn a inbound task, we can just write to the stream directly to initiate a substream + in_substream.write_all(b"hello").await.unwrap(); let node_identity = build_node_identity(Default::default()); @@ -359,7 +379,9 @@ async fn unknown_protocol() { .await .unwrap(); - let out_socket = outbound.incoming_mut().next().await.unwrap(); + let mut out_socket = outbound.incoming_mut().next().await.unwrap(); + // Read "hello" + out_socket.read_exact(&mut [0u8; 5]).await.unwrap(); let framed = framing::canonical(out_socket, 1024); let err = GreetingClient::connect(framed).await.unwrap_err(); assert!(matches!( @@ -370,8 +392,8 @@ async fn unknown_protocol() { #[tokio::test] async fn rejected_no_sessions_available() { - let (mut muxer, _outbound, _, _, _shutdown) = setup(GreetingService::new(&[]), 0).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, _shutdown) = setup(GreetingService::new(&[]), 0).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let err = GreetingClient::builder().connect(framed).await.unwrap_err(); assert!(matches!( @@ -383,8 +405,8 @@ async fn rejected_no_sessions_available() { #[tokio::test] async fn stream_still_works_after_cancel() { let service_impl = GreetingService::default(); - let (mut muxer, _outbound, _, _, _shutdown) = setup(service_impl.clone(), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, _shutdown) = setup(service_impl.clone(), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let mut client = GreetingClient::builder() @@ -423,8 +445,8 @@ async fn stream_still_works_after_cancel() { #[tokio::test] async fn stream_interruption_handling() { let service_impl = GreetingService::default(); - let (mut muxer, _outbound, _, _, _shutdown) = setup(service_impl.clone(), 1).await; - let socket = muxer.incoming_mut().next().await.unwrap(); + let (_inbound, outbound, _, _, _shutdown) = setup(service_impl.clone(), 1).await; + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let mut client = GreetingClient::builder() @@ -471,24 +493,15 @@ async fn stream_interruption_handling() { async fn max_global_sessions() { let builder = RpcServer::builder().with_maximum_simultaneous_sessions(1); let (muxer, _outbound, context, _shutdown) = setup_service_with_builder(GreetingService::default(), builder).await; - let (_, mut inbound, outbound) = build_multiplexed_connections().await; + let (_, inbound, outbound) = build_multiplexed_connections().await; let node_identity = build_node_identity(Default::default()); // Notify that a peer wants to speak the greeting RPC protocol context.peer_manager().add_peer(node_identity.to_peer()).await.unwrap(); - for _ in 0..2 { - let substream = outbound.get_yamux_control().open_stream().await.unwrap(); - muxer - .send(ProtocolNotification::new( - ProtocolId::from_static(b"/test/greeting/1.0"), - ProtocolEvent::NewInboundSubstream(node_identity.node_id().clone(), substream), - )) - .await - .unwrap(); - } + spawn_inbound(inbound.into_incoming(), muxer.clone(), node_identity.node_id().clone()); - let socket = inbound.incoming_mut().next().await.unwrap(); + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let mut client = GreetingClient::builder() .with_deadline(Duration::from_secs(5)) @@ -496,7 +509,7 @@ async fn max_global_sessions() { .await .unwrap(); - let socket = inbound.incoming_mut().next().await.unwrap(); + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let err = GreetingClient::builder() .with_deadline(Duration::from_secs(5)) @@ -514,15 +527,8 @@ async fn max_global_sessions() { } client.close().await; - let substream = outbound.get_yamux_control().open_stream().await.unwrap(); - muxer - .send(ProtocolNotification::new( - ProtocolId::from_static(b"/test/greeting/1.0"), - ProtocolEvent::NewInboundSubstream(node_identity.node_id().clone(), substream), - )) - .await - .unwrap(); - let socket = inbound.incoming_mut().next().await.unwrap(); + + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let _client = GreetingClient::builder() .with_deadline(Duration::from_secs(5)) @@ -537,23 +543,14 @@ async fn max_per_client_sessions() { .with_maximum_simultaneous_sessions(3) .with_maximum_sessions_per_client(1); let (muxer, _outbound, context, _shutdown) = setup_service_with_builder(GreetingService::default(), builder).await; - let (_, mut inbound, outbound) = build_multiplexed_connections().await; + let (_, inbound, outbound) = build_multiplexed_connections().await; let node_identity = build_node_identity(Default::default()); // Notify that a peer wants to speak the greeting RPC protocol context.peer_manager().add_peer(node_identity.to_peer()).await.unwrap(); - for _ in 0..2 { - let substream = outbound.get_yamux_control().open_stream().await.unwrap(); - muxer - .send(ProtocolNotification::new( - ProtocolId::from_static(b"/test/greeting/1.0"), - ProtocolEvent::NewInboundSubstream(node_identity.node_id().clone(), substream), - )) - .await - .unwrap(); - } + spawn_inbound(inbound.into_incoming(), muxer.clone(), node_identity.node_id().clone()); - let socket = inbound.incoming_mut().next().await.unwrap(); + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let client = GreetingClient::builder() .with_deadline(Duration::from_secs(5)) @@ -561,7 +558,7 @@ async fn max_per_client_sessions() { .await .unwrap(); - let socket = inbound.incoming_mut().next().await.unwrap(); + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let err = GreetingClient::builder() .with_deadline(Duration::from_secs(5)) @@ -579,15 +576,8 @@ async fn max_per_client_sessions() { } drop(client); - let substream = outbound.get_yamux_control().open_stream().await.unwrap(); - muxer - .send(ProtocolNotification::new( - ProtocolId::from_static(b"/test/greeting/1.0"), - ProtocolEvent::NewInboundSubstream(node_identity.node_id().clone(), substream), - )) - .await - .unwrap(); - let socket = inbound.incoming_mut().next().await.unwrap(); + + let socket = outbound.get_yamux_control().open_stream().await.unwrap(); let framed = framing::canonical(socket, 1024); let _client = GreetingClient::builder() .with_deadline(Duration::from_secs(5)) diff --git a/comms/core/src/test_utils/mocks/connectivity_manager.rs b/comms/core/src/test_utils/mocks/connectivity_manager.rs index ae81bc05db..52e88f60b9 100644 --- a/comms/core/src/test_utils/mocks/connectivity_manager.rs +++ b/comms/core/src/test_utils/mocks/connectivity_manager.rs @@ -293,6 +293,11 @@ impl ConnectivityManagerMock { .await; }, WaitStarted(reply) => reply.send(()).unwrap(), + GetNodeIdentity(_) => unimplemented!(), + GetAllowList(reply) => { + let _result = reply.send(vec![]); + }, + GetMinimizeConnectionsThreshold(_) => unimplemented!(), } } } diff --git a/comms/core/src/test_utils/mocks/peer_connection.rs b/comms/core/src/test_utils/mocks/peer_connection.rs index b288339ef8..10ca0d2469 100644 --- a/comms/core/src/test_utils/mocks/peer_connection.rs +++ b/comms/core/src/test_utils/mocks/peer_connection.rs @@ -33,6 +33,7 @@ use tokio::{ sync::{mpsc, Mutex}, }; use tokio_stream::StreamExt; +use yamux::ConnectionError; use crate::{ connection_manager::{ @@ -138,7 +139,7 @@ pub struct PeerConnectionMockState { impl PeerConnectionMockState { pub fn new(muxer: Yamux) -> Self { let control = muxer.get_yamux_control(); - let substream_counter = control.substream_counter(); + let substream_counter = muxer.substream_counter(); Self { call_count: Arc::new(AtomicUsize::new(0)), mux_control: Arc::new(Mutex::new(control)), @@ -172,7 +173,12 @@ impl PeerConnectionMockState { } pub async fn disconnect(&self) -> Result<(), PeerConnectionError> { - self.mux_control.lock().await.close().await.map_err(Into::into) + match self.mux_control.lock().await.close().await { + Ok(_) => Ok(()), + // Match the behaviour of the real PeerConnection. + Err(ConnectionError::Closed) => Ok(()), + Err(err) => Err(err.into()), + } } } @@ -215,7 +221,7 @@ impl PeerConnectionMock { reply_tx.send(Err(err)).unwrap(); }, }, - Disconnect(_, reply_tx) => { + Disconnect(_, reply_tx, _minimized) => { self.receiver.close(); reply_tx.send(self.state.disconnect().await).unwrap(); }, diff --git a/comms/core/src/test_utils/transport.rs b/comms/core/src/test_utils/transport.rs index 4dd4619c49..2d7daebab2 100644 --- a/comms/core/src/test_utils/transport.rs +++ b/comms/core/src/test_utils/transport.rs @@ -39,10 +39,7 @@ pub async fn build_connected_sockets() -> (Multiaddr, MemorySocket, MemorySocket pub async fn build_multiplexed_connections() -> (Multiaddr, Yamux, Yamux) { let (addr, socket_out, socket_in) = build_connected_sockets().await; - let muxer_out = Yamux::upgrade_connection(socket_out, ConnectionDirection::Outbound).unwrap(); - let muxer_in = Yamux::upgrade_connection(socket_in, ConnectionDirection::Inbound).unwrap(); - (addr, muxer_out, muxer_in) } diff --git a/comms/core/src/tor/control_client/client.rs b/comms/core/src/tor/control_client/client.rs index f9ce4f0e29..ce35453b5c 100644 --- a/comms/core/src/tor/control_client/client.rs +++ b/comms/core/src/tor/control_client/client.rs @@ -279,7 +279,7 @@ mod test { use tokio_stream::StreamExt; use super::*; - use crate::tor::control_client::{test_server, test_server::canned_responses, types::PrivateKey}; + use crate::tor::control_client::{test_server, test_server::canned_responses}; async fn setup_test() -> (TorControlPortClient, test_server::State) { let (_, mock_state, socket) = test_server::spawn().await; diff --git a/comms/core/src/tor/control_client/types.rs b/comms/core/src/tor/control_client/types.rs index c04dc9da6b..2f65514e09 100644 --- a/comms/core/src/tor/control_client/types.rs +++ b/comms/core/src/tor/control_client/types.rs @@ -23,7 +23,7 @@ use std::{fmt, net::SocketAddr}; use serde_derive::{Deserialize, Serialize}; -use zeroize::Zeroize; +use zeroize::{Zeroize, ZeroizeOnDrop}; #[derive(Clone, Copy, Debug)] pub enum KeyType { @@ -79,8 +79,7 @@ impl fmt::Display for KeyBlob<'_> { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Zeroize)] -#[zeroize(drop)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)] pub enum PrivateKey { /// The server should use the 1024 bit RSA key provided in as KeyBlob (v2). Rsa1024(String), diff --git a/comms/dht/examples/graphing_utilities/utilities.rs b/comms/dht/examples/graphing_utilities/utilities.rs index 1e3b63a047..bffb93b2b5 100644 --- a/comms/dht/examples/graphing_utilities/utilities.rs +++ b/comms/dht/examples/graphing_utilities/utilities.rs @@ -43,7 +43,7 @@ static GRAPH_FRAME_NUM: Lazy>> = Lazy::new(|| Mutex fn get_next_frame_num(name: &str) -> usize { let mut map = GRAPH_FRAME_NUM.lock().unwrap(); let current: usize; - match (*map).get_mut(&name.to_string()) { + match (*map).get_mut(name) { None => { current = 0; (*map).insert(name.to_string(), 1); diff --git a/comms/dht/examples/memory_net/utilities.rs b/comms/dht/examples/memory_net/utilities.rs index 3d07a3edd5..7812a53e8a 100644 --- a/comms/dht/examples/memory_net/utilities.rs +++ b/comms/dht/examples/memory_net/utilities.rs @@ -631,8 +631,13 @@ fn connection_manager_logger( println!("'{}' connected to '{}'", node_name, get_name(conn.peer_node_id()),); }, }, - PeerDisconnected(_, node_id) => { - println!("'{}' disconnected from '{}'", get_name(node_id), node_name); + PeerDisconnected(_, node_id, minimized) => { + println!( + "'{}' disconnected from '{}', {:?}", + get_name(node_id), + node_name, + minimized + ); }, PeerConnectFailed(node_id, err) => { println!( diff --git a/comms/dht/src/actor.rs b/comms/dht/src/actor.rs index 819cf7075b..6f86bf3df3 100644 --- a/comms/dht/src/actor.rs +++ b/comms/dht/src/actor.rs @@ -879,7 +879,6 @@ impl DiscoveryDialTask { mod test { use std::{convert::TryFrom, time::Duration}; - use chrono::{DateTime, Utc}; use tari_comms::test_utils::mocks::{ create_connectivity_mock, create_peer_connection_mock_pair, @@ -890,7 +889,6 @@ mod test { use super::*; use crate::{ - broadcast_strategy::BroadcastClosestRequest, envelope::NodeDestination, test_utils::{ build_peer_manager, diff --git a/comms/dht/src/config.rs b/comms/dht/src/config.rs index dc7cefc5bd..ec502499bc 100644 --- a/comms/dht/src/config.rs +++ b/comms/dht/src/config.rs @@ -50,6 +50,9 @@ pub struct DhtConfig { /// Number of random peers to include /// Default: 4 pub num_random_nodes: usize, + /// Connections above the configured number of neighbouring and random nodes will be removed + /// (default: false) + pub minimize_connections: bool, /// Send to this many peers when using the broadcast strategy /// Default: 8 pub broadcast_factor: usize, @@ -137,6 +140,7 @@ impl DhtConfig { network_discovery: NetworkDiscoveryConfig { // If a test requires the peer probe they should explicitly enable it enabled: false, + initial_peer_sync_delay: None, ..Default::default() }, peer_validator_config: PeerValidatorConfig { @@ -168,6 +172,7 @@ impl Default for DhtConfig { protocol_version: DhtProtocolVersion::latest(), num_neighbouring_nodes: 8, num_random_nodes: 4, + minimize_connections: false, propagation_factor: 4, broadcast_factor: 8, outbound_buffer_size: 20, diff --git a/comms/dht/src/connectivity/mod.rs b/comms/dht/src/connectivity/mod.rs index eb2b3dbdc0..ff222706b6 100644 --- a/comms/dht/src/connectivity/mod.rs +++ b/comms/dht/src/connectivity/mod.rs @@ -34,7 +34,10 @@ mod test; mod metrics; -use std::{sync::Arc, time::Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use log::*; pub use metrics::{MetricsCollector, MetricsCollectorHandle}; @@ -47,7 +50,8 @@ use tari_comms::{ ConnectivitySelection, }, multiaddr, - peer_manager::{NodeDistance, NodeId, PeerManagerError, PeerQuery, PeerQuerySortBy}, + peer_manager::{NodeDistance, NodeId, Peer, PeerManagerError, PeerQuery, PeerQuerySortBy}, + Minimized, NodeIdentity, PeerConnection, PeerManager, @@ -84,6 +88,8 @@ pub(crate) struct DhtConnectivity { neighbours: Vec, /// A randomly-selected set of peers, excluding neighbouring peers. random_pool: Vec, + /// The random pool history. + previous_random: Vec, /// Used to track when the random peer pool was last refreshed random_pool_last_refresh: Option, /// Holds references to peer connections that should be kept alive @@ -121,6 +127,7 @@ impl DhtConnectivity { dht_events, cooldown_in_effect: None, shutdown_signal, + previous_random: vec![], } } @@ -149,8 +156,13 @@ impl DhtConnectivity { } pub async fn run(mut self, mut connectivity_events: ConnectivityEventRx) -> Result<(), DhtConnectivityError> { - debug!(target: LOG_TARGET, "DHT connectivity starting"); - self.refresh_neighbour_pool().await?; + // Initial discovery and refresh sync peers delay period, when a configured connection needs preference, + // usually needed for the wallet to connect to its own base node first. + if let Some(delay) = self.config.network_discovery.initial_peer_sync_delay { + tokio::time::sleep(delay).await; + debug!(target: LOG_TARGET, "DHT connectivity starting after delayed for {:.0?}", delay); + } + self.refresh_neighbour_pool(true).await?; let mut ticker = time::interval(self.config.connectivity.update_interval); ticker.set_missed_tick_behavior(MissedTickBehavior::Skip); @@ -289,7 +301,7 @@ impl DhtConnectivity { match event { DhtEvent::NetworkDiscoveryPeersAdded(info) => { if info.num_new_peers > 0 { - self.refresh_peer_pools().await?; + self.refresh_peer_pools(false).await?; } }, _ => {}, @@ -323,14 +335,14 @@ impl DhtConnectivity { Ok(()) } - async fn refresh_peer_pools(&mut self) -> Result<(), DhtConnectivityError> { + async fn refresh_peer_pools(&mut self, try_revive_connections: bool) -> Result<(), DhtConnectivityError> { info!( target: LOG_TARGET, "Reinitializing neighbour pool. (size={})", self.neighbours.len(), ); - self.refresh_neighbour_pool().await?; + self.refresh_neighbour_pool(try_revive_connections).await?; self.refresh_random_pool().await?; Ok(()) @@ -338,7 +350,7 @@ impl DhtConnectivity { async fn refresh_neighbour_pool_if_required(&mut self) -> Result<(), DhtConnectivityError> { if self.num_connected_neighbours() < self.config.num_neighbouring_nodes { - self.refresh_neighbour_pool().await?; + self.refresh_neighbour_pool(false).await?; } Ok(()) @@ -351,13 +363,14 @@ impl DhtConnectivity { .count() } - fn connected_peers_iter(&self) -> impl Iterator { + fn connected_pool_peers_iter(&self) -> impl Iterator { self.connection_handles.iter().map(|c| c.peer_node_id()) } - async fn refresh_neighbour_pool(&mut self) -> Result<(), DhtConnectivityError> { + async fn refresh_neighbour_pool(&mut self, try_revive_connections: bool) -> Result<(), DhtConnectivityError> { + self.remove_allow_list_peers_from_pools().await?; let mut new_neighbours = self - .fetch_neighbouring_peers(self.config.num_neighbouring_nodes, &[]) + .fetch_neighbouring_peers(self.config.num_neighbouring_nodes, &[], try_revive_connections) .await?; if new_neighbours.is_empty() { @@ -380,14 +393,9 @@ impl DhtConnectivity { debug!( target: LOG_TARGET, - "Adding {} neighbouring peer(s), removing {} peers", - new_neighbours.len(), - difference.len() - ); - debug!( - target: LOG_TARGET, - "Adding {} peer(s) to DHT connectivity manager: {}", + "Adding {} neighbouring peer(s), removing {} peers: {}", new_neighbours.len(), + difference.len(), new_neighbours .iter() .map(ToString::to_string) @@ -396,14 +404,17 @@ impl DhtConnectivity { ); new_neighbours.iter().cloned().for_each(|peer| { - self.insert_neighbour(peer); + self.insert_neighbour_ordered_by_distance(peer); }); + self.dial_multiple_peers(&new_neighbours).await?; - if !new_neighbours.is_empty() { - self.connectivity.request_many_dials(new_neighbours).await?; - } + Ok(()) + } - self.redial_neighbours_as_required().await?; + async fn dial_multiple_peers(&self, peers_to_dial: &[NodeId]) -> Result<(), DhtConnectivityError> { + if !peers_to_dial.is_empty() { + self.connectivity.request_many_dials(peers_to_dial.to_vec()).await?; + } Ok(()) } @@ -427,7 +438,7 @@ impl DhtConnectivity { "Redialling {} disconnected peer(s)", to_redial.len() ); - self.connectivity.request_many_dials(to_redial).await?; + self.dial_multiple_peers(&to_redial).await?; } Ok(()) @@ -446,9 +457,12 @@ impl DhtConnectivity { } async fn refresh_random_pool(&mut self) -> Result<(), DhtConnectivityError> { - let mut random_peers = self - .fetch_random_peers(self.config.num_random_nodes, &self.neighbours) - .await?; + self.remove_allow_list_peers_from_pools().await?; + let mut exclude = self.neighbours.clone(); + if self.config.minimize_connections { + exclude.extend(self.previous_random.iter().cloned()); + } + let mut random_peers = self.fetch_random_peers(self.config.num_random_nodes, &exclude).await?; if random_peers.is_empty() { info!( target: LOG_TARGET, @@ -477,18 +491,21 @@ impl DhtConnectivity { random_peers, difference ); - self.random_pool.extend(random_peers.clone()); + for peer in &random_peers { + self.insert_random_peer_ordered_by_distance(peer.clone()); + } // Drop any connection handles that removed from the random pool difference.iter().for_each(|peer| { self.remove_connection_handle(peer); }); - self.connectivity.request_many_dials(random_peers).await?; + self.dial_multiple_peers(&random_peers).await?; self.random_pool_last_refresh = Some(Instant::now()); Ok(()) } async fn handle_new_peer_connected(&mut self, conn: PeerConnection) -> Result<(), DhtConnectivityError> { + self.remove_allow_list_peers_from_pools().await?; if conn.peer_features().is_client() { debug!( target: LOG_TARGET, @@ -498,10 +515,19 @@ impl DhtConnectivity { return Ok(()); } + if self.is_allow_list_peer(conn.peer_node_id()).await? { + debug!( + target: LOG_TARGET, + "Unmanaged peer '{}' connected", + conn.peer_node_id() + ); + return Ok(()); + } + if self.is_pool_peer(conn.peer_node_id()) { debug!( target: LOG_TARGET, - "Added peer {} to connection handles", + "Added pool peer '{}' to connection handles", conn.peer_node_id() ); self.insert_connection_handle(conn); @@ -518,20 +544,56 @@ impl DhtConnectivity { ); let peer_to_insert = conn.peer_node_id().clone(); - self.insert_connection_handle(conn); - if let Some(node_id) = self.insert_neighbour(peer_to_insert.clone()) { - // If we kicked a neighbour out of our neighbour pool but the random pool is not full. - // Add the neighbour to the random pool, otherwise remove the handle from the connection pool - if self.random_pool.len() < self.config.num_random_nodes { - debug!( - target: LOG_TARGET, - "Moving peer '{}' from neighbouring pool to random pool", peer_to_insert - ); - self.random_pool.push(node_id); - } else { - self.remove_connection_handle(&node_id) - } + if let Some(node_id) = self.insert_neighbour_ordered_by_distance(peer_to_insert.clone()) { + // If we kicked a neighbour out of our neighbour pool, add it to the random pool if + // it is not full or if it is closer than the furthest random peer. + debug!( + target: LOG_TARGET, + "Moving peer '{}' from neighbouring pool to random pool if not full or closer", peer_to_insert + ); + self.insert_random_peer_ordered_by_distance(node_id) } + self.insert_connection_handle(conn); + } + + Ok(()) + } + + async fn pool_peers_with_active_connections_by_distance(&self) -> Result, DhtConnectivityError> { + let query = PeerQuery::new() + .select_where(|peer| { + self.connection_handles + .iter() + .any(|conn| conn.peer_node_id() == &peer.node_id) + }) + .sort_by(PeerQuerySortBy::DistanceFrom(self.node_identity.node_id())); + let peers_by_distance = self.peer_manager.perform_query(query).await?; + debug!( + target: LOG_TARGET, + "minimize_connections: Filtered peers: {}, Handles: {}", + peers_by_distance.len(), + self.connection_handles.len(), + ); + Ok(peers_by_distance) + } + + async fn minimize_connections(&mut self) -> Result<(), DhtConnectivityError> { + // Retrieve all communication node peers with an active connection status + let mut peers_by_distance = self.pool_peers_with_active_connections_by_distance().await?; + let peer_allow_list = self.peer_allow_list().await?; + peers_by_distance.retain(|p| !peer_allow_list.contains(&p.node_id)); + + // Remove all above threshold connections + let threshold = self.config.num_neighbouring_nodes + self.config.num_random_nodes; + for peer in peers_by_distance.iter_mut().skip(threshold) { + debug!( + target: LOG_TARGET, + "minimize_connections: Disconnecting '{}' because the node is not among the {} closest peers", + peer.node_id, + threshold + ); + self.replace_pool_peer(&peer.node_id).await?; + self.remove_connection_handle(&peer.node_id); } Ok(()) @@ -557,7 +619,18 @@ impl DhtConnectivity { debug!(target: LOG_TARGET, "Connectivity event: {}", event); match event { PeerConnected(conn) => { - self.handle_new_peer_connected(*conn).await?; + self.handle_new_peer_connected(*conn.clone()).await?; + debug!( + target: LOG_TARGET, + "Peer: node_id '{}', allow_list '{}', connected '{}'", + conn.peer_node_id(), + self.is_allow_list_peer(conn.peer_node_id()).await?, + conn.is_connected(), + ); + + if self.config.minimize_connections { + self.minimize_connections().await?; + } }, PeerConnectFailed(node_id) => { self.connection_handles.retain(|c| *c.peer_node_id() != node_id); @@ -567,6 +640,7 @@ impl DhtConnectivity { "Failed to clear metrics for peer `{}`. Metric collector is shut down.", node_id ); }; + self.remove_allow_list_peers_from_pools().await?; if !self.is_pool_peer(&node_id) { debug!(target: LOG_TARGET, "{} is not managed by the DHT. Ignoring", node_id); return Ok(()); @@ -574,7 +648,13 @@ impl DhtConnectivity { self.replace_pool_peer(&node_id).await?; self.log_status(); }, - PeerDisconnected(node_id) => { + PeerDisconnected(node_id, minimized) => { + debug!( + target: LOG_TARGET, + "Peer: node_id '{}', allow_list '{}', connected 'false'", + node_id, + self.is_allow_list_peer(&node_id).await?, + ); self.connection_handles.retain(|c| *c.peer_node_id() != node_id); if self.metrics_collector.clear_metrics(node_id.clone()).await.is_err() { debug!( @@ -582,17 +662,30 @@ impl DhtConnectivity { "Failed to clear metrics for peer `{}`. Metric collector is shut down.", node_id ); }; + self.remove_allow_list_peers_from_pools().await?; if !self.is_pool_peer(&node_id) { debug!(target: LOG_TARGET, "{} is not managed by the DHT. Ignoring", node_id); return Ok(()); } + if minimized == Minimized::Yes || self.config.minimize_connections { + debug!( + target: LOG_TARGET, + "Peer '{}' was disconnected because it was minimized, will not reconnect.", + node_id + ); + // Remove from managed pool if applicable + self.replace_pool_peer(&node_id).await?; + // In case the connections was not managed, remove the connection handle + self.remove_connection_handle(&node_id); + return Ok(()); + } debug!(target: LOG_TARGET, "Pool peer {} disconnected. Redialling...", node_id); // Attempt to reestablish the lost connection to the pool peer. If reconnection fails, // it is replaced with another peer (replace_pool_peer via PeerConnectFailed) - self.connectivity.request_many_dials([node_id]).await?; + self.dial_multiple_peers(&[node_id]).await?; }, ConnectivityStateOnline(n) => { - self.refresh_peer_pools().await?; + self.refresh_peer_pools(false).await?; if self.config.auto_join && self.should_send_join() { debug!( target: LOG_TARGET, @@ -608,7 +701,8 @@ impl DhtConnectivity { }, ConnectivityStateOffline => { debug!(target: LOG_TARGET, "Node is OFFLINE"); - self.refresh_peer_pools().await?; + tokio::time::sleep(Duration::from_secs(15)).await; + self.refresh_peer_pools(true).await?; }, _ => {}, } @@ -616,15 +710,47 @@ impl DhtConnectivity { Ok(()) } + async fn peer_allow_list(&mut self) -> Result, DhtConnectivityError> { + Ok(self.connectivity.get_allow_list().await?) + } + + async fn all_connected_comms_nodes(&mut self) -> Result, DhtConnectivityError> { + let all_connections = self + .connectivity + .select_connections(ConnectivitySelection::closest_to( + self.node_identity.node_id().clone(), + usize::MAX, + vec![], + )) + .await?; + let comms_nodes = all_connections + .iter() + .filter(|p| p.peer_features().is_node()) + .map(|p| p.peer_node_id().clone()) + .collect(); + Ok(comms_nodes) + } + async fn replace_pool_peer(&mut self, current_peer: &NodeId) -> Result<(), DhtConnectivityError> { + self.remove_allow_list_peers_from_pools().await?; + if self.is_allow_list_peer(current_peer).await? { + debug!( + target: LOG_TARGET, + "Peer '{}' is on the allow list, ignoring replacement.", + current_peer + ); + return Ok(()); + } + if self.random_pool.contains(current_peer) { - let exclude = self.get_pool_peers(); - let pos = self - .random_pool - .iter() - .position(|n| n == current_peer) - .expect("unreachable panic"); - self.random_pool.swap_remove(pos); + let mut exclude = self.get_pool_peers(); + if self.config.minimize_connections { + exclude.extend(self.previous_random.iter().cloned()); + self.previous_random.push(current_peer.clone()); + } + + self.random_pool.retain(|n| n != current_peer); + self.remove_connection_handle(current_peer); debug!( target: LOG_TARGET, @@ -632,12 +758,8 @@ impl DhtConnectivity { ); match self.fetch_random_peers(1, &exclude).await?.pop() { Some(new_peer) => { - self.remove_connection_handle(current_peer); - if let Some(pos) = self.random_pool.iter().position(|n| n == current_peer) { - self.random_pool.swap_remove(pos); - } - self.random_pool.push(new_peer.clone()); - self.connectivity.request_many_dials([new_peer]).await?; + self.insert_random_peer_ordered_by_distance(new_peer.clone()); + self.dial_multiple_peers(&[new_peer]).await?; }, None => { debug!( @@ -653,25 +775,18 @@ impl DhtConnectivity { if self.neighbours.contains(current_peer) { let exclude = self.get_pool_peers(); - let pos = self - .neighbours - .iter() - .position(|n| n == current_peer) - .expect("unreachable panic"); - self.neighbours.remove(pos); + + self.neighbours.retain(|n| n != current_peer); + self.remove_connection_handle(current_peer); debug!( target: LOG_TARGET, "Peer '{}' in neighbour pool is offline. Adding a new peer if possible", current_peer ); - match self.fetch_neighbouring_peers(1, &exclude).await?.pop() { - Some(node_id) => { - self.remove_connection_handle(current_peer); - if let Some(pos) = self.neighbours.iter().position(|n| n == current_peer) { - self.neighbours.remove(pos); - } - self.insert_neighbour(node_id.clone()); - self.connectivity.request_many_dials([node_id]).await?; + match self.fetch_neighbouring_peers(1, &exclude, false).await?.pop() { + Some(new_peer) => { + self.insert_neighbour_ordered_by_distance(new_peer.clone()); + self.dial_multiple_peers(&[new_peer]).await?; }, None => { info!( @@ -685,32 +800,68 @@ impl DhtConnectivity { } } + self.log_status(); + Ok(()) } - fn insert_neighbour(&mut self, node_id: NodeId) -> Option { + fn insert_neighbour_ordered_by_distance(&mut self, node_id: NodeId) -> Option { let dist = node_id.distance(self.node_identity.node_id()); let pos = self .neighbours .iter() .position(|node_id| node_id.distance(self.node_identity.node_id()) > dist); - let removed_peer = if self.neighbours.len() + 1 > self.config.num_neighbouring_nodes { + match pos { + Some(idx) => { + self.neighbours.insert(idx, node_id); + }, + None => { + self.neighbours.push(node_id); + }, + } + + if self.neighbours.len() > self.config.num_neighbouring_nodes { self.neighbours.pop() } else { None - }; + } + } + + fn insert_random_peer_ordered_by_distance(&mut self, node_id: NodeId) { + let dist = node_id.distance(self.node_identity.node_id()); + let pos = self + .random_pool + .iter() + .position(|node_id| node_id.distance(self.node_identity.node_id()) > dist); match pos { Some(idx) => { - self.neighbours.insert(idx, node_id); + self.random_pool.insert(idx, node_id); }, None => { - self.neighbours.push(node_id); + self.random_pool.push(node_id); }, } - removed_peer + if self.random_pool.len() > self.config.num_random_nodes { + if let Some(removed_peer) = self.random_pool.pop() { + if self.config.minimize_connections { + self.previous_random.push(removed_peer.clone()); + } + } + } + } + + async fn remove_allow_list_peers_from_pools(&mut self) -> Result<(), DhtConnectivityError> { + let allow_list = self.peer_allow_list().await?; + self.neighbours.retain(|n| !allow_list.contains(n)); + self.random_pool.retain(|n| !allow_list.contains(n)); + Ok(()) + } + + async fn is_allow_list_peer(&mut self, node_id: &NodeId) -> Result { + Ok(self.peer_allow_list().await?.contains(node_id)) } fn is_pool_peer(&self, node_id: &NodeId) -> bool { @@ -737,18 +888,38 @@ impl DhtConnectivity { .expect("already checked") } + async fn max_neighbour_distance_all_conncetions(&mut self) -> Result { + let mut distance = self.get_neighbour_max_distance(); + if self.config.minimize_connections { + let all_connected_comms_nodes = self.all_connected_comms_nodes().await?; + if let Some(node_id) = all_connected_comms_nodes.get(self.config.num_neighbouring_nodes - 1) { + let node_distance = self.node_identity.node_id().distance(node_id); + if node_distance < distance { + distance = node_distance; + } + } + } + Ok(distance) + } + async fn fetch_neighbouring_peers( - &self, + &mut self, n: usize, excluded: &[NodeId], + try_revive_connections: bool, ) -> Result, DhtConnectivityError> { + let peer_allow_list = self.peer_allow_list().await?; + let neighbour_distance = self.max_neighbour_distance_all_conncetions().await?; let peer_manager = &self.peer_manager; - let node_id = self.node_identity.node_id(); - let connected = self.connected_peers_iter().collect::>(); + let self_node_id = self.node_identity.node_id(); + let connected_pool_peers = self.connected_pool_peers_iter().collect::>(); + + let mut excluded = excluded.to_vec(); + excluded.extend(peer_allow_list); // Fetch to all n nearest neighbour Communication Nodes // which are eligible for connection. - // Currently that means: + // Currently, that means: // - The peer isn't banned, // - it has the required features // - it didn't recently fail to connect, and @@ -764,20 +935,22 @@ impl DhtConnectivity { return false; } - if connected.contains(&&peer.node_id) { + if connected_pool_peers.contains(&&peer.node_id) { return false; } - if peer - .offline_since() - .map(|since| since <= offline_cooldown) - .unwrap_or(false) - { - return false; - } - // we have tried to connect to this peer, and we have never made a successful attempt at connection - if peer.last_connect_attempt().is_some() && peer.last_seen().is_none() { - return false; + if !try_revive_connections { + if peer + .offline_since() + .map(|since| since <= offline_cooldown) + .unwrap_or(false) + { + return false; + } + // we have tried to connect to this peer, and we have never made a successful attempt at connection + if peer.all_addresses_failed() { + return false; + } } let is_excluded = excluded.contains(&peer.node_id); @@ -785,9 +958,16 @@ impl DhtConnectivity { return false; } + if self.config.minimize_connections { + // If the peer is not closer, return false + if self_node_id.distance(&peer.node_id) >= neighbour_distance { + return false; + } + } + true }) - .sort_by(PeerQuerySortBy::DistanceFrom(node_id)) + .sort_by(PeerQuerySortBy::DistanceFrom(self_node_id)) .limit(n); let peers = peer_manager.perform_query(query).await?; @@ -795,8 +975,10 @@ impl DhtConnectivity { Ok(peers.into_iter().map(|p| p.node_id).take(n).collect()) } - async fn fetch_random_peers(&self, n: usize, excluded: &[NodeId]) -> Result, DhtConnectivityError> { - let peers = self.peer_manager.random_peers(n, excluded).await?; + async fn fetch_random_peers(&mut self, n: usize, excluded: &[NodeId]) -> Result, DhtConnectivityError> { + let mut excluded = excluded.to_vec(); + excluded.extend(self.peer_allow_list().await?); + let peers = self.peer_manager.random_peers(n, &excluded).await?; Ok(peers.into_iter().map(|p| p.node_id).collect()) } diff --git a/comms/dht/src/connectivity/test.rs b/comms/dht/src/connectivity/test.rs index 3120aa075b..376f05929f 100644 --- a/comms/dht/src/connectivity/test.rs +++ b/comms/dht/src/connectivity/test.rs @@ -31,6 +31,7 @@ use tari_comms::{ mocks::{create_connectivity_mock, create_dummy_peer_connection, ConnectivityManagerMockState}, node_identity::ordered_node_identities_by_distance, }, + Minimized, NodeIdentity, PeerManager, }; @@ -192,6 +193,7 @@ async fn replace_peer_when_peer_goes_offline() { connectivity.publish_event(ConnectivityEvent::PeerDisconnected( node_identities[4].node_id().clone(), + Minimized::No, )); async_assert!( @@ -242,12 +244,16 @@ async fn insert_neighbour() { // First 8 inserts should not remove a peer (because num_neighbouring_nodes == 8) for ni in shuffled.iter().take(8) { - assert!(dht_connectivity.insert_neighbour(ni.node_id().clone()).is_none()); + assert!(dht_connectivity + .insert_neighbour_ordered_by_distance(ni.node_id().clone()) + .is_none()); } // Next 2 inserts will always remove a node id for ni in shuffled.iter().skip(8) { - assert!(dht_connectivity.insert_neighbour(ni.node_id().clone()).is_some()) + assert!(dht_connectivity + .insert_neighbour_ordered_by_distance(ni.node_id().clone()) + .is_some()) } // Check the first 7 node ids match our neighbours, the last element depends on distance and ordering of inserts diff --git a/comms/dht/src/crypt.rs b/comms/dht/src/crypt.rs index 4530ef771d..2f5196e013 100644 --- a/comms/dht/src/crypt.rs +++ b/comms/dht/src/crypt.rs @@ -283,7 +283,6 @@ pub fn create_message_domain_separated_hash_parts( mod test { use prost::Message; use rand::rngs::OsRng; - use tari_comms::message::MessageExt; use tari_crypto::keys::PublicKey; use super::*; diff --git a/comms/dht/src/dedup/dedup_cache.rs b/comms/dht/src/dedup/dedup_cache.rs index c18034af09..f7ebe72180 100644 --- a/comms/dht/src/dedup/dedup_cache.rs +++ b/comms/dht/src/dedup/dedup_cache.rs @@ -33,6 +33,7 @@ use crate::{ const LOG_TARGET: &str = "comms::dht::dedup_cache"; +#[allow(dead_code)] #[derive(Queryable, PartialEq, Eq, Debug)] struct DedupCacheEntry { body_hash: String, diff --git a/comms/dht/src/dht.rs b/comms/dht/src/dht.rs index 62f2d5a0f7..3866ade9f6 100644 --- a/comms/dht/src/dht.rs +++ b/comms/dht/src/dht.rs @@ -452,7 +452,7 @@ fn discard_expired_messages(msg: &DhtInboundMessage) -> bool { #[cfg(test)] mod test { - use std::{sync::Arc, time::Duration}; + use std::time::Duration; use tari_comms::{ message::{MessageExt, MessageTag}, @@ -462,15 +462,13 @@ mod test { wrap_in_envelope_body, }; use tari_shutdown::Shutdown; - use tokio::{sync::mpsc, task, time}; - use tower::{layer::Layer, Service}; + use tokio::{task, time}; use super::*; use crate::{ crypt, envelope::DhtMessageFlags, outbound::mock::create_outbound_service_mock, - proto::envelope::DhtMessageType, test_utils::{ build_peer_manager, make_client_identity, diff --git a/comms/dht/src/discovery/mod.rs b/comms/dht/src/discovery/mod.rs index f412c66d3b..521ff24a03 100644 --- a/comms/dht/src/discovery/mod.rs +++ b/comms/dht/src/discovery/mod.rs @@ -28,9 +28,8 @@ //! //! The protocol functions as follows: //! 1. Broadcast an encrypted [Discovery](crate::envelope::DhtMessageType) message destined for the peer containing the -//! necessary details to connect to this peer. -//! 1. If the peer is online, it may decrypt the message and view the peer -//! connection details. +//! necessary details to connect to this peer. +//! 1. If the peer is online, it may decrypt the message and view the peer connection details. //! 1. The peer may then add the peer and attempt to connect to it. //! 1. Once a direct connection is established, the discovery is complete. diff --git a/comms/dht/src/discovery/requester.rs b/comms/dht/src/discovery/requester.rs index 846baab03b..3313943162 100644 --- a/comms/dht/src/discovery/requester.rs +++ b/comms/dht/src/discovery/requester.rs @@ -74,7 +74,7 @@ impl DhtDiscoveryRequester { /// /// ## Arguments /// - `dest_public_key` - The public key of the recipient used to create a shared ECDH key which in turn is used to - /// encrypt the discovery message + /// encrypt the discovery message /// - `destination` - The `NodeDestination` to use in the DhtHeader when sending a discovery message. /// - `Unknown` destination will maintain complete privacy, the trade off is that discovery needs to propagate /// the entire network to reach the destination and so may take longer diff --git a/comms/dht/src/discovery/service.rs b/comms/dht/src/discovery/service.rs index f946327af4..ae0a4a77ec 100644 --- a/comms/dht/src/discovery/service.rs +++ b/comms/dht/src/discovery/service.rs @@ -282,6 +282,7 @@ impl DhtDiscoveryService { ) -> Result { match result { Ok(peer) => Ok(peer), + Err(err @ DhtPeerValidatorError::NewAndExistingMismatch { .. }) => Err(err), Err(err @ DhtPeerValidatorError::IdentityTooManyClaims { .. }) | Err(err @ DhtPeerValidatorError::ValidatorError(_)) => { self.dht.ban_peer(public_key.clone(), OffenceSeverity::High, &err).await; diff --git a/comms/dht/src/inbound/dht_handler/task.rs b/comms/dht/src/inbound/dht_handler/task.rs index 99671f1288..691639f9f0 100644 --- a/comms/dht/src/inbound/dht_handler/task.rs +++ b/comms/dht/src/inbound/dht_handler/task.rs @@ -464,6 +464,7 @@ where S: Service Err(err) => { match &err { DhtInboundError::PeerValidatorError(err) => match err { + DhtPeerValidatorError::NewAndExistingMismatch { .. } => {}, err @ DhtPeerValidatorError::ValidatorError(_) | err @ DhtPeerValidatorError::IdentityTooManyClaims { .. } => { self.dht diff --git a/comms/dht/src/network_discovery/config.rs b/comms/dht/src/network_discovery/config.rs index a6c932ea25..bb2c00dc31 100644 --- a/comms/dht/src/network_discovery/config.rs +++ b/comms/dht/src/network_discovery/config.rs @@ -53,6 +53,10 @@ pub struct NetworkDiscoveryConfig { /// The maximum number of peers we allow per round of sync. /// Default: 500 pub max_peers_to_sync_per_round: u32, + /// Initial refresh sync peers delay period, when a configured connection needs preference. + /// Default: None + #[serde(with = "serializers::optional_seconds")] + pub initial_peer_sync_delay: Option, } impl Default for NetworkDiscoveryConfig { @@ -65,6 +69,7 @@ impl Default for NetworkDiscoveryConfig { on_failure_idle_period: Duration::from_secs(5), max_sync_peers: 5, max_peers_to_sync_per_round: 500, + initial_peer_sync_delay: None, } } } diff --git a/comms/dht/src/network_discovery/initializing.rs b/comms/dht/src/network_discovery/initializing.rs index 16eec2dbed..12726ff8f8 100644 --- a/comms/dht/src/network_discovery/initializing.rs +++ b/comms/dht/src/network_discovery/initializing.rs @@ -53,6 +53,13 @@ impl<'a> Initializing<'a> { } } + // Initial discovery and refresh sync peers delay period, when a configured connection needs preference, + // usually needed for the wallet to connect to its own base node first. + if let Some(delay) = self.context.config.network_discovery.initial_peer_sync_delay { + tokio::time::sleep(delay).await; + debug!(target: LOG_TARGET, "Discovery starting after delayed for {:.0?}", delay); + } + debug!(target: LOG_TARGET, "Node is online. Starting network discovery"); StateEvent::Initialized } diff --git a/comms/dht/src/network_discovery/on_connect.rs b/comms/dht/src/network_discovery/on_connect.rs index d0ac25cb66..49ff209afc 100644 --- a/comms/dht/src/network_discovery/on_connect.rs +++ b/comms/dht/src/network_discovery/on_connect.rs @@ -121,7 +121,7 @@ impl OnConnect { StateEvent::Shutdown } - async fn sync_peers(&self, mut conn: PeerConnection) -> Result<(), NetworkDiscoveryError> { + async fn sync_peers(&mut self, mut conn: PeerConnection) -> Result<(), NetworkDiscoveryError> { let mut client = conn.connect_rpc::().await?; let peer_stream = client .get_peers(GetPeersRequest { diff --git a/comms/dht/src/network_discovery/state_machine.rs b/comms/dht/src/network_discovery/state_machine.rs index 78281d56b9..700b62291b 100644 --- a/comms/dht/src/network_discovery/state_machine.rs +++ b/comms/dht/src/network_discovery/state_machine.rs @@ -308,7 +308,7 @@ impl Display for DiscoveryParams { let _ = write!(peers, "{p}, "); peers }), - self.num_peers_to_request + self.num_peers_to_request, ) } } diff --git a/comms/dht/src/network_discovery/test.rs b/comms/dht/src/network_discovery/test.rs index 3d8c7e067d..c4e21c6fff 100644 --- a/comms/dht/src/network_discovery/test.rs +++ b/comms/dht/src/network_discovery/test.rs @@ -107,6 +107,7 @@ mod state_machine { num_neighbouring_nodes: 4, network_discovery: NetworkDiscoveryConfig { min_desired_peers: NUM_PEERS, + initial_peer_sync_delay: None, ..Default::default() }, ..DhtConfig::default_local_test() @@ -230,6 +231,7 @@ mod discovery_ready { let config = NetworkDiscoveryConfig { min_desired_peers: 0, idle_after_num_rounds: 0, + initial_peer_sync_delay: None, ..Default::default() }; let (_, _, _, mut ready, context) = setup(config); @@ -250,6 +252,7 @@ mod discovery_ready { let config = NetworkDiscoveryConfig { min_desired_peers: 0, idle_after_num_rounds: 0, + initial_peer_sync_delay: None, ..Default::default() }; let (_, _, _, mut ready, context) = setup(config); diff --git a/comms/dht/src/outbound/broadcast.rs b/comms/dht/src/outbound/broadcast.rs index 5a06922866..b87d00a1e3 100644 --- a/comms/dht/src/outbound/broadcast.rs +++ b/comms/dht/src/outbound/broadcast.rs @@ -559,16 +559,13 @@ where S: Service mod test { use std::time::Duration; - use rand::rngs::OsRng; use tari_comms::{ multiaddr::Multiaddr, net_address::{MultiaddressesWithStats, PeerAddressSource}, - peer_manager::{NodeId, Peer, PeerFeatures, PeerFlags}, - types::CommsPublicKey, + peer_manager::{PeerFeatures, PeerFlags}, }; - use tari_crypto::keys::PublicKey; use tari_test_utils::unpack_enum; - use tokio::{sync::oneshot, task}; + use tokio::task; use super::*; use crate::{ diff --git a/comms/dht/src/outbound/message_params.rs b/comms/dht/src/outbound/message_params.rs index 269d75ddc2..2195389a48 100644 --- a/comms/dht/src/outbound/message_params.rs +++ b/comms/dht/src/outbound/message_params.rs @@ -84,7 +84,7 @@ impl Default for FinalSendMessageParams { is_discovery_enabled: false, dht_header: None, debug_info: None, - tag: None, + tag: Some(MessageTag::new()), } } } diff --git a/comms/dht/src/peer_validator.rs b/comms/dht/src/peer_validator.rs index 1ee542485b..af737cadd4 100644 --- a/comms/dht/src/peer_validator.rs +++ b/comms/dht/src/peer_validator.rs @@ -27,10 +27,11 @@ use tari_comms::{ peer_validator, peer_validator::{find_most_recent_claim, PeerValidatorError}, }; +use tari_utilities::hex::Hex; use crate::{rpc::UnvalidatedPeerInfo, DhtConfig}; -const _LOG_TARGET: &str = "dht::network_discovery::peer_validator"; +const LOG_TARGET: &str = "dht::network_discovery::peer_validator"; /// Validation errors for peers shared on the network #[derive(Debug, thiserror::Error)] @@ -39,6 +40,8 @@ pub enum DhtPeerValidatorError { ValidatorError(#[from] PeerValidatorError), #[error("Peer provided too many claims: expected max {max} but got {length}")] IdentityTooManyClaims { length: usize, max: usize }, + #[error("Optional existing peer does not match new peer: existing '{existing}', new '{new}'")] + NewAndExistingMismatch { existing: String, new: String }, } /// Validator for Peers @@ -73,6 +76,19 @@ impl<'a> PeerValidator<'a> { }); } + if let Some(existing) = &existing_peer { + if existing.public_key != new_peer.public_key { + return Err(DhtPeerValidatorError::NewAndExistingMismatch { + existing: format!("BUG: '{}' / '{}'", existing.node_id, existing.public_key), + new: format!( + "BUG: '{}' / '{}'", + NodeId::from_public_key(&new_peer.public_key), + new_peer.public_key + ), + }); + } + } + let most_recent_claim = find_most_recent_claim(&new_peer.claims).expect("new_peer.claims is not empty"); let node_id = NodeId::from_public_key(&new_peer.public_key); @@ -80,7 +96,7 @@ impl<'a> PeerValidator<'a> { let mut peer = existing_peer.unwrap_or_else(|| { Peer::new( new_peer.public_key.clone(), - node_id, + node_id.clone(), MultiaddressesWithStats::default(), PeerFlags::default(), most_recent_claim.features, @@ -98,7 +114,13 @@ impl<'a> PeerValidator<'a> { peer.update_addresses(&claim.addresses, &PeerAddressSource::FromDiscovery { peer_identity_claim: claim.clone(), }); - peer.addresses.mark_all_addresses_as_last_seen_now(&claim.addresses); + trace!( + target: LOG_TARGET, + "Peer '{}' / '{}' added with address(es) from claim: {:?}", + node_id, + new_peer.public_key.to_hex(), + claim.addresses + ); } Ok(peer) @@ -111,7 +133,6 @@ mod tests { use tari_comms::{ multiaddr::Multiaddr, - net_address::MultiaddressesWithStats, peer_manager::{IdentitySignature, PeerFeatures, PeerIdentityClaim}, types::Signature, }; diff --git a/comms/dht/src/rpc/test.rs b/comms/dht/src/rpc/test.rs index 5d245586cf..0b147fddeb 100644 --- a/comms/dht/src/rpc/test.rs +++ b/comms/dht/src/rpc/test.rs @@ -191,7 +191,7 @@ mod get_closer_peers { } mod get_peers { - use std::{borrow::BorrowMut, time::Duration}; + use std::borrow::BorrowMut; use tari_comms::test_utils::node_identity::build_many_node_identities; diff --git a/comms/dht/src/store_forward/error.rs b/comms/dht/src/store_forward/error.rs index d7f47ff225..3c57a236fa 100644 --- a/comms/dht/src/store_forward/error.rs +++ b/comms/dht/src/store_forward/error.rs @@ -24,6 +24,7 @@ use std::time::Duration; use prost::DecodeError; use tari_comms::{ + connectivity::ConnectivityError, message::MessageError, peer_manager::{NodeId, PeerManagerError}, }; @@ -89,4 +90,6 @@ pub enum StoreAndForwardError { StoredAtWasInFuture, #[error("Invariant error (POSSIBLE BUG): {0}")] InvariantError(String), + #[error("ConnectivityError: {0}")] + ConnectivityError(#[from] ConnectivityError), } diff --git a/comms/dht/src/store_forward/saf_handler/task.rs b/comms/dht/src/store_forward/saf_handler/task.rs index e630d84c41..82a01ea790 100644 --- a/comms/dht/src/store_forward/saf_handler/task.rs +++ b/comms/dht/src/store_forward/saf_handler/task.rs @@ -632,7 +632,8 @@ where S: Service Err(err @ StoreAndForwardError::RequesterChannelClosed) | Err(err @ StoreAndForwardError::DhtOutboundError(_)) | Err(err @ StoreAndForwardError::StorageError(_)) | - Err(err @ StoreAndForwardError::PeerManagerError(_)) => { + Err(err @ StoreAndForwardError::PeerManagerError(_)) | + Err(err @ StoreAndForwardError::ConnectivityError(_)) => { error!(target: LOG_TARGET, "Internal error: {}", err); None }, @@ -752,11 +753,11 @@ where S: Service mod test { use std::time::Duration; - use chrono::{Timelike, Utc}; + use chrono::Timelike; use tari_comms::{message::MessageExt, wrap_in_envelope_body}; use tari_test_utils::collect_recv; use tari_utilities::{hex, hex::Hex}; - use tokio::{sync::mpsc, task, time::sleep}; + use tokio::{task, time::sleep}; use super::*; use crate::{ diff --git a/comms/dht/src/store_forward/service.rs b/comms/dht/src/store_forward/service.rs index f3b09b3643..28d390852b 100644 --- a/comms/dht/src/store_forward/service.rs +++ b/comms/dht/src/store_forward/service.rs @@ -26,7 +26,7 @@ use chrono::{DateTime, NaiveDateTime, Utc}; use log::*; use tari_comms::{ connectivity::{ConnectivityEvent, ConnectivityEventRx, ConnectivityRequester}, - peer_manager::{NodeId, PeerFeatures}, + peer_manager::{NodeDistance, NodeId, PeerFeatures}, types::CommsPublicKey, PeerManager, }; @@ -203,6 +203,7 @@ pub struct StoreAndForwardService { database: StoreAndForwardDatabase, peer_manager: Arc, connection_events: ConnectivityEventRx, + connectivity: ConnectivityRequester, outbound_requester: OutboundMessageRequester, request_rx: mpsc::Receiver, shutdown_signal: ShutdownSignal, @@ -211,6 +212,8 @@ pub struct StoreAndForwardService { saf_response_signal_rx: mpsc::Receiver<()>, event_publisher: DhtEventSender, local_state: SafLocalState, + ignore_saf_threshold: Option, + node_id: NodeId, } impl StoreAndForwardService { @@ -234,6 +237,7 @@ impl StoreAndForwardService { dht_requester, request_rx, connection_events: connectivity.get_event_subscription(), + connectivity: connectivity.clone(), outbound_requester, shutdown_signal, num_received_saf_responses: Some(0), @@ -241,6 +245,8 @@ impl StoreAndForwardService { saf_response_signal_rx, event_publisher, local_state: Default::default(), + ignore_saf_threshold: None, + node_id: Default::default(), } } @@ -250,6 +256,21 @@ impl StoreAndForwardService { } async fn run(mut self) { + self.ignore_saf_threshold = self + .connectivity + .get_minimize_connections_threshold() + .await + .unwrap_or_else(|err| { + warn!(target: LOG_TARGET, "Failed to get the minimize connections threshold: {:?}", err); + None + }); + self.node_id = self.connectivity.get_node_identity().await.map_or_else( + |err| { + warn!(target: LOG_TARGET, "Failed to get the node identity: {:?}", err); + NodeId::default() + }, + |node_identity| node_identity.node_id().clone(), + ); let mut cleanup_ticker = time::interval(CLEANUP_INTERVAL); cleanup_ticker.set_missed_tick_behavior(MissedTickBehavior::Delay); @@ -370,9 +391,50 @@ impl StoreAndForwardService { return Ok(()); } - // Whenever we connect to a peer, request SAF messages - let features = self.peer_manager.get_peer_features(conn.peer_node_id()).await?; - if features.contains(PeerFeatures::DHT_STORE_FORWARD) { + // Whenever we connect to a peer, request SAF messages based on the peer's features + // and the current connectivity state + let request_saf = { + let features = self.peer_manager.get_peer_features(conn.peer_node_id()).await?; + if !features.contains(PeerFeatures::DHT_STORE_FORWARD) { + false + } else if let Some(threshold) = self.ignore_saf_threshold { + let active_connections = self.connectivity.get_active_connections().await?; + let mut active_connections_with_distance = active_connections + .into_iter() + .map(|c| { + let distance = self.node_id.distance(&c.peer_node_id().clone()); + (c, distance) + }) + .collect::>(); + active_connections_with_distance.sort_by(|a, b| a.1.cmp(&b.1)); + let saf_ignore_distance = active_connections_with_distance + .get(threshold - 1) + .map(|(_, distance)| distance) + .cloned() + .unwrap_or_else(|| { + active_connections_with_distance + .last() + .map(|(_, distance)| distance) + .cloned() + .unwrap_or(NodeDistance::max_distance()) + }); + + let decision = self.node_id.distance(&conn.peer_node_id().clone()) <= saf_ignore_distance; + trace!( + target: LOG_TARGET, + "Ignore SAF decision for peer: '{}', this node id: '{}', is closer: {}, will request SAF: {}, closer peers threshold: {}", + conn.peer_node_id().short_str(), + self.node_id.short_str(), + decision, + decision, + threshold + ); + decision + } else { + true + } + }; + if request_saf { info!( target: LOG_TARGET, "Connected peer '{}' is a SAF node. Requesting stored messages.", diff --git a/comms/dht/src/store_forward/store.rs b/comms/dht/src/store_forward/store.rs index 16283bede2..d41df26e6c 100644 --- a/comms/dht/src/store_forward/store.rs +++ b/comms/dht/src/store_forward/store.rs @@ -183,9 +183,9 @@ where S: Service + Se /// 1. Messages MUST have a message origin set and be encrypted (Join messages are the exception) /// 1. Unencrypted Join messages - this increases the knowledge the network has of peers (Low priority) /// 1. Encrypted Discovery messages - so that nodes are aware of other nodes that are looking for them (High - /// priority) 1. Encrypted messages addressed to the neighbourhood - some node in the neighbourhood may be - /// interested in this message (High priority) 1. Encrypted messages addressed to a particular public key or - /// node id that this node knows about + /// priority) 1. Encrypted messages addressed to the neighbourhood - some node in the neighbourhood may be + /// interested in this message (High priority) 1. Encrypted messages addressed to a particular public key or node + /// id that this node knows about async fn handle(mut self, mut message: DecryptedDhtMessage) -> Result<(), PipelineError> { if !self.node_identity.features().contains(PeerFeatures::DHT_STORE_FORWARD) { trace!( diff --git a/comms/dht/tests/harness.rs b/comms/dht/tests/harness.rs index ab1b0aa7ea..2b0414f6ac 100644 --- a/comms/dht/tests/harness.rs +++ b/comms/dht/tests/harness.rs @@ -55,6 +55,7 @@ pub struct TestNode { pub comms: CommsNode, pub dht: Dht, pub inbound_messages: mpsc::Receiver, + #[allow(dead_code)] pub messaging_events: broadcast::Sender, pub shutdown: Shutdown, } diff --git a/comms/rpc_macros/src/options.rs b/comms/rpc_macros/src/options.rs index 9a8c1fb56b..f2868d3f9d 100644 --- a/comms/rpc_macros/src/options.rs +++ b/comms/rpc_macros/src/options.rs @@ -28,6 +28,7 @@ use syn::{ Token, }; +#[allow(dead_code)] #[derive(Debug)] pub struct RpcTraitOptions { pub protocol_name: syn::LitByteStr, diff --git a/hashing/src/borsh_hasher.rs b/hashing/src/borsh_hasher.rs index 47c876fbe8..b494149e36 100644 --- a/hashing/src/borsh_hasher.rs +++ b/hashing/src/borsh_hasher.rs @@ -23,13 +23,16 @@ // 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::{io, io::Write, marker::PhantomData}; +use core::marker::PhantomData; -use borsh::BorshSerialize; +use borsh::{io, io::Write, BorshSerialize}; use digest::Digest; use tari_crypto::hashing::DomainSeparation; -/// Domain separated borsh-encoding hasher. +/// A domain-separated hasher that uses Borsh internally to ensure hashing is canonical. +/// +/// This assumes that any input type supports `BorshSerialize` canonically; that is, two different values of the same +/// type must serialize distinctly. pub struct DomainSeparatedBorshHasher { writer: WriteHashWrapper, _m: PhantomData, @@ -50,6 +53,7 @@ impl DomainSeparatedBorshHasher self.writer.0.finalize() } + /// Update the hasher using the Borsh encoding of the input, which is assumed to be canonical. pub fn update_consensus_encode(&mut self, data: &T) { BorshSerialize::serialize(data, &mut self.writer) .expect("Incorrect implementation of BorshSerialize encountered. Implementations MUST be infallible."); @@ -62,7 +66,9 @@ impl DomainSeparatedBorshHasher } /// This private struct wraps a Digest and implements the Write trait to satisfy the consensus encoding trait. -/// Do not use the DomainSeparatedHasher with this. +/// +/// It's important not to use `DomainSeparatedHasher` with this, since that can inconsistently handle length prepending +/// and render hashing inconsistent. It's fine to use it with `DomainSeparatedBorshHasher`. #[derive(Clone)] struct WriteHashWrapper(D); @@ -79,6 +85,9 @@ impl Write for WriteHashWrapper { #[cfg(test)] mod tests { + extern crate alloc; + use alloc::vec::Vec; + use blake2::Blake2b; use digest::consts::U32; use tari_crypto::hash_domain; diff --git a/hashing/src/domains.rs b/hashing/src/domains.rs index cf400cc38e..e93769d465 100644 --- a/hashing/src/domains.rs +++ b/hashing/src/domains.rs @@ -28,3 +28,11 @@ hash_domain!( // Hash domain for all transaction-related hashes, including the script signature challenge, transaction hash and kernel // signature challenge hash_domain!(TransactionHashDomain, "com.tari.base_layer.core.transactions", 0); + +hash_domain!(LedgerHashDomain, "com.tari.minotari_ledger_wallet", 0); + +hash_domain!( + KeyManagerTransactionsHashDomain, + "com.tari.base_layer.core.transactions.key_manager", + 1 +); diff --git a/hashing/src/lib.rs b/hashing/src/lib.rs index 8993831cc4..ce560d90b4 100644 --- a/hashing/src/lib.rs +++ b/hashing/src/lib.rs @@ -20,6 +20,9 @@ // 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. +// This library is no_std because it's used everywhere including ledger devices. +#![no_std] + mod domains; pub use domains::*; diff --git a/infrastructure/tari_script/src/lib.rs b/infrastructure/tari_script/src/lib.rs index a620b82b92..c3371acf9f 100644 --- a/infrastructure/tari_script/src/lib.rs +++ b/infrastructure/tari_script/src/lib.rs @@ -23,7 +23,16 @@ mod serde; mod stack; pub use error::ScriptError; -pub use op_codes::{slice_to_boxed_hash, slice_to_hash, HashValue, Message, Opcode, OpcodeVersion, ScalarValue}; +pub use op_codes::{ + slice_to_boxed_hash, + slice_to_boxed_message, + slice_to_hash, + HashValue, + Message, + Opcode, + OpcodeVersion, + ScalarValue, +}; pub use script::TariScript; pub use script_context::ScriptContext; pub use stack::{ExecutionStack, StackItem}; @@ -38,15 +47,7 @@ hash_domain!(CheckSigHashDomain, "com.tari.script.check_sig", 1); /// The type used for `CheckSig`, `CheckMultiSig`, and related opcodes' signatures pub type CheckSigSchnorrSignature = SchnorrSignature; -/// The standard payment script to be used for one-sided payment to stealth addresses -pub fn stealth_payment_script( - nonce_public_key: &RistrettoPublicKey, - script_spending_key: &RistrettoPublicKey, -) -> TariScript { - script!(PushPubKey(Box::new(nonce_public_key.clone())) Drop PushPubKey(Box::new(script_spending_key.clone()))) -} - /// The standard payment script to be used for one-sided payment to public addresses -pub fn one_sided_payment_script(destination_public_key: &RistrettoPublicKey) -> TariScript { +pub fn push_pubkey_script(destination_public_key: &RistrettoPublicKey) -> TariScript { script!(PushPubKey(Box::new(destination_public_key.clone()))) } diff --git a/infrastructure/tari_script/src/op_codes.rs b/infrastructure/tari_script/src/op_codes.rs index 5e91c529fd..8fe2f96b64 100644 --- a/infrastructure/tari_script/src/op_codes.rs +++ b/infrastructure/tari_script/src/op_codes.rs @@ -642,7 +642,7 @@ pub enum OpcodeVersion { #[cfg(test)] mod test { - use crate::{op_codes::*, Opcode, ScriptError}; + use crate::op_codes::*; #[test] fn empty_script() { diff --git a/infrastructure/tari_script/src/script.rs b/infrastructure/tari_script/src/script.rs index 091c03b973..ab07a14323 100644 --- a/infrastructure/tari_script/src/script.rs +++ b/infrastructure/tari_script/src/script.rs @@ -16,7 +16,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // pending updates to Dalek/Digest -use std::{cmp::Ordering, collections::HashSet, convert::TryFrom, fmt, io, ops::Deref}; +use std::{cmp::Ordering, collections::HashSet, fmt, io, ops::Deref}; use blake2::Blake2b; use borsh::{BorshDeserialize, BorshSerialize}; diff --git a/infrastructure/tari_script/src/stack.rs b/infrastructure/tari_script/src/stack.rs index 38185e4c89..111cfb4e50 100644 --- a/infrastructure/tari_script/src/stack.rs +++ b/infrastructure/tari_script/src/stack.rs @@ -15,7 +15,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. -use std::{convert::TryFrom, io}; +use std::io; use borsh::{BorshDeserialize, BorshSerialize}; use integer_encoding::{VarIntReader, VarIntWriter}; diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 08e3041cda..4d707453a1 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -47,7 +47,7 @@ reqwest = "0.11.11" serde_json = "1.0.64" tempfile = "3.3.0" thiserror = "^1.0.20" -time = "0.3.15" +time = "0.3.36" tokio = { version = "1.36", features = ["macros", "time", "sync", "rt-multi-thread"] } tonic = "0.8.3" diff --git a/integration_tests/log4rs/cucumber.yml b/integration_tests/log4rs/cucumber.yml index 2025a1b01c..6c99dbceed 100644 --- a/integration_tests/log4rs/cucumber.yml +++ b/integration_tests/log4rs/cucumber.yml @@ -166,7 +166,7 @@ loggers: appenders: - base_layer_contacts wallet: - level: debug + level: trace appenders: - base_layer_wallet # miner diff --git a/integration_tests/src/base_node_process.rs b/integration_tests/src/base_node_process.rs index 675bee11ff..83c14f7908 100644 --- a/integration_tests/src/base_node_process.rs +++ b/integration_tests/src/base_node_process.rs @@ -21,7 +21,6 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::{ - default::Default, fmt::{Debug, Formatter}, path::PathBuf, str::FromStr, diff --git a/integration_tests/src/chat_client.rs b/integration_tests/src/chat_client.rs index 7993310a05..7014748c1e 100644 --- a/integration_tests/src/chat_client.rs +++ b/integration_tests/src/chat_client.rs @@ -29,6 +29,7 @@ use tari_chat_client::{ Client, }; use tari_common::configuration::MultiaddrList; +use tari_common_types::tari_address::TariAddress; use tari_comms::{ multiaddr::Multiaddr, peer_manager::{Peer, PeerFeatures}, @@ -61,8 +62,10 @@ pub async fn spawn_chat_client(name: &str, seed_peers: Vec, base_dir: Path .map(|p| p.to_short_string()) .collect::>() .into(); - - let mut client = Client::new(identity, config); + let user_agent = format!("tari/integration_tests/{}", env!("CARGO_PKG_VERSION")); + let address = + TariAddress::new_single_address_with_interactive_only(identity.public_key().clone(), config.network()); + let mut client = Client::new(identity, address, config, user_agent); client.initialize().await.expect("the chat client to spawn"); client diff --git a/integration_tests/src/chat_ffi.rs b/integration_tests/src/chat_ffi.rs index 3b02918d38..da0ceb127a 100644 --- a/integration_tests/src/chat_ffi.rs +++ b/integration_tests/src/chat_ffi.rs @@ -70,22 +70,29 @@ extern "C" fn callback_read_confirmation_received(_state: *mut c_void) { extern "C" { pub fn create_chat_client( config: *mut c_void, - error_out: *const c_int, callback_contact_status_change: unsafe extern "C" fn(*mut c_void), callback_message_received: unsafe extern "C" fn(*mut c_void), callback_delivery_confirmation_received: unsafe extern "C" fn(*mut c_void), callback_read_confirmation_received: unsafe extern "C" fn(*mut c_void), + tari_address: *mut c_void, + error_out: *const c_int, ) -> *mut ClientFFI; pub fn sideload_chat_client( config: *mut c_void, contact_handle: *mut c_void, - error_out: *const c_int, callback_contact_status_change: unsafe extern "C" fn(*mut c_void), callback_message_received: unsafe extern "C" fn(*mut c_void), callback_delivery_confirmation_received: unsafe extern "C" fn(*mut c_void), callback_read_confirmation_received: unsafe extern "C" fn(*mut c_void), + tari_address: *mut c_void, + error_out: *const c_int, ) -> *mut ClientFFI; - pub fn create_chat_message(receiver: *mut c_void, message: *const c_char, error_out: *const c_int) -> *mut c_void; + pub fn create_chat_message( + receiver: *mut c_void, + sender: *mut c_void, + message: *const c_char, + error_out: *const c_int, + ) -> *mut c_void; pub fn send_chat_message(client: *mut ClientFFI, message: *mut c_void, error_out: *const c_int); pub fn add_chat_message_metadata( message: *mut c_void, @@ -102,6 +109,7 @@ extern "C" { page: c_int, error_out: *const c_int, ) -> *mut c_void; + pub fn get_chat_message(client: *mut ClientFFI, message_id: *mut c_void, error_out: *const c_int) -> *mut c_void; pub fn destroy_chat_client(client: *mut ClientFFI); pub fn chat_byte_vector_create( byte_array: *const c_uchar, @@ -140,6 +148,29 @@ impl ChatClient for ChatFFI { Ok(result) } + fn add_metadata(&self, message: Message, key: String, data: String) -> Message { + let message_ptr = Box::into_raw(Box::new(message)) as *mut c_void; + let error_out = Box::into_raw(Box::new(0)); + + let key_bytes = key.into_bytes(); + let len = i32::try_from(key_bytes.len()).expect("Truncation occurred") as c_uint; + let byte_key = unsafe { chat_byte_vector_create(key_bytes.as_ptr(), len, error_out) }; + + let data_bytes = data.into_bytes(); + let len = i32::try_from(data_bytes.len()).expect("Truncation occurred") as c_uint; + let byte_data = unsafe { chat_byte_vector_create(data_bytes.as_ptr(), len, error_out) }; + + unsafe { + add_chat_message_metadata( + message_ptr, + byte_key as *const c_char, + byte_data as *const c_char, + error_out, + ); + *Box::from_raw(message_ptr as *mut Message) + } + } + async fn check_online_status(&self, address: &TariAddress) -> Result { let client = self.ptr.lock().unwrap(); @@ -152,17 +183,20 @@ impl ChatClient for ChatFFI { Ok(ContactOnlineStatus::from_byte(u8::try_from(result).unwrap()).expect("A valid u8 from FFI status")) } - async fn send_message(&self, message: Message) -> Result<(), ClientError> { - let client = self.ptr.lock().unwrap(); + fn create_message(&self, receiver: &TariAddress, message: String) -> Message { + let receiver_address_ptr = Box::into_raw(Box::new(receiver.to_owned())) as *mut c_void; + let sender_address_ptr = Box::into_raw(Box::new(self.address.clone())) as *mut c_void; + + let message_c_str = CString::new(message).unwrap(); + let message_c_char: *const c_char = CString::into_raw(message_c_str) as *const c_char; let error_out = Box::into_raw(Box::new(0)); - let message_ptr = Box::into_raw(Box::new(message)) as *mut c_void; unsafe { - send_chat_message(client.0, message_ptr, error_out); + let message_ptr = create_chat_message(receiver_address_ptr, sender_address_ptr, message_c_char, error_out) + as *mut Message; + *Box::from_raw(message_ptr) } - - Ok(()) } async fn get_messages(&self, address: &TariAddress, limit: u64, page: u64) -> Result, ClientError> { @@ -182,41 +216,31 @@ impl ChatClient for ChatFFI { Ok(messages) } - fn create_message(&self, receiver: &TariAddress, message: String) -> Message { - let address_ptr = Box::into_raw(Box::new(receiver.to_owned())) as *mut c_void; - - let message_c_str = CString::new(message).unwrap(); - let message_c_char: *const c_char = CString::into_raw(message_c_str) as *const c_char; + async fn get_message(&self, message_id: &[u8]) -> Result { + let client = self.ptr.lock().unwrap(); let error_out = Box::into_raw(Box::new(0)); + let len = i32::try_from(message_id.len()).expect("Truncation occurred") as c_uint; + let byte_vector = unsafe { chat_byte_vector_create(message_id.as_ptr(), len, error_out) }; unsafe { - let message_ptr = create_chat_message(address_ptr, message_c_char, error_out) as *mut Message; - *Box::from_raw(message_ptr) + let message = get_chat_message(client.0, byte_vector, error_out) as *mut Message; + let message = (*message).clone(); + return Ok(message); } } - fn add_metadata(&self, message: Message, key: String, data: String) -> Message { - let message_ptr = Box::into_raw(Box::new(message)) as *mut c_void; - let error_out = Box::into_raw(Box::new(0)); - - let key_bytes = key.into_bytes(); - let len = i32::try_from(key_bytes.len()).expect("Truncation occurred") as c_uint; - let byte_key = unsafe { chat_byte_vector_create(key_bytes.as_ptr(), len, error_out) }; + async fn send_message(&self, message: Message) -> Result<(), ClientError> { + let client = self.ptr.lock().unwrap(); - let data_bytes = data.into_bytes(); - let len = i32::try_from(data_bytes.len()).expect("Truncation occurred") as c_uint; - let byte_data = unsafe { chat_byte_vector_create(data_bytes.as_ptr(), len, error_out) }; + let error_out = Box::into_raw(Box::new(0)); + let message_ptr = Box::into_raw(Box::new(message)) as *mut c_void; unsafe { - add_chat_message_metadata( - message_ptr, - byte_key as *const c_char, - byte_data as *const c_char, - error_out, - ); - *Box::from_raw(message_ptr as *mut Message) + send_chat_message(client.0, message_ptr, error_out); } + + Ok(()) } async fn send_read_receipt(&self, message: Message) -> Result<(), ClientError> { @@ -288,23 +312,26 @@ pub async fn spawn_ffi_chat_client(name: &str, seed_peers: Vec, base_dir: let client_ptr; let error_out = Box::into_raw(Box::new(0)); - + let address = + TariAddress::new_single_address_with_interactive_only(identity.public_key().clone(), Network::LocalNet); + let address_ptr = Box::into_raw(Box::new(address.clone())) as *mut c_void; unsafe { *ChatCallback::instance().contact_status_change.lock().unwrap() = 0; client_ptr = create_chat_client( config_ptr, - error_out, callback_contact_status_change, callback_message_received, callback_delivery_confirmation_received, callback_read_confirmation_received, + address_ptr, + error_out, ); } ChatFFI { ptr: Arc::new(Mutex::new(PtrWrapper(client_ptr))), - address: TariAddress::from_public_key(identity.public_key(), Network::LocalNet), + address, } } @@ -317,6 +344,7 @@ pub async fn sideload_ffi_chat_client( config.chat_client.set_base_path(base_dir); let config_ptr = Box::into_raw(Box::new(config)) as *mut c_void; + let adress_ptr = Box::into_raw(Box::new(address.clone())) as *mut c_void; let client_ptr; let error_out = Box::into_raw(Box::new(0)); @@ -326,11 +354,12 @@ pub async fn sideload_ffi_chat_client( client_ptr = sideload_chat_client( config_ptr, contacts_handle_ptr, - error_out, callback_contact_status_change, callback_message_received, callback_delivery_confirmation_received, callback_read_confirmation_received, + adress_ptr, + error_out, ); } diff --git a/integration_tests/src/ffi/callbacks.rs b/integration_tests/src/ffi/callbacks.rs index 7530259662..e29711a4b5 100644 --- a/integration_tests/src/ffi/callbacks.rs +++ b/integration_tests/src/ffi/callbacks.rs @@ -23,6 +23,7 @@ use std::sync::{Arc, Mutex, Once}; use libc::c_void; +use tari_common_types::tari_address::TariAddress; use super::{Balance, CompletedTransaction, ContactsLivenessData, PendingInboundTransaction, Wallet}; use crate::ffi::TransactionSendStatus; @@ -236,11 +237,12 @@ impl Callbacks { pub fn on_contacts_liveness_data_updated(&mut self, ptr: *mut c_void) { let contact_liveness_data = ContactsLivenessData::from_ptr(ptr); + let address = TariAddress::from_bytes(&contact_liveness_data.get_public_key().address().get_vec()).unwrap(); 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(), + address.to_base58(), contact_liveness_data.get_latency(), contact_liveness_data.get_last_seen(), contact_liveness_data.get_online_status() diff --git a/integration_tests/src/ffi/contact.rs b/integration_tests/src/ffi/contact.rs index 2485219b51..410d445662 100644 --- a/integration_tests/src/ffi/contact.rs +++ b/integration_tests/src/ffi/contact.rs @@ -52,7 +52,7 @@ impl Contact { unsafe { ptr = ffi_import::contact_create( CString::new(alias).unwrap().into_raw(), - WalletAddress::from_hex(address).get_ptr(), + WalletAddress::from_base58(address).get_ptr(), false, &mut error, ); diff --git a/integration_tests/src/ffi/ffi_import.rs b/integration_tests/src/ffi/ffi_import.rs index 01f7405477..f95eecc3e2 100644 --- a/integration_tests/src/ffi/ffi_import.rs +++ b/integration_tests/src/ffi/ffi_import.rs @@ -112,7 +112,7 @@ extern "C" { 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_from_base58(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( @@ -474,6 +474,7 @@ extern "C" { fee_per_gram: c_ulonglong, message: *const c_char, one_sided: bool, + payment_id_string: *const c_char, error_out: *mut c_int, ) -> c_ulonglong; pub fn wallet_get_fee_estimate( diff --git a/integration_tests/src/ffi/wallet.rs b/integration_tests/src/ffi/wallet.rs index 45f28c51a4..d406360296 100644 --- a/integration_tests/src/ffi/wallet.rs +++ b/integration_tests/src/ffi/wallet.rs @@ -29,6 +29,7 @@ use std::{ use callbacks::Callbacks; use indexmap::IndexMap; use libc::{c_ulonglong, c_void}; +use tari_common_types::tari_address::TariAddress; use super::{ ffi_import::{ @@ -216,10 +217,11 @@ impl 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, - ); + let address = TariAddress::from_bytes(&contact_liveness_data.get_public_key().address().get_vec()).unwrap(); + self.liveness_data + .lock() + .unwrap() + .insert(address.to_base58(), contact_liveness_data); } pub fn set_balance(&mut self, balance: Balance) { @@ -333,12 +335,13 @@ impl Wallet { unsafe { tx_id = ffi_import::wallet_send_transaction( self.ptr, - WalletAddress::from_hex(dest).get_ptr(), + WalletAddress::from_base58(dest).get_ptr(), amount, null_mut(), fee_per_gram, CString::new(message).unwrap().into_raw(), one_sided, + CString::new("").unwrap().into_raw(), &mut error, ); if error > 0 { diff --git a/integration_tests/src/ffi/wallet_address.rs b/integration_tests/src/ffi/wallet_address.rs index af865c9eea..b65c396424 100644 --- a/integration_tests/src/ffi/wallet_address.rs +++ b/integration_tests/src/ffi/wallet_address.rs @@ -24,7 +24,7 @@ use std::{ffi::CString, ptr::null_mut}; use libc::c_void; -use super::{ffi_bytes::FFIBytes, ffi_import, FFIString, PrivateKey}; +use super::{ffi_bytes::FFIBytes, ffi_import, FFIString}; pub struct WalletAddress { ptr: *mut c_void, @@ -42,24 +42,11 @@ impl WalletAddress { 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 { + pub fn from_base58(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); + ptr = ffi_import::tari_address_from_base58(CString::new(address).unwrap().into_raw(), &mut error); if error > 0 { println!("wallet_get_tari_address error {}", error); } diff --git a/integration_tests/src/merge_mining_proxy.rs b/integration_tests/src/merge_mining_proxy.rs index 26663bab8a..b74758aa11 100644 --- a/integration_tests/src/merge_mining_proxy.rs +++ b/integration_tests/src/merge_mining_proxy.rs @@ -22,14 +22,12 @@ use std::{convert::TryInto, thread}; -use minotari_app_grpc::tari_rpc::GetIdentityRequest; use minotari_app_utilities::common_cli_args::CommonCliArgs; use minotari_merge_mining_proxy::{merge_miner, Cli}; -use minotari_wallet_grpc_client::WalletGrpcClient; +use minotari_wallet_grpc_client::{grpc, WalletGrpcClient}; use serde_json::{json, Value}; use tari_common::{configuration::Network, network_check::set_network_if_choice_valid}; -use tari_common_types::{tari_address::TariAddress, types::PublicKey}; -use tari_utilities::ByteArray; +use tari_common_types::tari_address::TariAddress; use tempfile::tempdir; use tokio::runtime; use tonic::transport::Channel; @@ -45,7 +43,6 @@ pub struct MergeMiningProxyProcess { pub port: u64, pub origin_submission: bool, id: u64, - pub stealth: bool, } pub async fn register_merge_mining_proxy_process( @@ -54,7 +51,6 @@ pub async fn register_merge_mining_proxy_process( base_node_name: String, wallet_name: String, origin_submission: bool, - stealth: bool, ) { let merge_mining_proxy = MergeMiningProxyProcess { name: merge_mining_proxy_name.clone(), @@ -63,7 +59,6 @@ pub async fn register_merge_mining_proxy_process( port: get_port(18000..18499).unwrap(), origin_submission, id: 0, - stealth, }; merge_mining_proxy.start(world).await; @@ -88,17 +83,13 @@ impl MergeMiningProxyProcess { let mut wallet_client = create_wallet_client(world, self.wallet_name.clone()) .await .expect("wallet grpc client"); - let wallet_public_key = PublicKey::from_vec( - &wallet_client - .identify(GetIdentityRequest {}) - .await - .unwrap() - .into_inner() - .public_key, - ) - .unwrap(); - let wallet_payment_address = TariAddress::new(wallet_public_key, Network::LocalNet); - let stealth = self.stealth; + let wallet_public_key = &wallet_client + .get_address(grpc::Empty {}) + .await + .unwrap() + .into_inner() + .address; + let wallet_payment_address = TariAddress::from_bytes(wallet_public_key).unwrap(); thread::spawn(move || { let cli = Cli { common: CommonCliArgs { @@ -137,9 +128,8 @@ impl MergeMiningProxyProcess { ), ( "merge_mining_proxy.wallet_payment_address".to_string(), - wallet_payment_address.to_hex(), + wallet_payment_address.to_base58(), ), - ("merge_mining_proxy.stealth_payment".to_string(), stealth.to_string()), ( "merge_mining_proxy.use_dynamic_fail_data".to_string(), "false".to_string(), diff --git a/integration_tests/src/miner.rs b/integration_tests/src/miner.rs index 840107b3a2..fc4a5ed726 100644 --- a/integration_tests/src/miner.rs +++ b/integration_tests/src/miner.rs @@ -25,7 +25,6 @@ use std::{convert::TryFrom, time::Duration}; use minotari_app_grpc::tari_rpc::{ pow_algo::PowAlgos, Block, - GetIdentityRequest, NewBlockTemplate, NewBlockTemplateRequest, PowAlgo, @@ -34,19 +33,18 @@ use minotari_app_grpc::tari_rpc::{ use minotari_app_utilities::common_cli_args::CommonCliArgs; use minotari_miner::{run_miner, Cli}; use minotari_node_grpc_client::BaseNodeGrpcClient; -use minotari_wallet_grpc_client::WalletGrpcClient; +use minotari_wallet_grpc_client::{grpc, WalletGrpcClient}; use tari_common::{configuration::Network, network_check::set_network_if_choice_valid}; -use tari_common_types::{tari_address::TariAddress, types::PublicKey}; +use tari_common_types::tari_address::TariAddress; use tari_core::{ consensus::ConsensusManager, transactions::{ generate_coinbase_with_wallet_output, key_manager::{MemoryDbKeyManager, TariKeyId}, tari_amount::MicroMinotari, - transaction_components::{RangeProofType, WalletOutput}, + transaction_components::{encrypted_data::PaymentId, RangeProofType, WalletOutput}, }, }; -use tari_utilities::ByteArray; use tonic::transport::Channel; use crate::TariWorld; @@ -94,16 +92,14 @@ impl MinerProcess { let mut wallet_client = create_wallet_client(world, self.wallet_name.clone()) .await .expect("wallet grpc client"); - let wallet_public_key = PublicKey::from_vec( - &wallet_client - .identify(GetIdentityRequest {}) - .await - .unwrap() - .into_inner() - .public_key, - ) - .unwrap(); - let wallet_payment_address = TariAddress::new(wallet_public_key, Network::LocalNet); + + let wallet_public_key = &wallet_client + .get_address(grpc::Empty {}) + .await + .unwrap() + .into_inner() + .address; + let wallet_payment_address = TariAddress::from_bytes(wallet_public_key).unwrap(); let node = world.get_node(&self.base_node_name).unwrap().grpc_port; let temp_dir = world @@ -131,9 +127,8 @@ impl MinerProcess { ("miner.mine_on_tip_only".to_string(), "false".to_string()), ( "miner.wallet_payment_address".to_string(), - wallet_payment_address.to_hex(), + wallet_payment_address.to_base58(), ), - ("miner.stealth_payment".to_string(), self.stealth.to_string()), ], network: Some(Network::LocalNet), }, @@ -301,6 +296,7 @@ async fn create_block_template_with_coinbase( stealth_payment, consensus_manager.consensus_constants(height), RangeProofType::BulletProofPlus, + PaymentId::Empty, ) .await .unwrap(); diff --git a/integration_tests/src/transaction.rs b/integration_tests/src/transaction.rs index d358d9f63b..98ca4e333e 100644 --- a/integration_tests/src/transaction.rs +++ b/integration_tests/src/transaction.rs @@ -20,8 +20,6 @@ // 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_core::{ borsh::SerializedSize, covenants::Covenant, @@ -114,7 +112,7 @@ impl TestTransactionBuilder { let value = self.amount - self.estimate_fee(num_inputs, features.clone(), script.clone(), covenant.clone()) .expect("Failed to estimate fee"); - let builder = WalletOutputBuilder::new(value, self.keys.spend_key_id.clone()) + let builder = WalletOutputBuilder::new(value, self.keys.commitment_mask_key_id.clone()) .with_features(features) .with_script(script) .with_script_key(self.keys.script_key_id.clone()) diff --git a/integration_tests/src/wallet_ffi.rs b/integration_tests/src/wallet_ffi.rs index f1834ebf28..25c9ef737b 100644 --- a/integration_tests/src/wallet_ffi.rs +++ b/integration_tests/src/wallet_ffi.rs @@ -31,6 +31,7 @@ use std::{ use chrono::{DateTime, Utc}; use indexmap::IndexMap; use libc::c_void; +use tari_common_types::tari_address::TariAddress; use super::ffi::{ Balance, @@ -86,7 +87,8 @@ impl WalletFFI { pub fn identify(&self) -> String { let tari_address = self.get_address(); let key = tari_address.address(); - key.get_as_hex() + let address = TariAddress::from_bytes(&key.get_vec()).unwrap(); + address.to_base58() } pub fn get_emoji_id(&self) -> String { diff --git a/integration_tests/src/world.rs b/integration_tests/src/world.rs index 18aeadd894..854722c966 100644 --- a/integration_tests/src/world.rs +++ b/integration_tests/src/world.rs @@ -46,7 +46,6 @@ use tari_core::{ }; use tari_crypto::keys::{PublicKey as PK, SecretKey}; use tari_key_manager::key_manager_service::{KeyId, KeyManagerInterface}; -use tari_utilities::hex::Hex; use thiserror::Error; use crate::{ @@ -112,8 +111,11 @@ impl Default for TariWorld { fn default() -> Self { println!("\nWorld initialized - remove this line when called!\n"); let wallet_private_key = PrivateKey::random(&mut OsRng); - let default_payment_address = - TariAddress::new(PublicKey::from_secret_key(&wallet_private_key), Network::LocalNet); + let default_payment_address = TariAddress::new_dual_address_with_default_features( + PublicKey::from_secret_key(&wallet_private_key), + PublicKey::from_secret_key(&wallet_private_key), + Network::LocalNet, + ); Self { current_scenario_name: None, current_feature_name: None, @@ -136,7 +138,7 @@ impl Default for TariWorld { errors: Default::default(), last_imported_tx_ids: vec![], last_merge_miner_response: Default::default(), - key_manager: create_memory_db_key_manager(), + key_manager: create_memory_db_key_manager().unwrap(), wallet_private_key, default_payment_address, consensus_manager: ConsensusManager::builder(Network::LocalNet).build().unwrap(), @@ -199,24 +201,25 @@ impl TariWorld { if let Some(address) = self.wallet_addresses.get(name.as_ref()) { return Ok(address.clone()); } - match self.get_wallet_client(name).await { + let address_bytes = match self.get_wallet_client(name).await { Ok(wallet) => { let mut wallet = wallet; - Ok(wallet + wallet .get_address(minotari_wallet_grpc_client::grpc::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()) + ffi_wallet.get_address().address().get_vec() }, - } + }; + let tari_address = TariAddress::from_bytes(&address_bytes)?; + Ok(tari_address.to_base58()) } #[allow(dead_code)] diff --git a/integration_tests/tests/features/BlockTemplate.feature b/integration_tests/tests/features/BlockTemplate.feature index 12b4533c7f..a0eb409f1f 100644 --- a/integration_tests/tests/features/BlockTemplate.feature +++ b/integration_tests/tests/features/BlockTemplate.feature @@ -11,7 +11,7 @@ Scenario: Verify UTXO and kernel MMR size in header Then meddling with block template data from node SEED_A is not allowed @critical - Scenario: Verify gprc cna create block with more than 1 coinbase + Scenario: Verify gprc can create block with more than 1 coinbase Given I have a seed node SEED_A When I have 1 base nodes connected to all seed nodes Then generate a block with 2 coinbases from node SEED_A diff --git a/integration_tests/tests/features/Chat.feature b/integration_tests/tests/features/Chat.feature index 3f9bc2fc8f..1b8c45fd15 100644 --- a/integration_tests/tests/features/Chat.feature +++ b/integration_tests/tests/features/Chat.feature @@ -78,3 +78,10 @@ Feature: Chat messaging When CHAT_A will have 1 message with CHAT_C When CHAT_A will have 1 message with CHAT_D Then CHAT_A will have 3 conversationalists + + Scenario: A single message is fetched + Given I have a seed node SEED_A + When I have a chat client CHAT_A connected to seed node SEED_A + When I have a chat client CHAT_B connected to seed node SEED_A + When I use CHAT_A to send a message with id 'abcd1234' 'Hey there' to CHAT_B + Then CHAT_A can find a message locally by id 'abcd1234' diff --git a/integration_tests/tests/features/ChatFFI.feature b/integration_tests/tests/features/ChatFFI.feature index 989e8130ee..466d116338 100644 --- a/integration_tests/tests/features/ChatFFI.feature +++ b/integration_tests/tests/features/ChatFFI.feature @@ -112,4 +112,11 @@ Feature: Chat FFI messaging When I have a sideloaded chat FFI client CHAT_A from WALLET_A When I have a chat FFI client CHAT_B connected to seed node SEED_A When I use CHAT_A to send a message 'Hey there' to CHAT_B - Then CHAT_B will have 1 message with CHAT_A \ No newline at end of file + Then CHAT_B will have 1 message with CHAT_A + + Scenario: A single message is fetched from FFI + Given I have a seed node SEED_A + When I have a chat FFI client CHAT_A connected to seed node SEED_A + When I have a chat FFI client CHAT_B connected to seed node SEED_A + When I use CHAT_A to send a message with id 'abcd1234' 'Hey there' to CHAT_B + Then CHAT_A can find a message locally by id 'abcd1234' diff --git a/integration_tests/tests/features/WalletCli.feature b/integration_tests/tests/features/WalletCli.feature index a3d7d76080..ccadf0aa91 100644 --- a/integration_tests/tests/features/WalletCli.feature +++ b/integration_tests/tests/features/WalletCli.feature @@ -56,22 +56,6 @@ Feature: Wallet CLI 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 - # - @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 - 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 diff --git a/integration_tests/tests/features/WalletTransactions.feature b/integration_tests/tests/features/WalletTransactions.feature index 1e2409d8d4..1120dbd487 100644 --- a/integration_tests/tests/features/WalletTransactions.feature +++ b/integration_tests/tests/features/WalletTransactions.feature @@ -166,6 +166,7 @@ Feature: Wallet Transactions When node C is at height 11 Then I check if last imported transactions are invalid in wallet WALLET_IMPORTED + @critical Scenario: Wallet imports faucet UTXO Given I have a seed node NODE When I have 1 base nodes connected to all seed nodes diff --git a/integration_tests/tests/steps/chat_ffi_steps.rs b/integration_tests/tests/steps/chat_ffi_steps.rs index 35acd32845..ac4a3a126f 100644 --- a/integration_tests/tests/steps/chat_ffi_steps.rs +++ b/integration_tests/tests/steps/chat_ffi_steps.rs @@ -47,9 +47,8 @@ async fn chat_ffi_client_connected_to_base_node(world: &mut TariWorld, name: Str #[when(expr = "I have a sideloaded chat FFI client {word} from {word}")] async fn sideloaded_chat_ffi_client_connected_to_wallet(world: &mut TariWorld, chat_name: String, wallet_name: String) { let wallet = world.get_ffi_wallet(&wallet_name).unwrap(); - let pubkey = world.get_wallet_address(&wallet_name).await.unwrap(); - let address = TariAddress::from_hex(&pubkey).unwrap(); - + let address = world.get_wallet_address(&wallet_name).await.unwrap(); + let address = TariAddress::from_base58(&address).unwrap(); let client = sideload_ffi_chat_client(address, wallet.base_dir.clone(), wallet.contacts_handle()).await; world.chat_clients.insert(chat_name, Box::new(client)); } diff --git a/integration_tests/tests/steps/chat_steps.rs b/integration_tests/tests/steps/chat_steps.rs index 7fc202faf2..705b10f532 100644 --- a/integration_tests/tests/steps/chat_steps.rs +++ b/integration_tests/tests/steps/chat_steps.rs @@ -60,7 +60,7 @@ async fn chat_client_with_no_peers(world: &mut TariWorld, name: String) { world.chat_clients.insert(name, Box::new(client)); } -#[when(regex = r"^I use (.+) to send a message '(.+)' to (.*)$")] +#[when(regex = r"^I use (.+) to send a message '(.+)' to (.+)$")] async fn send_message_to( world: &mut TariWorld, sender: String, @@ -407,3 +407,33 @@ async fn count_conversationalists(world: &mut TariWorld, user: String, num: u64) } panic!("Only found conversations with {}/{} addresses", addresses, num) } + +#[when(regex = r"^I use (.+) to send a message with id '(.+)' '(.+)' to (.+)$")] +async fn send_message_with_id_to( + world: &mut TariWorld, + sender: String, + id: String, + message: String, + receiver: String, +) -> anyhow::Result<()> { + let sender = world.chat_clients.get(&sender).unwrap(); + let receiver = world.chat_clients.get(&receiver).unwrap(); + + let mut message = sender.create_message(&receiver.address(), message); + message.message_id = id.into_bytes(); + + sender.send_message(message).await?; + Ok(()) +} + +#[then(regex = r"^(.+) can find a message locally by id '(.+)'$")] +async fn find_message_with_id(world: &mut TariWorld, sender: String, message_id: String) -> anyhow::Result<()> { + let sender = world.chat_clients.get(&sender).unwrap(); + + sender + .get_message(message_id.as_bytes()) + .await + .unwrap_or_else(|_| panic!("Message not found with id {:?}", message_id)); + + Ok(()) +} diff --git a/integration_tests/tests/steps/merge_mining_steps.rs b/integration_tests/tests/steps/merge_mining_steps.rs index 723463d68c..8155c76ea0 100644 --- a/integration_tests/tests/steps/merge_mining_steps.rs +++ b/integration_tests/tests/steps/merge_mining_steps.rs @@ -38,7 +38,7 @@ async fn merge_mining_proxy_with_submission( "disabled" => false, _ => panic!("This should be a boolean"), }; - register_merge_mining_proxy_process(world, mining_proxy_name, base_node_name, wallet_name, enabled, true).await; + 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")] @@ -48,7 +48,7 @@ async fn merge_mining_proxy_with_default_config( base_node_name: String, wallet_name: String, ) { - register_merge_mining_proxy_process(world, mining_proxy_name, base_node_name, wallet_name, true, false).await; + 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}")] diff --git a/integration_tests/tests/steps/node_steps.rs b/integration_tests/tests/steps/node_steps.rs index f6c1049ab6..a6c8cf2a60 100644 --- a/integration_tests/tests/steps/node_steps.rs +++ b/integration_tests/tests/steps/node_steps.rs @@ -753,18 +753,22 @@ async fn generate_block_with_2_coinbases(world: &mut TariWorld, node: String) { new_template: Some(block_template), coinbases: vec![ NewBlockCoinbase { - address: TariAddress::from_hex("30a815df7b8d7f653ce3252f08a21d570b1ac44958cb4d7af0e0ef124f89b11943") - .unwrap() - .to_hex(), + address: TariAddress::from_base58( + "f4L8GRWsXqz26DM3qAGErLtVknYzmTe2fYP2yKFn4biFXYJMP61W9MeD726QJ7ytWhRGyewTZzTzjZ7tEPskDptwRub", + ) + .unwrap() + .to_base58(), value: amount - 1000, stealth_payment: false, revealed_value_proof: true, coinbase_extra: Vec::new(), }, NewBlockCoinbase { - address: TariAddress::from_hex("3e596f98f6904f0fc1c8685e2274bd8b2c445d5dac284a9398d09a0e9a760436d0") - .unwrap() - .to_hex(), + address: TariAddress::from_base58( + "f4HS8b64MDbvdaG5fiNgtsHhnoeCPaniS5M7iFuvEMDoyh9uikhWmYbnRtjdgHHVPjAXr7oSW61VSH5QvHU8jps1JXW", + ) + .unwrap() + .to_base58(), value: 1000, stealth_payment: false, revealed_value_proof: true, @@ -809,18 +813,22 @@ async fn generate_block_with_2_as_single_request_coinbases(world: &mut TariWorld max_weight: 0, coinbases: vec![ NewBlockCoinbase { - address: TariAddress::from_hex("30a815df7b8d7f653ce3252f08a21d570b1ac44958cb4d7af0e0ef124f89b11943") - .unwrap() - .to_hex(), + address: TariAddress::from_base58( + "f4L8GRWsXqz26DM3qAGErLtVknYzmTe2fYP2yKFn4biFXYJMP61W9MeD726QJ7ytWhRGyewTZzTzjZ7tEPskDptwRub", + ) + .unwrap() + .to_base58(), value: 1, stealth_payment: false, revealed_value_proof: true, coinbase_extra: Vec::new(), }, NewBlockCoinbase { - address: TariAddress::from_hex("3e596f98f6904f0fc1c8685e2274bd8b2c445d5dac284a9398d09a0e9a760436d0") - .unwrap() - .to_hex(), + address: TariAddress::from_base58( + "f4HS8b64MDbvdaG5fiNgtsHhnoeCPaniS5M7iFuvEMDoyh9uikhWmYbnRtjdgHHVPjAXr7oSW61VSH5QvHU8jps1JXW", + ) + .unwrap() + .to_base58(), value: 2, stealth_payment: false, revealed_value_proof: true, @@ -851,18 +859,19 @@ async fn generate_block_with_2_as_single_request_coinbases(world: &mut TariWorld } assert_eq!(coinbase_kernel_count, 1); assert_eq!(coinbase_utxo_count, 2); - let mut num_6154266700 = 0; - let mut num_12308533398 = 0; + let mut num_6154266699 = 0; + let mut num_12308533399 = 0; for output in body.outputs() { - if output.minimum_value_promise.as_u64() == 6154266700 { - num_6154266700 += 1; + if output.minimum_value_promise.as_u64() == 6154266699 { + num_6154266699 += 1; } - if output.minimum_value_promise.as_u64() == 12308533398 { - num_12308533398 += 1; + if output.minimum_value_promise.as_u64() == 12308533399 { + num_12308533399 += 1; } } - assert_eq!(num_6154266700, 1); - assert_eq!(num_12308533398, 1); + + assert_eq!(num_6154266699, 1); + assert_eq!(num_12308533399, 1); match client.submit_block(new_block).await { Ok(_) => (), diff --git a/integration_tests/tests/steps/wallet_cli_steps.rs b/integration_tests/tests/steps/wallet_cli_steps.rs index 83b7c889a0..4c89339db9 100644 --- a/integration_tests/tests/steps/wallet_cli_steps.rs +++ b/integration_tests/tests/steps/wallet_cli_steps.rs @@ -146,7 +146,7 @@ async fn send_from_cli(world: &mut TariWorld, amount: u64, wallet_a: String, wal .into_inner() .address .to_hex(); - let wallet_b_address = TariAddress::from_hex(wallet_b_address.as_str()).unwrap(); + let wallet_b_address = TariAddress::from_base58(wallet_b_address.as_str()).unwrap(); let mut cli = get_default_cli(); @@ -184,38 +184,6 @@ async fn create_burn_tx_via_cli(world: &mut TariWorld, amount: u64, wallet: Stri 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 = SendMinotariArgs { - amount: MicroMinotari(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 {word} rate {int} txns_per_sec duration {int} sec value {int} uT increment {int} uT \ to {word} via command line" @@ -242,7 +210,7 @@ async fn make_it_rain( .into_inner() .address .to_hex(); - let wallet_b_address = TariAddress::from_hex(wallet_b_address.as_str()).unwrap(); + let wallet_b_address = TariAddress::from_base58(wallet_b_address.as_str()).unwrap(); let mut cli = get_default_cli(); @@ -258,7 +226,6 @@ async fn make_it_rain( destination: wallet_b_address, start_time: None, one_sided: false, - stealth: false, burn_tari: false, }; @@ -328,6 +295,7 @@ async fn export_utxos(world: &mut TariWorld, wallet: String) { let args = ExportUtxosArgs { output_file: Some(path_buf.clone()), + with_private_keys: true, }; cli.command2 = Some(CliCommands::ExportUtxos(args)); diff --git a/integration_tests/tests/steps/wallet_ffi_steps.rs b/integration_tests/tests/steps/wallet_ffi_steps.rs index a9dd1a360e..e49bd1e994 100644 --- a/integration_tests/tests/steps/wallet_ffi_steps.rs +++ b/integration_tests/tests/steps/wallet_ffi_steps.rs @@ -23,6 +23,7 @@ use std::{convert::TryFrom, io::BufRead, ptr::null, time::Duration}; use cucumber::{given, then, when}; +use tari_common_types::tari_address::TariAddress; use tari_integration_tests::{ wallet_ffi::{create_contact, create_seed_words, get_mnemonic_word_list_for_language, spawn_wallet_ffi}, TariWorld, @@ -166,7 +167,8 @@ async fn check_contact(world: &mut TariWorld, alias: String, pubkey: Option().unwrap(); let encrypted_data = EncryptedData::from_hex(&output[19]).unwrap(); let minimum_value_promise = MicroMinotari(output[20].parse::().unwrap()); + let proof = if output[21].is_empty() { + None + } else { + Some(RangeProof::from_hex(&output[21]).unwrap()) + }; let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None, RangeProofType::BulletProofPlus); let metadata_signature = ComAndPubSignature::new( ephemeral_commitment, ephemeral_nonce, - signature_u_x, signature_u_a, + signature_u_x, signature_u_y, ); let utxo = UnblindedOutput::new( @@ -2285,6 +2301,7 @@ async fn import_wallet_unspent_outputs(world: &mut TariWorld, wallet_a: String, covenant, encrypted_data, minimum_value_promise, + proof, ); outputs.push(utxo); @@ -2320,6 +2337,7 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa let args = ExportUtxosArgs { output_file: Some(path_buf.clone()), + with_private_keys: true, }; cli.command2 = Some(CliCommands::ExportSpentUtxos(args)); @@ -2365,14 +2383,19 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa let script_lock_height = output[18].parse::().unwrap(); let encrypted_data = EncryptedData::from_hex(&output[19]).unwrap(); let minimum_value_promise = MicroMinotari(output[20].parse::().unwrap()); + let proof = if output[21].is_empty() { + None + } else { + Some(RangeProof::from_hex(&output[21]).unwrap()) + }; let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None, RangeProofType::BulletProofPlus); let metadata_signature = ComAndPubSignature::new( ephemeral_commitment, ephemeral_nonce, - signature_u_x, signature_u_a, + signature_u_x, signature_u_y, ); let utxo = UnblindedOutput::new( @@ -2389,6 +2412,7 @@ async fn import_wallet_spent_outputs(world: &mut TariWorld, wallet_a: String, wa covenant, encrypted_data, minimum_value_promise, + proof, ); outputs.push(utxo); @@ -2424,6 +2448,7 @@ async fn import_unspent_outputs_as_faucets(world: &mut TariWorld, wallet_a: Stri let args = ExportUtxosArgs { output_file: Some(path_buf.clone()), + with_private_keys: true, }; cli.command2 = Some(CliCommands::ExportUtxos(args)); @@ -2469,17 +2494,22 @@ async fn import_unspent_outputs_as_faucets(world: &mut TariWorld, wallet_a: Stri let script_lock_height = output[18].parse::().unwrap(); let encrypted_data = EncryptedData::from_hex(&output[19]).unwrap(); let minimum_value_promise = MicroMinotari(output[20].parse::().unwrap()); + let proof = if output[21].is_empty() { + None + } else { + Some(RangeProof::from_hex(&output[21]).unwrap()) + }; let features = OutputFeatures::new_current_version(flags, maturity, coinbase_extra, None, RangeProofType::BulletProofPlus); let metadata_signature = ComAndPubSignature::new( ephemeral_commitment, ephemeral_nonce, - signature_u_x, signature_u_a, + signature_u_x, signature_u_y, ); - let mut utxo = UnblindedOutput::new( + let utxo = UnblindedOutput::new( version, value, spending_key, @@ -2493,20 +2523,10 @@ async fn import_unspent_outputs_as_faucets(world: &mut TariWorld, wallet_a: Stri covenant, encrypted_data, minimum_value_promise, + proof, ); - 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()); + outputs.push(utxo); } let mut wallet_b_client = create_wallet_client(world, wallet_b.clone()).await.unwrap(); @@ -2608,6 +2628,7 @@ async fn multi_send_txs_from_wallet( fee_per_gram ), payment_type: 0, // mimblewimble transaction + payment_id: Vec::new(), }; let transfer_req = TransferRequest { diff --git a/package-lock.json b/package-lock.json index 538e117b71..defe53dc08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "tari", - "version": "1.0.0-pre.11a", + "version": "1.0.0-pre.16", "lockfileVersion": 2, "requires": true, "packages": {} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 4b396a926c..d186e7d294 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -13,4 +13,4 @@ # - the CI files in .github folder # - the Makefile in base_layer/key_manager/Makefile [toolchain] -channel = "nightly-2024-02-04" +channel = "nightly-2024-07-07" diff --git a/scripts/cross_compile_tooling.sh b/scripts/cross_compile_tooling.sh old mode 100644 new mode 100755 diff --git a/scripts/cross_compile_ubuntu_18-pre-build.sh b/scripts/cross_compile_ubuntu_18-pre-build.sh new file mode 100755 index 0000000000..e93f2a10fc --- /dev/null +++ b/scripts/cross_compile_ubuntu_18-pre-build.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# +# Single script for Ubuntu 18.04 package setup, mostly used for cross-compiling +# + +set -e + +USAGE="Usage: $0 target build ie: x86_64-unknown-linux-gnu or aarch64-unknown-linux-gnu" + +if [ "$#" == "0" ]; then + echo "$USAGE" + exit 1 +fi + +if [ -z "${CROSS_DEB_ARCH}" ]; then + echo "Should be run from cross, which sets the env CROSS_DEB_ARCH" + exit 1 +fi + +targetBuild="${1}" +nativeRunTime=$(uname -m) +echo "Native RunTime is ${nativeRunTime}" + +if [ "${nativeRunTime}" == "x86_64" ]; then + nativeArch=amd64 + if [ "${targetBuild}" == "aarch64-unknown-linux-gnu" ]; then + targetArch=arm64 + targetPlatform=aarch64 + else + targetArch=amd64 + targetPlatform=x86-64 + fi +elif [ "${nativeRunTime}" == "aarch64" ]; then + nativeArch=arm64 + if [ "${targetBuild}" == "x86_64-unknown-linux-gnu" ]; then + targetArch=amd64 + targetPlatform=x86-64 + fi +elif [ "${nativeRunTime}" == "riscv64" ]; then + nativeArch=riscv64 + echo "ToDo!" +else + echo "!!Unsupport platform!!" + exit 1 +fi + +crossArch=${CROSS_DEB_ARCH} +apt-get update + +# Base install packages +# scripts/install_ubuntu_dependencies.sh +apt-get install --no-install-recommends --assume-yes \ + apt-transport-https \ + ca-certificates \ + curl \ + gpg \ + bash \ + less \ + openssl \ + libssl-dev \ + pkg-config \ + libsqlite3-dev \ + libsqlite3-0 \ + libreadline-dev \ + git \ + cmake \ + dh-autoreconf \ + clang \ + g++ \ + libc++-dev \ + libc++abi-dev \ + libprotobuf-dev \ + protobuf-compiler \ + libncurses5-dev \ + libncursesw5-dev \ + libudev-dev \ + libhidapi-dev \ + zip + +echo "Installing rust ..." +mkdir -p "$HOME/.cargo/bin/" +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +export PATH="$HOME/.cargo/bin:$PATH" +. "$HOME/.cargo/env" + +# Cross-CPU compile setup +if [ "${CROSS_DEB_ARCH}" != "${nativeArch}" ]; then + echo "Setup Cross CPU Compile ..." + sed -i.save -e "s/^deb\ http/deb [arch="${nativeArch}"] http/g" /etc/apt/sources.list + + . /etc/lsb-release + ubuntu_tag=${DISTRIB_CODENAME} + + if [ "${crossArch}" == "arm64" ]; then + cat << EoF > /etc/apt/sources.list.d/${ubuntu_tag}-${crossArch}.list +deb [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag} main restricted universe multiverse +# deb-src [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag} main restricted universe multiverse + +deb [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag}-updates main restricted universe multiverse +# deb-src [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag}-updates main restricted universe multiverse + +deb [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag}-backports main restricted universe multiverse +# deb-src [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag}-backports main restricted universe multiverse + +deb [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag}-security main restricted universe multiverse +# deb-src [arch=${crossArch}] http://ports.ubuntu.com/ubuntu-ports ${ubuntu_tag}-security main restricted universe multiverse + +deb [arch=${crossArch}] http://archive.canonical.com/ubuntu ${ubuntu_tag} partner +# deb-src [arch=${crossArch}] http://archive.canonical.com/ubuntu ${ubuntu_tag} partner +EoF + fi + + if [ "${crossArch}" == "amd64" ]; then + cat << EoF > /etc/apt/sources.list.d/${ubuntu_tag}-${crossArch}.list +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag} main restricted +# deb-src http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag} main restricted + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-updates main restricted +# deb-src http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-updates main restricted + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag} universe +# deb-src http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag} universe +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-updates universe +# deb-src http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-updates universe + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag} multiverse +# deb-src http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag} multiverse +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-updates multiverse +# deb-src http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-updates multiverse + +deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-backports main restricted universe multiverse +# deb-src http://archive.ubuntu.com/ubuntu/ ${ubuntu_tag}-backports main restricted universe multiverse + +# deb http://archive.canonical.com/ubuntu ${ubuntu_tag} partner +# deb-src http://archive.canonical.com/ubuntu ${ubuntu_tag} partner + +deb [arch=amd64] http://security.ubuntu.com/ubuntu/ ${ubuntu_tag}-security main restricted +# deb-src http://security.ubuntu.com/ubuntu/ ${ubuntu_tag}-security main restricted +deb [arch=amd64] http://security.ubuntu.com/ubuntu/ ${ubuntu_tag}-security universe +# deb-src http://security.ubuntu.com/ubuntu/ ${ubuntu_tag}-security universe +deb [arch=amd64] http://security.ubuntu.com/ubuntu/ ${ubuntu_tag}-security multiverse +# deb-src http://security.ubuntu.com/ubuntu/ ${ubuntu_tag}-security multiverse +EoF + fi + + dpkg --add-architecture ${CROSS_DEB_ARCH} + apt-get update + + # scripts/install_ubuntu_dependencies-cross_compile.sh x86-64 + apt-get --assume-yes install \ + pkg-config-${targetPlatform}-linux-gnu \ + gcc-${targetPlatform}-linux-gnu \ + g++-${targetPlatform}-linux-gnu + + # packages needed for Ledger and hidapi + apt-get --assume-yes install \ + libudev-dev:${CROSS_DEB_ARCH} \ + libhidapi-dev:${CROSS_DEB_ARCH} + +fi + +rustup target add ${targetBuild} +rustup toolchain install stable-${targetBuild} --force-non-host + +rustup target list --installed +rustup toolchain list diff --git a/scripts/install_ubuntu_dependencies-rust-arm64.sh b/scripts/install_ubuntu_dependencies-rust-arm64.sh index 48f82ad1aa..33495487bb 100755 --- a/scripts/install_ubuntu_dependencies-rust-arm64.sh +++ b/scripts/install_ubuntu_dependencies-rust-arm64.sh @@ -4,4 +4,4 @@ # export PATH="$HOME/.cargo/bin:$PATH" rustup target add aarch64-unknown-linux-gnu -rustup toolchain install stable-aarch64-unknown-linux-gnu +rustup toolchain install stable-aarch64-unknown-linux-gnu --force-non-host diff --git a/scripts/install_ubuntu_dependencies-rust.sh b/scripts/install_ubuntu_dependencies-rust.sh index 7d13a7a1e9..62535ae5de 100755 --- a/scripts/install_ubuntu_dependencies-rust.sh +++ b/scripts/install_ubuntu_dependencies-rust.sh @@ -4,3 +4,4 @@ # curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y export PATH="$HOME/.cargo/bin:$PATH" +. "$HOME/.cargo/env" diff --git a/scripts/install_ubuntu_dependencies.sh b/scripts/install_ubuntu_dependencies.sh index d3113b6930..b326d2207e 100755 --- a/scripts/install_ubuntu_dependencies.sh +++ b/scripts/install_ubuntu_dependencies.sh @@ -14,6 +14,8 @@ apt-get install --no-install-recommends --assume-yes \ git \ cmake \ dh-autoreconf \ + clang \ + g++ \ libc++-dev \ libc++abi-dev \ libprotobuf-dev \ @@ -21,4 +23,5 @@ apt-get install --no-install-recommends --assume-yes \ libncurses5-dev \ libncursesw5-dev \ libudev-dev \ + libhidapi-dev \ zip diff --git a/scripts/test_in_docker.sh b/scripts/test_in_docker.sh index 8717674b52..bf49bb4498 100755 --- a/scripts/test_in_docker.sh +++ b/scripts/test_in_docker.sh @@ -2,8 +2,8 @@ # Run the Tari test suite locally inside a suitable docker container -IMAGE=quay.io/tarilabs/rust_tari-build-with-deps:nightly-2024-02-04 -TOOLCHAIN_VERSION=nightly-2024-02-04 +IMAGE=quay.io/tarilabs/rust_tari-build-with-deps:nightly-2024-07-07 +TOOLCHAIN_VERSION=nightly-2024-07-07 CONTAINER=tari_test echo "Deleting old container"