From 1d9452e2400b8fdbf58bb739391143ad215481e6 Mon Sep 17 00:00:00 2001 From: JeffreyDallas <39912573+JeffreyDallas@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:52:28 -0600 Subject: [PATCH 1/3] feat: add example for using javascript SDK (#832) Signed-off-by: Jeffrey Tang Signed-off-by: Jeromy Cannon Co-authored-by: Jeromy Cannon --- .github/workflows/flow-task-test.yaml | 15 +- .github/workflows/script/relay_smoke_test.sh | 82 - .github/workflows/zxc-e2e-test.yaml | 7 +- HelperTasks.yml | 184 +++ Taskfile.yml | 301 +--- docs/content/User/SDK.md | 92 ++ docs/data/menu/main.yml | 2 + examples/custom-network-config/Taskfile.yml | 140 +- examples/sdk-network-connection/README.md | 13 + .../sdk-network-connection/package-lock.json | 1411 +++++++++++++++++ examples/sdk-network-connection/package.json | 15 + .../solo-network-connection.js | 63 + src/commands/account.ts | 10 +- src/commands/flags.ts | 13 +- src/commands/relay.ts | 4 +- test/e2e/commands/account.test.ts | 90 +- version.ts | 1 + 17 files changed, 1969 insertions(+), 474 deletions(-) delete mode 100755 .github/workflows/script/relay_smoke_test.sh create mode 100644 HelperTasks.yml create mode 100644 docs/content/User/SDK.md create mode 100644 examples/sdk-network-connection/README.md create mode 100644 examples/sdk-network-connection/package-lock.json create mode 100644 examples/sdk-network-connection/package.json create mode 100644 examples/sdk-network-connection/solo-network-connection.js diff --git a/.github/workflows/flow-task-test.yaml b/.github/workflows/flow-task-test.yaml index 1f08b63ac..ebdb99514 100644 --- a/.github/workflows/flow-task-test.yaml +++ b/.github/workflows/flow-task-test.yaml @@ -18,6 +18,7 @@ name: "Test Taskfile Commands" # The purpose of this reusable workflow is to make sure task files are working as expected. on: + workflow_dispatch: workflow_call: pull_request: types: @@ -35,6 +36,7 @@ permissions: jobs: example-task-file-test: + timeout-minutes: 20 runs-on: solo-linux-large steps: - name: Harden Runner @@ -51,6 +53,16 @@ jobs: node-version: 20 cache: npm + - name: Setup Kind + uses: helm/kind-action@0025e74a8c7512023d06dc019c617aa3cf561fde # v1.10.0 + with: + install_only: true + node_image: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 + version: v0.21.0 + kubectl_version: v1.28.6 + verbosity: 3 + wait: 120s + - name: Install Dependencies id: npm-deps run: npm ci @@ -60,6 +72,5 @@ jobs: - name: Run Example Task File Test run: | - task readme - task default-with-mirror + task default-with-relay task clean diff --git a/.github/workflows/script/relay_smoke_test.sh b/.github/workflows/script/relay_smoke_test.sh deleted file mode 100755 index 6b4b74fdd..000000000 --- a/.github/workflows/script/relay_smoke_test.sh +++ /dev/null @@ -1,82 +0,0 @@ -#!/bin/bash -set -eo pipefail - -echo "Starting test network with a single node" - -./test/e2e/setup-e2e.sh -solo network deploy -solo node keys --gossip-keys --tls-keys -i node1 -solo node setup -i node1 -solo node start -i node1 -solo mirror-node deploy -solo relay deploy -i node1 -kubectl port-forward svc/relay-node1-hedera-json-rpc-relay -n solo-e2e 7546:7546 & -kubectl port-forward svc/haproxy-node1-svc -n solo-e2e 50211:50211 & -kubectl port-forward svc/solo-deployment-hedera-explorer -n solo-e2e 8080:80 & - -echo "Clone hedera local node" - -cd .. - -if [ -d "hedera-local-node" ]; then - echo "Directory hedera-local-node exists." -else - echo "Directory hedera-local-node does not exist." - git clone https://github.com/hashgraph/hedera-local-node --branch release-2.29.0 -fi - -cd hedera-local-node -npm install - -echo "Generate ECDSA keys, extract from output and save to key.txt" -npm run generate-accounts 3 > key.log -sed -n 's/.* - \(0x[0-9a-f]*\) - \(0x[0-9a-f]*\) - .*/\1 \2/p' key.log > key.txt - -echo "Only keep the private key, the second column of each line of file key.txt" -awk '{print "\"" $2 "\","}' key.txt > private_key_with_quote.txt -awk '{print "" $2 ","}' key.txt > private_key_without_quote.txt - -echo "Remove the comma of the last line before add to json file" -sed '$ s/.$//' private_key_with_quote.txt > private_key_with_quote_final.txt -sed '$ s/.$//' private_key_without_quote.txt > private_key_without_quote_final.txt - -LOCAL_NODE_KEYS=$(cat private_key_with_quote_final.txt) -CONTRACT_TEST_KEYS=$(cat private_key_without_quote_final.txt) - -echo "Add new keys to hardhat.config.js" -git checkout test/smoke/hardhat.config.js -awk '/accounts: \[/ {print; getline; getline; next} 1' test/smoke/hardhat.config.js > test/smoke/hardhat.config.js.tmp -awk -v new_keys="$LOCAL_NODE_KEYS" '/\],/ {print new_keys; print; next} 1' test/smoke/hardhat.config.js.tmp > test/smoke/hardhat.config.js || true -cat test/smoke/hardhat.config.js - -#echo "Run smoke test" -#cd test/smoke -#npm install -#npx hardhat test - -cd .. - -if [ -d "hedera-smart-contracts" ]; then - echo "Directory hedera-smart-contracts exists." -else - echo "Directory hedera-smart-contracts does not exist." - git clone https://github.com/hashgraph/hedera-smart-contracts --branch only-erc20-tests -fi -cd hedera-smart-contracts - -npm install -npx hardhat compile - -echo "Build .env file" - -echo "PRIVATE_KEYS=\"$CONTRACT_TEST_KEYS\"" > .env -echo "RETRY_DELAY=5000 # ms" >> .env -echo "MAX_RETRY=5" >> .env -cat .env - -echo "Start background transaction" -cd ../hedera-local-node; watch npm run generate-accounts 3 > background.log & cd - - -npm list -echo "Run contract test" -npm run hh:test diff --git a/.github/workflows/zxc-e2e-test.yaml b/.github/workflows/zxc-e2e-test.yaml index d309d55c6..62682e730 100644 --- a/.github/workflows/zxc-e2e-test.yaml +++ b/.github/workflows/zxc-e2e-test.yaml @@ -21,6 +21,7 @@ name: "ZXC: E2E Test" # - .github/workflows/flow-build-application.yaml on: + workflow_dispatch: workflow_call: inputs: node-version: @@ -181,12 +182,6 @@ jobs: run: | ${{ env.CG_EXEC }} npm run ${{ inputs.npm-test-script }} - - name: RPC relay smoke test - if: ${{ runner.os == 'linux' && inputs.npm-test-script == 'test-e2e-relay' && !cancelled() && !failure() }} - run: | - echo "Skipped smoke test for relay" - #.github/workflows/script/relay_smoke_test.sh - - name: Upload E2E Logs to GitHub if: ${{ !cancelled() }} uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 diff --git a/HelperTasks.yml b/HelperTasks.yml new file mode 100644 index 000000000..45f488197 --- /dev/null +++ b/HelperTasks.yml @@ -0,0 +1,184 @@ +version: 3 +output: prefixed +silent: false +vars: + nodes: + ref: until (env "SOLO_NETWORK_SIZE" | default .SOLO_NETWORK_SIZE | int) + # node name should be node1, node2, node3, etc. + node_list_internal: "{{range $idx, $n := .nodes }}node{{add $n 1}},{{end}}" + node_identifiers: "{{ .node_list_internal | trimSuffix \",\" }}" + + solo_user_dir: "{{ env \"HOME\" }}/.solo" + solo_cache_dir: "{{ .solo_user_dir }}/cache" + solo_logs_dir: "{{ .solo_user_dir }}/logs" + solo_keys_dir: "{{ .solo_cache_dir }}/keys" + solo_bin_dir: "{{ .solo_user_dir }}/bin" + +tasks: + readme: + silent: true + cmds: + - echo "This is a custom network configuration for the Hedera Hashgraph Solo network." + - echo "The network is configured to have {{ .SOLO_NETWORK_SIZE }} nodes." + - echo "The network is deployed in the namespace {{ .SOLO_NAMESPACE }}." + - echo "The cluster is deployed in the namespace {{ .SOLO_CLUSTER_SETUP_NAMESPACE }}." + - echo "Use command 'task default' to deploy the network." + - echo "Use command 'task destroy' to destroy the network." + - echo "Use command 'task clean' to destroy and clean up the network." + - echo "Use command 'task show:ips' to show the external IPs of the nodes." + - echo "Use command 'task default-with-mirror' to deploy the network with a mirror node." + - echo "Use command 'task default-with-relay' to deploy the network with a relay node." + + install:solo: + internal: true + status: + - command -v solo + cmds: + - npm install -g @hashgraph/solo + - cd ../.. + - npm link + + install:kubectl:darwin: + internal: true + platforms: + - darwin + status: + - command -v kubectl + cmds: + - brew update + - brew install kubernetes-cli + + install:kubectl:linux: + internal: true + platforms: + - linux + status: + - command -v kubectl + cmds: + - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/{{ ARCH }}/kubectl" + - sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl + - rm -rf kubectl + + solo:init: + internal: true + status: + - test -f {{ .solo_bin_dir }}/helm + - test -f {{ .solo_cache_dir }}/profiles/custom-spec.yaml + - test -f {{ .solo_cache_dir }}/templates/api-permission.properties + - test -f {{ .solo_cache_dir }}/templates/application.properties + - test -f {{ .solo_cache_dir }}/templates/bootstrap.properties + - test -f {{ .solo_cache_dir }}/templates/settings.txt + - test -f {{ .solo_cache_dir }}/templates/log4j2.xml + #- test "$(yq -r '.flags."node-ids"' < {{ .solo_user_dir }}/solo.yaml)" == "{{ .node_identifiers }}" + - test "$(jq -r '.flags."node-ids"' < {{ .solo_user_dir }}/solo.config)" == "{{ .node_identifiers }}" + cmds: + - npm run build + - solo init + + solo:keys: + internal: true + status: + - | + for n in $(seq 0 {{ sub (env "SOLO_NETWORK_SIZE" | default .SOLO_NETWORK_SIZE | int) 1 }}); do + test -f {{ .solo_keys_dir }}/hedera-node${n}.crt + test -f {{ .solo_keys_dir }}/hedera-node${n}.key + test -f {{ .solo_keys_dir }}/s-public-node${n}.pem + test -f {{ .solo_keys_dir }}/s-private-node${n}.pem + done + cmds: + - npm run build + - solo node keys --gossip-keys --tls-keys --node-aliases-unparsed {{.node_identifiers}} + + solo:network:deploy: + internal: true + cmds: + - npm run build + - solo network deploy --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} --release-tag "${CONSENSUS_NODE_VERSION}" --solo-chart-version "${SOLO_CHART_VERSION}" + - solo node setup --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} --release-tag "${CONSENSUS_NODE_VERSION}" + + solo:network:destroy: + internal: true + cmds: + - npm run build + - solo network destroy --namespace "${SOLO_NAMESPACE}" --delete-pvcs --delete-secrets --force + + solo:node:start: + internal: true + cmds: + - npm run build + - solo node start --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} {{ .CLI_ARGS }} + - kubectl port-forward -n "${SOLO_NAMESPACE}" svc/haproxy-node1-svc 50211:50211 & + - task: "sleep_after_port_forward" + + solo:node:stop: + internal: true + ignore_error: true + cmds: + - npm run build + - solo node stop --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} {{ .CLI_ARGS }} + + solo:node:addresses: + internal: true + cmds: + - kubectl get svc -n "${SOLO_NAMESPACE}" -l "solo.hedera.com/type=network-node-svc" + + solo:relay: + cmds: + - npm run build + - solo relay deploy -n "${SOLO_NAMESPACE}" -i node1 + - echo "Enable port forwarding for Hedera JSON RPC Relay" + - kubectl port-forward -n "${SOLO_NAMESPACE}" svc/relay-node1-hedera-json-rpc-relay 7546:7546 & + - task: "sleep_after_port_forward" + + solo:destroy-relay: + status: + - helm list -n "${SOLO_NAMESPACE}" | grep -vqz relay-node1 + cmds: + - npm run build + - solo relay destroy -n "${SOLO_NAMESPACE}" -i node1 + + solo:cache:remove: + internal: true + status: + - test [[ ! -d {{ .solo_cache_dir }} ]] + cmds: + - rm -rf {{ .solo_cache_dir }} + + solo:logs:remove: + internal: true + status: + - test [[ ! -d {{ .solo_logs_dir }} ]] + cmds: + - rm -rf {{ .solo_logs_dir }} + + solo:config:remove: + internal: true + status: + - test [[ ! -f {{ .solo_user_dir }}/solo.yaml ]] + cmds: + - rm -rf {{ .solo_user_dir }}/solo.yaml + + cluster:create: + status: + - kind get clusters | grep -q "${SOLO_CLUSTER_NAME}" + cmds: + - kind create cluster -n "${SOLO_CLUSTER_NAME}" --image "${KIND_IMAGE}" + + cluster:setup: + cmds: + - npm run build + - solo cluster setup --cluster-setup-namespace "${SOLO_CLUSTER_SETUP_NAMESPACE}" + + cluster:destroy: + cmds: + - kind delete cluster --name "${SOLO_CLUSTER_NAME}" + + clean:port-forward: + cmds: + - pkill -f "kubectl port-forward -n {{ .SOLO_NAMESPACE }}" || true + + sleep_after_port_forward: + cmds: + # somehow without the sleep, when port-forward is the last command of a series of tasks, port-forward + # prematurely killed when task is exiting + - sleep 4 diff --git a/Taskfile.yml b/Taskfile.yml index cb47a5fca..b26afd878 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,13 +1,12 @@ version: 3 -output: prefixed +includes: + helper: ./HelperTasks.yml dotenv: - .env -silent: false - env: SOLO_CHART_VERSION: 0.34.0 CONSENSUS_NODE_VERSION: v0.56.0 - SOLO_NAMESPACE: solo-{{ env "USER" | replace "." "-" | trunc 63 | default "test" }} + SOLO_NAMESPACE: solo-e2e SOLO_CLUSTER_SETUP_NAMESPACE: solo-setup SOLO_CLUSTER_RELEASE_NAME: solo-cluster-setup SOLO_NETWORK_SIZE: 2 @@ -15,295 +14,89 @@ env: KIND_IMAGE: kindest/node:v1.27.3@sha256:3966ac761ae0136263ffdb6cfd4db23ef8a83cba8a463690e98317add2c9ba72 MIRROR_RELEASE_NAME: mirror -vars: - nodes: - ref: until (env "SOLO_NETWORK_SIZE" | default .SOLO_NETWORK_SIZE | int) - node_list_internal: "{{range $idx, $n := .nodes }}node{{$n}},{{end}}" - node_identifiers: "{{ .node_list_internal | trimSuffix \",\" }}" - - solo_user_dir: "{{ env \"HOME\" }}/.solo" - solo_cache_dir: "{{ .solo_user_dir }}/cache" - solo_logs_dir: "{{ .solo_user_dir }}/logs" - solo_keys_dir: "{{ .solo_cache_dir }}/keys" - solo_bin_dir: "{{ .solo_user_dir }}/bin" tasks: - readme: - silent: true - cmds: - - echo "This is a custom network configuration for the Hedera Hashgraph Solo network." - - echo "The network is configured to have {{ .SOLO_NETWORK_SIZE }} nodes." - - echo "The network is deployed in the namespace {{ .SOLO_NAMESPACE }}." - - echo "The cluster is deployed in the namespace {{ .SOLO_CLUSTER_SETUP_NAMESPACE }}." - - echo "Use command 'task default' to deploy the network." - - echo "Use command 'task destroy' to destroy the network." - - echo "Use command 'task clean' to destroy and clean up the network." - - echo "Use command 'task show:ips' to show the external IPs of the nodes." - - echo "Use command 'task default-with-mirror' to deploy the network with a mirror node." - default: + desc: install Solo, deploy the network, set it up, and start it cmds: - - task: "install:node:darwin" - - task: "install:node:linux" - - task: "install:kubectl:darwin" - - task: "install:kubectl:linux" - - task: "install:helm:darwin" - - task: "install:helm:linux" - - task: "install:kind:darwin" - - task: "install:kind:linux:x86_64" - - task: "install:kind:linux:aarch64" - - task: "install:solo" + - task: "helper:install:solo" - task: "install" - task: "start" default-with-mirror: + desc: in addition to the defaults, also deploy the mirror node cmds: - task: "default" - - task: "mirror:deploy" - - install: - cmds: - - task: "cluster:create" - - task: "solo:init" - - task: "cluster:setup" - - task: "solo:keys" - - task: "solo:network:deploy" - - cluster:create: - status: - - kind get clusters | grep -q "${SOLO_CLUSTER_NAME}" - cmds: - - kind create cluster -n "${SOLO_CLUSTER_NAME}" --image "${KIND_IMAGE}" + - task: "solo:mirror-node" - cluster:setup: + default-with-relay: + desc: in addition to default-with-mirror, deploy the JSON RPC relay cmds: - - npm run solo-test -- cluster setup --cluster-setup-namespace "${SOLO_CLUSTER_SETUP_NAMESPACE}" + - task: "default" + - task: "solo:mirror-node" + - task: "helper:solo:relay" - cluster:destroy: + install: + desc: create the cluster, solo init, solo cluster create, solo node keys, solo network deploy cmds: - - kind delete cluster --name "${SOLO_CLUSTER_NAME}" + - task: "helper:cluster:create" + - task: "helper:solo:init" + - task: "helper:cluster:setup" + - task: "helper:solo:keys" + - task: "helper:solo:network:deploy" start: + desc: solo node start cmds: - - task: "solo:node:start" + - task: "helper:solo:node:start" stop: + desc: solo node stop cmds: - - task: "solo:node:stop" + - task: "helper:solo:node:stop" - mirror:deploy: + solo:mirror-node: + desc: solo mirror-node deploy with port forward on explorer cmds: - - npm run solo-test -- mirror-node deploy --namespace "${SOLO_NAMESPACE}" - - echo "Enable port forwarding for Hedera Explorer" + - npm run build + - solo mirror-node deploy --namespace "${SOLO_NAMESPACE}" + - echo "Enable port forwarding for Hedera Explorer & Mirror Node Network" - kubectl port-forward -n "${SOLO_NAMESPACE}" svc/hedera-explorer 8080:80 & + - kubectl port-forward svc/mirror-grpc -n "${SOLO_NAMESPACE}" 5600:5600 & + - task: "helper:sleep_after_port_forward" - mirror:destroy: + solo:destroy-mirror-node: + desc: solo mirror-node destroy status: - helm list -n "${SOLO_NAMESPACE}" | grep -vqz "${MIRROR_RELEASE_NAME}" cmds: - - npm run solo-test -- mirror-node destroy --namespace "${SOLO_NAMESPACE}" --force || true - - show:ips: - cmds: - - task: "solo:node:addresses" + - npm run build + - solo mirror-node destroy --namespace "${SOLO_NAMESPACE}" --force || true destroy: + desc: destroy relay, mirror-node, and network cmds: - - task: "solo:node:stop" - - task: "solo:network:destroy" - - task: "mirror:destroy" - - task: "cluster:destroy" + - task: "helper:solo:node:stop" + - task: "helper:solo:network:destroy" + - task: "solo:destroy-mirror-node" + - task: "helper:solo:destroy-relay" + - task: "helper:cluster:destroy" clean: + desc: destroy, then remove cache directory, logs directory, config, and port forwards cmds: - task: "destroy" - task: "clean:cache" - task: "clean:logs" - - task: "solo:config:remove" + - task: "helper:solo:config:remove" + - task: "helper:clean:port-forward" clean:cache: + desc: remove solo cache directory cmds: - - task: "solo:cache:remove" + - task: "helper:solo:cache:remove" clean:logs: + desc: remove solo logs director cmds: - - task: "solo:logs:remove" - - solo:init: - internal: true - status: - - test -f {{ .solo_bin_dir }}/helm - - test -f {{ .solo_cache_dir }}/profiles/custom-spec.yaml - - test -f {{ .solo_cache_dir }}/templates/api-permission.properties - - test -f {{ .solo_cache_dir }}/templates/application.properties - - test -f {{ .solo_cache_dir }}/templates/bootstrap.properties - - test -f {{ .solo_cache_dir }}/templates/settings.txt - - test -f {{ .solo_cache_dir }}/templates/log4j2.xml - #- test "$(yq -r '.flags."node-ids"' < {{ .solo_user_dir }}/solo.yaml)" == "{{ .node_identifiers }}" - - test "$(jq -r '.flags."node-ids"' < {{ .solo_user_dir }}/solo.config)" == "{{ .node_identifiers }}" - cmds: - - npm run solo-test -- init - - solo:keys: - internal: true - status: - - | - for n in $(seq 0 {{ sub (env "SOLO_NETWORK_SIZE" | default .SOLO_NETWORK_SIZE | int) 1 }}); do - test -f {{ .solo_keys_dir }}/hedera-node${n}.crt - test -f {{ .solo_keys_dir }}/hedera-node${n}.key - test -f {{ .solo_keys_dir }}/s-public-node${n}.pem - test -f {{ .solo_keys_dir }}/s-private-node${n}.pem - done - cmds: - - npm run solo-test -- node keys --gossip-keys --tls-keys --node-aliases-unparsed {{.node_identifiers}} - - solo:network:deploy: - internal: true - cmds: - - npm run solo-test -- network deploy --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} --release-tag "${CONSENSUS_NODE_VERSION}" --solo-chart-version "${SOLO_CHART_VERSION}" - - npm run solo-test -- node setup --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} --release-tag "${CONSENSUS_NODE_VERSION}" - - solo:network:destroy: - internal: true - cmds: - - npm run solo-test -- network destroy --namespace "${SOLO_NAMESPACE}" --delete-pvcs --delete-secrets --force - - solo:node:start: - internal: true - cmds: - - npm run solo-test -- node start --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} {{ .CLI_ARGS }} - - solo:node:stop: - internal: true - ignore_error: true - cmds: - - npm run solo-test -- node stop --namespace "${SOLO_NAMESPACE}" --node-aliases-unparsed {{.node_identifiers}} {{ .CLI_ARGS }} - - solo:node:addresses: - internal: true - cmds: - - kubectl get svc -n "${SOLO_NAMESPACE}" -l "solo.hedera.com/type=network-node-svc" - - solo:cache:remove: - internal: true - status: - - test [[ ! -d {{ .solo_cache_dir }} ]] - cmds: - - rm -rf {{ .solo_cache_dir }} - - solo:logs:remove: - internal: true - status: - - test [[ ! -d {{ .solo_logs_dir }} ]] - cmds: - - rm -rf {{ .solo_logs_dir }} - - solo:config:remove: - internal: true - status: - - test [[ ! -f {{ .solo_user_dir }}/solo.yaml ]] - cmds: - - rm -rf {{ .solo_user_dir }}/solo.yaml - - install:solo: - internal: true - status: - - command -v solo - cmds: - - npm install -g @hashgraph/solo - - install:kubectl:darwin: - internal: true - platforms: - - darwin - status: - - command -v kubectl - cmds: - - brew update - - brew install kubernetes-cli - - install:kubectl:linux: - internal: true - platforms: - - linux - status: - - command -v kubectl - cmds: - - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/{{ ARCH }}/kubectl" - - sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl - - rm -rf kubectl - - install:kind:darwin: - internal: true - platforms: - - darwin - status: - - command -v kind - cmds: - - brew install kind - - install:kind:linux:x86_64: - internal: true - platforms: - - linux - status: - - command -v kind - - test ! "$(uname -m)" == "x86_64" - cmds: - - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64 - - chmod +x ./kind - - sudo mv ./kind /usr/local/bin/kind - - install:kind:linux:aarch64: - internal: true - platforms: - - linux - status: - - command -v kind - - test ! "$(uname -m)" == "aarch64" - cmds: - - curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-arm64 - - chmod +x ./kind - - sudo mv ./kind /usr/local/bin/kind - - install:node:linux: - internal: true - platforms: - - linux - status: - - command -v node - cmds: - - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash - - source ~/.bashrc - - nvm install 21 - - install:node:darwin: - internal: true - platforms: - - darwin - status: - - command -v node - cmds: - - brew install node@21 - - install:helm:darwin: - internal: true - platforms: - - darwin - status: - - command -v helm - cmds: - - brew install helm - - install:helm:linux: - internal: true - platforms: - - linux - status: - - command -v helm - cmds: - - curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null - - sudo apt-get install apt-transport-https --yes - - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list - - sudo apt-get update - - sudo apt-get install helm + - task: "helper:solo:logs:remove" diff --git a/docs/content/User/SDK.md b/docs/content/User/SDK.md new file mode 100644 index 000000000..ec3d07785 --- /dev/null +++ b/docs/content/User/SDK.md @@ -0,0 +1,92 @@ +# Instructions for using Solo with Hedera JavaScript SDK +First, please follow solo repository README to install solo and Docker Desktop. +You also need to install the Taskfile tool following the instructions here: +https://taskfile.dev/installation/ + +Then we start with launching a local Solo network with the following commands: + +```bash +# launch a local Solo network with mirror node and hedera explorer +task default-with-mirror-node +``` +Then create a new test account with the following command: +``` +npm run solo-test -- account create -n solo-e2e --hbar-amount 100 +``` +The output would be similar to the following: + +```bash + *** new account created *** +------------------------------------------------------------------------------- +{ + "accountId": "0.0.1007", + "publicKey": "302a300506032b65700321001d8978e647aca1195c54a4d3d5dc469b95666de14e9b6edde8ed337917b96013", + "balance": 100 +} +``` + +Then use the following commmand to get private key of the account `0.0.1007`: +```bash + npm run solo-test -- account get --account-id 0.0.1007 -n solo-e2e --private-key +``` +The output would be similar to the following: +```bash +{ + "accountId": "0.0.1007", + "privateKey": "302e020100300506032b657004220420cfea706dd9ed2d3c1660ba98acf4fdb74d247cce289ef6ef47486e055e0b9508", + "publicKey": "302a300506032b65700321001d8978e647aca1195c54a4d3d5dc469b95666de14e9b6edde8ed337917b96013", + "balance": 100 +} +``` + +Next step please clone the Hedera Javascript SDK repository https://github.com/hashgraph/hedera-sdk-js. +At the root of the project `hedera-sdk-js`, create a file `.env` and add the following content: + +```bash +# Hedera Operator Account ID +OPERATOR_ID="0.0.1007" + +# Hedera Operator Private Key +OPERATOR_KEY="302a300506032b65700321001d8978e647aca1195c54a4d3d5dc469b95666de14e9b6edde8ed337917b96013" + +# Hedera Network +HEDERA_NETWORK="local-node" +``` +Make sure to assign the value of accountId to OPERATOR_ID and the value of privateKey to OPERATOR_KEY. + +Then try the following command to run the test + +```bash +node examples/create-account.js +``` + +The output should be similar to the following: + +```bash +private key = 302e020100300506032b6570042204208a3c1093c4df779c4aa980d20731899e0b509c7a55733beac41857a9dd3f1193 +public key = 302a300506032b6570032100c55adafae7e85608ea893d0e2c77e2dae3df90ba8ee7af2f16a023ba2258c143 +account id = 0.0.1009 +``` + +Or try the topic creation example: +```bash +node examples/create-topic.js +``` +The output should be similar to the following: + +```bash +topic id = 0.0.1008 +topic sequence number = 1 + + +``` + +You can use Hedera explorer to check transactions and topics created in the Solo network: +http://localhost:8080/localnet/dashboard + +Finally, after done with using solo, using the following command to tear down the Solo network: + +```bash +task clean +``` + diff --git a/docs/data/menu/main.yml b/docs/data/menu/main.yml index 538527ec5..04beab152 100644 --- a/docs/data/menu/main.yml +++ b/docs/data/menu/main.yml @@ -2,6 +2,8 @@ main: - name: Getting Started ref: "/User/README.md" + - name: How to use Hashgraph javascript SDK with Solo + ref: "/User/SDK.md" - name: Development ref: "/Developer/DEV.md" - name: Classes diff --git a/examples/custom-network-config/Taskfile.yml b/examples/custom-network-config/Taskfile.yml index 5c76ab0ce..176909d23 100644 --- a/examples/custom-network-config/Taskfile.yml +++ b/examples/custom-network-config/Taskfile.yml @@ -1,8 +1,8 @@ version: 3 -output: prefixed +includes: + helper: ../HelperTasks.yml dotenv: - .env -silent: false env: SOLO_CHART_VERSION: v0.32.0 @@ -10,17 +10,18 @@ env: SOLO_NAMESPACE: solo-{{ env "USER" | replace "." "-" | trunc 63 }} SOLO_CLUSTER_SETUP_NAMESPACE: solo-setup SOLO_NETWORK_SIZE: 7 + SOLO_CLUSTER_RELEASE_NAME: solo-cluster-setup + SOLO_CLUSTER_NAME: solo-cluster + MIRROR_RELEASE_NAME: mirror vars: solo_settings_file: "{{.ROOT_DIR}}/settings.txt" solo_values_file: "{{.ROOT_DIR}}/init-containers-values.yaml" ip_list_template_file: "{{.ROOT_DIR}}/list-external-ips.gotemplate" - nodes: ref: until (env "SOLO_NETWORK_SIZE" | default .SOLO_NETWORK_SIZE | int) node_list_internal: "{{range $idx, $n := .nodes }}node{{$n}},{{end}}" node_identifiers: "{{ .node_list_internal | trimSuffix \",\" }}" - solo_user_dir: "{{ env \"HOME\" }}/.solo" solo_cache_dir: "{{ .solo_user_dir }}/cache" solo_logs_dir: "{{ .solo_user_dir }}/logs" @@ -30,34 +31,34 @@ vars: tasks: default: cmds: - - task: "install:kubectl:darwin" - - task: "install:kubectl:linux" - - task: "install:solo" + - task: "helper:install:kubectl:darwin" + - task: "helper:install:kubectl:linux" + - task: "helper:install:solo" - task: "install" - task: "start" install: cmds: - - task: "solo:init" - - task: "solo:keys" - - task: "solo:network:deploy" + - task: "helper:solo:init" + - task: "helper:solo:keys" + - task: "helper:solo:network:deploy" start: cmds: - - task: "solo:node:start" + - task: "helper:solo:node:start" stop: cmds: - - task: "solo:node:stop" + - task: "helper:solo:node:stop" show:ips: cmds: - - task: "solo:node:addresses" + - task: "helper:solo:node:addresses" destroy: cmds: - - task: "solo:node:stop" - - task: "solo:network:destroy" + - task: "helper:solo:node:stop" + - task: "helper:solo:network:destroy" clean: cmds: @@ -67,113 +68,8 @@ tasks: clean:cache: cmds: - - task: "solo:cache:remove" + - task: "helper:solo:cache:remove" clean:logs: cmds: - - task: "solo:logs:remove" - - solo:init: - internal: true - status: - - test -f {{ .solo_bin_dir }}/helm - - test -f {{ .solo_cache_dir }}/profiles/custom-spec.yaml - - test -f {{ .solo_cache_dir }}/templates/api-permission.properties - - test -f {{ .solo_cache_dir }}/templates/application.properties - - test -f {{ .solo_cache_dir }}/templates/bootstrap.properties - - test -f {{ .solo_cache_dir }}/templates/settings.txt - - test -f {{ .solo_cache_dir }}/templates/log4j2.xml - #- test "$(yq -r '.flags."node-ids"' < {{ .solo_user_dir }}/solo.yaml)" == "{{ .node_identifiers }}" - - test "$(jq -r '.flags."node-ids"' < {{ .solo_user_dir }}/solo.config)" == "{{ .node_identifiers }}" - cmds: - - solo init --namespace "${SOLO_NAMESPACE}" --node-ids {{.node_identifiers}} --release-tag "${CONSENSUS_NODE_VERSION}" --cluster-setup-namespace "${SOLO_CLUSTER_SETUP_NAMESPACE}" - - solo:keys: - internal: true - status: - - | - for n in $(seq 0 {{ sub (env "SOLO_NETWORK_SIZE" | default .SOLO_NETWORK_SIZE | int) 1 }}); do - test -f {{ .solo_keys_dir }}/hedera-node${n}.crt - test -f {{ .solo_keys_dir }}/hedera-node${n}.key - test -f {{ .solo_keys_dir }}/s-public-node${n}.pem - test -f {{ .solo_keys_dir }}/s-private-node${n}.pem - done - cmds: - - solo node keys --gossip-keys --tls-keys - - solo:network:deploy: - internal: true - cmds: - - solo network deploy --release-tag "${CONSENSUS_NODE_VERSION}" --solo-chart-version "${SOLO_CHART_VERSION}" --values-file {{ .solo_values_file }} --settings-txt {{ .solo_settings_file }} - - solo node setup --release-tag "${CONSENSUS_NODE_VERSION}" - - solo:network:destroy: - internal: true - cmds: - - solo network destroy --namespace "${SOLO_NAMESPACE}" --delete-pvcs --delete-secrets --force - - solo:node:start: - internal: true - cmds: - - solo node start --namespace "${SOLO_NAMESPACE}" {{ .CLI_ARGS }} - - solo:node:stop: - internal: true - ignore_error: true - cmds: - - solo node stop --namespace "${SOLO_NAMESPACE}" {{ .CLI_ARGS }} - - solo:node:addresses: - internal: true - cmds: - - kubectl get svc -n "${SOLO_NAMESPACE}" -l "solo.hedera.com/type=network-node-svc" --output=go-template-file={{ .ip_list_template_file }} - - solo:cache:remove: - internal: true - status: - - test [[ ! -d {{ .solo_cache_dir }} ]] - cmds: - - rm -rf {{ .solo_cache_dir }} - - solo:logs:remove: - internal: true - status: - - test [[ ! -d {{ .solo_logs_dir }} ]] - cmds: - - rm -rf {{ .solo_logs_dir }} - - solo:config:remove: - internal: true - status: - - test [[ ! -f {{ .solo_user_dir }}/solo.yaml ]] - cmds: - - rm -rf {{ .solo_user_dir }}/solo.yaml - - install:solo: - internal: true - status: - - command -v solo - cmds: - - npm install -g @hashgraph/solo - - install:kubectl:darwin: - internal: true - platforms: - - darwin - status: - - command -v kubectl - cmds: - - brew update - - brew install kubernetes-cli - - install:kubectl:linux: - internal: true - platforms: - - linux - status: - - command -v kubectl - cmds: - - curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/{{ ARCH }}/kubectl" - - sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl - - rm -rf kubectl - + - task: "helper:solo:logs:remove" diff --git a/examples/sdk-network-connection/README.md b/examples/sdk-network-connection/README.md new file mode 100644 index 000000000..eafd68dbb --- /dev/null +++ b/examples/sdk-network-connection/README.md @@ -0,0 +1,13 @@ +# Solo Network Connection Example + +## pre-requirements: +1. fork or download the solo repository: https://github.com/hashgraph/solo +2. have NodeJS 20+ and NPM installed: https://nodejs.org/en/download/package-manager +3. have Taskfile installed: https://taskfile.dev/installation/ + +## running the Solo connection example: +1. open a terminal and cd into the root of the solo repo directory +2. run: `task default-with-mirror` +3. run: `cd examples/sdk-network-connection` +4. run: `npm install` +5. run: `node solo-network-connection.js` diff --git a/examples/sdk-network-connection/package-lock.json b/examples/sdk-network-connection/package-lock.json new file mode 100644 index 000000000..aba63aaaf --- /dev/null +++ b/examples/sdk-network-connection/package-lock.json @@ -0,0 +1,1411 @@ +{ + "name": "sdk-network-connection", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sdk-network-connection", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@hashgraph/sdk": "^2.53.0" + } + }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ] + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.2.tgz", + "integrity": "sha512-5cqCjUvDKJWHGeu1prlrFOUmjuML0NequZKJ38PsCkfwIqPnZq4Q9burPP3It7/+46wpl0KsqVN3s6Te3B9Qtw==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/@hashgraph/cryptography": { + "version": "1.4.8-beta.9", + "resolved": "https://registry.npmjs.org/@hashgraph/cryptography/-/cryptography-1.4.8-beta.9.tgz", + "integrity": "sha512-U6XeX2TsTg7XT6tDRJOfVTO17ltvjcfMtoK42OCUbN28UPBHuxO7ZOy7z0qVqtjX8knOwbURTjrt9bIrU96AsQ==", + "dependencies": { + "asn1js": "^3.0.5", + "bignumber.js": "^9.1.1", + "bn.js": "^5.2.1", + "buffer": "^6.0.3", + "crypto-js": "^4.2.0", + "elliptic": "^6.5.4", + "js-base64": "^3.7.4", + "node-forge": "^1.3.1", + "spark-md5": "^3.0.2", + "tweetnacl": "^1.0.3", + "utf8": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "expo": "^49.0.16", + "expo-crypto": "^10.1.2", + "expo-random": "^12.1.2" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-crypto": { + "optional": true + }, + "expo-random": { + "optional": true + } + } + }, + "node_modules/@hashgraph/cryptography/node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/@hashgraph/proto": { + "version": "2.15.0-beta.4", + "resolved": "https://registry.npmjs.org/@hashgraph/proto/-/proto-2.15.0-beta.4.tgz", + "integrity": "sha512-da51j1RCHm+uXpQNM0KJ7qbhUJLTp6Avw8GdL+PQCbZ4lBwKAo8jjJ5rRjf1odsN1+zKl+JF7SMmKZB8PY229Q==", + "dependencies": { + "long": "^4.0.0", + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@hashgraph/sdk": { + "version": "2.53.0", + "resolved": "https://registry.npmjs.org/@hashgraph/sdk/-/sdk-2.53.0.tgz", + "integrity": "sha512-NpKGzWGzMoKa37VZ1ph+wAKQ4QRLKQazAcNFLftDugAFSLVHrFJRJkwu8j2M0BLC99faLQkkvjwSGsUtWu1JFg==", + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@grpc/grpc-js": "1.8.2", + "@hashgraph/cryptography": "1.4.8-beta.9", + "@hashgraph/proto": "2.15.0-beta.4", + "axios": "^1.6.4", + "bignumber.js": "^9.1.1", + "bn.js": "^5.1.1", + "crypto-js": "^4.2.0", + "js-base64": "^3.7.4", + "long": "^4.0.0", + "pino": "^8.14.1", + "pino-pretty": "^10.0.0", + "protobufjs": "7.2.5", + "rfc4648": "^1.5.3", + "utf8": "^3.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "expo": "^49.0.16" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@types/node": { + "version": "22.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", + "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/asn1js": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.5.tgz", + "integrity": "sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==", + "dependencies": { + "pvtsutils": "^1.3.2", + "pvutils": "^1.1.3", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pino": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz", + "integrity": "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.6.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", + "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, + "node_modules/protobufjs": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", + "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.5.tgz", + "integrity": "sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==", + "dependencies": { + "tslib": "^2.6.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz", + "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rfc4648": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.3.tgz", + "integrity": "sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/sonic-boom": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz", + "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==" + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thread-stream": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.7.0.tgz", + "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", + "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/examples/sdk-network-connection/package.json b/examples/sdk-network-connection/package.json new file mode 100644 index 000000000..5a2e26363 --- /dev/null +++ b/examples/sdk-network-connection/package.json @@ -0,0 +1,15 @@ +{ + "name": "sdk-network-connection", + "version": "1.0.0", + "description": "", + "main": "solo-network-connection.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@hashgraph/sdk": "^2.53.0" + } +} diff --git a/examples/sdk-network-connection/solo-network-connection.js b/examples/sdk-network-connection/solo-network-connection.js new file mode 100644 index 000000000..a2fb158d5 --- /dev/null +++ b/examples/sdk-network-connection/solo-network-connection.js @@ -0,0 +1,63 @@ +import { + AccountBalanceQuery, + AccountId, + Client, + Logger, LogLevel, + PrivateKey +} from '@hashgraph/sdk' + +export const TREASURY_ACCOUNT_ID = '0.0.2' +export const GENESIS_KEY = '302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137' + +/** + * Given that you have deployed Solo in a cluster on your local machine + * And it is in namespace = solo-e2e + * we want to run the following commands to open ports + * $ export SOLO_NAMESPACE=solo-e2e + * $ kubectl port-forward svc/haproxy-node1-svc -n "${SOLO_NAMESPACE}" 50211:50211 & + * $ kubectl port-forward svc/mirror-grpc -n "${SOLO_NAMESPACE}" 5600:5600 & + **/ + + +function main() { + console.log('begin...') + const treasuryAccountId = TREASURY_ACCOUNT_ID + const treasuryPrivateKey = PrivateKey.fromStringED25519(GENESIS_KEY) + const network = {} + + network['127.0.0.1:50211'] = AccountId.fromString('0.0.3') + + const mirrorNetwork = '127.0.0.1:5600' + + // scheduleNetworkUpdate is set to false, because the ports 50212/50211 are hardcoded in JS SDK that will not work when running locally or in a pipeline + console.log('creating client') + const nodeClient = Client.fromConfig({ + network, + mirrorNetwork, + scheduleNetworkUpdate: false + }) + nodeClient.setOperator(treasuryAccountId, treasuryPrivateKey) + nodeClient.setLogger(new Logger(LogLevel.Trace, 'hashgraph-sdk.log')) + console.log('client created') + + // check balance + try { + console.log('checking balance') + new AccountBalanceQuery() + .setAccountId('0.0.2') + .execute(nodeClient).then(balance => { + console.log('checking balance...end') + console.log(`Account ${treasuryAccountId} balance: ${balance?.hbars}`) + console.log('...end') + }).catch(err => { + console.log('failure') + console.log(err.message, err.stacktrace) + }) + }catch(e){ + console.log('failure') + console.log(e.message, e.stacktrace) + }finally{ + console.log('finally') + } +} +main() diff --git a/src/commands/account.ts b/src/commands/account.ts index 0dd04b602..795d15610 100644 --- a/src/commands/account.ts +++ b/src/commands/account.ts @@ -300,12 +300,11 @@ export class AccountCommand extends BaseCommand { } }, { - title: 'create the new account.js', + title: 'create the new account', task: async (ctx) => { self.accountInfo = await self.createNewAccount(ctx) const accountInfoCopy = { ...self.accountInfo } delete accountInfoCopy.privateKey - this.logger.showJSON('new account created', accountInfoCopy) } } @@ -414,6 +413,7 @@ export class AccountCommand extends BaseCommand { config: { accountId: string; namespace: string; + privateKey: boolean; } } @@ -430,7 +430,8 @@ export class AccountCommand extends BaseCommand { const config = { accountId: self.configManager.getFlag(flags.accountId) as string, - namespace: self.configManager.getFlag(flags.namespace) as string + namespace: self.configManager.getFlag(flags.namespace) as string, + privateKey: self.configManager.getFlag(flags.privateKey) as boolean } if (!await this.k8.hasNamespace(config.namespace)) { @@ -448,7 +449,7 @@ export class AccountCommand extends BaseCommand { { title: 'get the account info', task: async (ctx) => { - self.accountInfo = await self.buildAccountInfo(await self.getAccountInfo(ctx), ctx.config.namespace, false) + self.accountInfo = await self.buildAccountInfo(await self.getAccountInfo(ctx), ctx.config.namespace, ctx.config.privateKey) this.logger.showJSON('account info', self.accountInfo) } } @@ -546,6 +547,7 @@ export class AccountCommand extends BaseCommand { desc: 'Gets the account info including the current amount of HBAR', builder: (y: any) => flags.setCommandFlags(y, flags.accountId, + flags.privateKey, flags.namespace ), handler: (argv: any) => { diff --git a/src/commands/flags.ts b/src/commands/flags.ts index 8e3fcb6df..551e489c3 100644 --- a/src/commands/flags.ts +++ b/src/commands/flags.ts @@ -198,7 +198,7 @@ export const relayReleaseTag: CommandFlag = { name: 'relay-release', definition: { describe: 'Relay release tag to be used (e.g. v0.48.0)', - defaultValue: 'v0.53.0', + defaultValue: version.HEDERA_JSON_RPC_RELAY_VERSION, type: 'string' } } @@ -289,6 +289,16 @@ export const operatorKey: CommandFlag = { } } +export const privateKey: CommandFlag = { + constName: 'privateKey', + name: 'private-key', + definition: { + describe: 'Show private key information', + defaultValue: false, + type: 'boolean' + } +} + export const generateGossipKeys: CommandFlag = { constName: 'generateGossipKeys', name: 'gossip-keys', @@ -844,6 +854,7 @@ export const allFlags: CommandFlag[] = [ operatorKey, outputDir, persistentVolumeClaims, + privateKey, profileFile, profileName, relayReleaseTag, diff --git a/src/commands/relay.ts b/src/commands/relay.ts index 39ce73ac3..fa1e1d212 100644 --- a/src/commands/relay.ts +++ b/src/commands/relay.ts @@ -79,8 +79,8 @@ export class RelayCommand extends BaseCommand { valuesArg += this.prepareValuesFiles(profileValuesFile) } - valuesArg += ` --set config.MIRROR_NODE_URL=http://${constants.SOLO_DEPLOYMENT_CHART}-rest` - valuesArg += ` --set config.MIRROR_NODE_URL_WEB3=http://${constants.SOLO_DEPLOYMENT_CHART}-web3` + valuesArg += ` --set config.MIRROR_NODE_URL=http://${constants.MIRROR_NODE_RELEASE_NAME}-rest` + valuesArg += ` --set config.MIRROR_NODE_URL_WEB3=http://${constants.MIRROR_NODE_RELEASE_NAME}-web3` valuesArg += ' --set config.MIRROR_NODE_AGENT_CACHEABLE_DNS=false' valuesArg += ' --set config.MIRROR_NODE_RETRY_DELAY=2001' valuesArg += ' --set config.MIRROR_NODE_GET_CONTRACT_RESULTS_DEFAULT_RETRIES=21' diff --git a/test/e2e/commands/account.test.ts b/test/e2e/commands/account.test.ts index 1ebd6cd7a..1372b9054 100644 --- a/test/e2e/commands/account.test.ts +++ b/test/e2e/commands/account.test.ts @@ -17,10 +17,21 @@ import { it, describe, after, before } from 'mocha' import { expect } from 'chai' -import { AccountId, PrivateKey } from '@hashgraph/sdk' +import { + AccountCreateTransaction, + AccountId, + Client, + Hbar, + HbarUnit, + Logger, + LogLevel, + PrivateKey, Status, + TopicCreateTransaction, TopicMessageSubmitTransaction +} from '@hashgraph/sdk' import { constants } from '../../../src/core/index.js' import * as version from '../../../version.js' import { + bootstrapTestVariables, e2eTestSuite, getDefaultArgv, HEDERA_PLATFORM_VERSION_TAG, @@ -273,5 +284,82 @@ e2eTestSuite(testName, argv, undefined, undefined, undefined, undefined, undefin } }).timeout(defaultTimeout) }) + + + describe('Test SDK create account and submit transaction', () => { + const accountManager = bootstrapResp.opts.accountManager + const networkCmd = bootstrapResp.cmd.networkCmd + + let accountInfo: { + accountId: string, + privateKey: string, + publicKey: string, + balance: number } + + let MY_ACCOUNT_ID: string + let MY_PRIVATE_KEY: string + + it('Create new account', async () => { + try { + await accountManager.loadNodeClient(namespace) + const privateKey = PrivateKey.generate() + const amount = 100 + + const newAccount = await new AccountCreateTransaction() + .setKey(privateKey) + .setInitialBalance(Hbar.from(amount, HbarUnit.Hbar)) + .execute(accountManager._nodeClient) + + // Get the new account ID + const getReceipt = await newAccount.getReceipt(accountManager._nodeClient) + accountInfo = { + accountId: getReceipt.accountId.toString(), + privateKey: privateKey.toString(), + publicKey: privateKey.publicKey.toString(), + balance: amount + } + + MY_ACCOUNT_ID = accountInfo.accountId + MY_PRIVATE_KEY = accountInfo.privateKey + + networkCmd.logger.info(`Account created: ${JSON.stringify(accountInfo)}`) + expect(accountInfo.accountId).not.to.be.null + expect(accountInfo.balance).to.equal(amount) + } catch (e) { + networkCmd.logger.showUserError(e) + } + }).timeout(2 * MINUTES) + + + it('Create client from network config and submit topic/message should succeed', async () => { + try { + + // Setup network configuration + const networkConfig = {} + networkConfig['127.0.0.1:30212'] = AccountId.fromString('0.0.3') + networkConfig['127.0.0.1:30213'] = AccountId.fromString('0.0.4') + + // Instantiate SDK client + const sdkClient = Client.fromConfig({ network: networkConfig, scheduleNetworkUpdate: false }) + sdkClient.setOperator(MY_ACCOUNT_ID, MY_PRIVATE_KEY) + sdkClient.setLogger(new Logger(LogLevel.Trace, 'hashgraph-sdk.log')) + + // Create a new public topic and submit a message + const txResponse = await new TopicCreateTransaction().execute(sdkClient) + const receipt = await txResponse.getReceipt(sdkClient) + + const submitResponse = await new TopicMessageSubmitTransaction({ + topicId: receipt.topicId, + message: 'Hello, Hedera!' + }).execute(sdkClient) + + const submitReceipt = await submitResponse.getReceipt(sdkClient) + + expect(submitReceipt.status).to.deep.equal(Status.Success) + } catch (e) { + networkCmd.logger.showUserError(e) + } + }).timeout(2 * MINUTES) + }) }) }) diff --git a/version.ts b/version.ts index 4d17eb1b5..e6192161b 100644 --- a/version.ts +++ b/version.ts @@ -25,3 +25,4 @@ export const SOLO_CHART_VERSION = '0.34.1' export const HEDERA_PLATFORM_VERSION = 'v0.56.5' export const MIRROR_NODE_VERSION = '0.116.0' export const HEDERA_EXPLORER_VERSION = '0.2.1' +export const HEDERA_JSON_RPC_RELAY_VERSION = 'v0.59.0' From 5378e8fe970cce8cd831dd7898e52941dfda2d16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:29:32 -0600 Subject: [PATCH 2/3] chore(deps-dev): bump eslint-plugin-n from 17.13.2 to 17.14.0 (#861) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1e6e5a0ec..8a60103b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,7 +78,7 @@ "eslint-plugin-headers": "^1.2.0", "eslint-plugin-import": "^2.30.0", "eslint-plugin-mocha": "^10.5.0", - "eslint-plugin-n": "^17.13.2", + "eslint-plugin-n": "^17.14.0", "eslint-plugin-promise": "^7.1.0", "eslint-plugin-tsdoc": "^0.3.0", "globals": "^15.12.0", @@ -4878,11 +4878,10 @@ } }, "node_modules/eslint-plugin-n": { - "version": "17.13.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.13.2.tgz", - "integrity": "sha512-MhBAKkT01h8cOXcTBTlpuR7bxH5OBUNpUXefsvwSVEy46cY4m/Kzr2osUCQvA3zJFD6KuCeNNDv0+HDuWk/OcA==", + "version": "17.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.14.0.tgz", + "integrity": "sha512-maxPLMEA0rPmRpoOlxEclKng4UpDe+N5BJS4t24I3UKnN109Qcivnfs37KMy84G0af3bxjog5lKctP5ObsvcTA==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.1", "enhanced-resolve": "^5.17.1", diff --git a/package.json b/package.json index 2c7bb3c9d..2fc119d1f 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "eslint-plugin-headers": "^1.2.0", "eslint-plugin-import": "^2.30.0", "eslint-plugin-mocha": "^10.5.0", - "eslint-plugin-n": "^17.13.2", + "eslint-plugin-n": "^17.14.0", "eslint-plugin-promise": "^7.1.0", "eslint-plugin-tsdoc": "^0.3.0", "globals": "^15.12.0", From 13ea46d7db68bfe4f5ec9954ae6229eb37f4f571 Mon Sep 17 00:00:00 2001 From: Jeromy Cannon Date: Mon, 25 Nov 2024 10:46:49 +0000 Subject: [PATCH 3/3] fix: inconsistent failures (#866) Signed-off-by: Jeromy Cannon --- .github/workflows/flow-build-application.yaml | 8 +- .github/workflows/zxc-code-analysis.yaml | 42 +-- eslint.config.mjs | 11 +- package.json | 2 +- resources/post-build-script.js | 16 + src/commands/flags.ts | 12 + src/commands/network.ts | 4 +- src/commands/node/configs.ts | 1 + src/commands/node/flags.ts | 5 +- src/commands/node/handlers.ts | 20 +- src/commands/node/tasks.ts | 298 ++++++++++-------- src/core/account_manager.ts | 22 +- src/core/config_manager.ts | 12 +- src/core/constants.ts | 5 +- src/core/enumerations.ts | 6 + src/core/helpers.ts | 6 +- src/core/k8.ts | 97 +++++- src/core/logging.ts | 2 +- src/core/platform_installer.ts | 12 +- test/e2e/commands/node_delete.test.ts | 3 +- test/e2e/commands/separate_node_add.test.ts | 3 +- .../e2e/commands/separate_node_delete.test.ts | 3 +- test/e2e/integration/core/k8_e2e.test.ts | 53 ++-- test/test_add.ts | 3 +- test/test_util.ts | 8 +- 25 files changed, 417 insertions(+), 237 deletions(-) diff --git a/.github/workflows/flow-build-application.yaml b/.github/workflows/flow-build-application.yaml index 404622268..766f3eb36 100644 --- a/.github/workflows/flow-build-application.yaml +++ b/.github/workflows/flow-build-application.yaml @@ -82,9 +82,11 @@ jobs: - { name: "Node PEM Kill", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-pem-kill-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-pem-kill-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-pem-kill-coverage-report }}" } - { name: "Node Local Hedera", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-local-hedera-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-local-hedera-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-local-hedera-coverage-report }}" } - { name: "Node Local PTT", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-local-ptt-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-local-ptt-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-local-ptt-coverage-report }}" } - - { name: "Node Add", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-add-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-add-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-add-coverage-report }}" } - - { name: "Node Add Local", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-add-local-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-add-local-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-add-local-coverage-report }}" } - - { name: "Node Add - Separate commands", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-add-separate-commands-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-add-separate-commands-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-add-separate-commands-coverage-report }}" } + # Node Add tests are disabled and are not viable for the amount of memory available. The teacher crashes during + # the reconnect attempt. +# - { name: "Node Add", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-add-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-add-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-add-coverage-report }}" } +# - { name: "Node Add Local", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-add-local-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-add-local-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-add-local-coverage-report }}" } +# - { name: "Node Add - Separate commands", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-add-separate-commands-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-add-separate-commands-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-add-separate-commands-coverage-report }}" } - { name: "Node Update", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-update-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-update-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-update-coverage-report }}" } - { name: "Node Update - Separate commands", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-update-separate-commands-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-update-separate-commands-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-update-separate-commands-coverage-report }}" } - { name: "Node Delete", npm-test-script: "test-${{ needs.env-vars.outputs.e2e-node-delete-test-subdir }}", coverage-subdirectory: "${{ needs.env-vars.outputs.e2e-node-delete-test-subdir }}", coverage-report-name: "${{ needs.env-vars.outputs.e2e-node-delete-coverage-report }}" } diff --git a/.github/workflows/zxc-code-analysis.yaml b/.github/workflows/zxc-code-analysis.yaml index 5abf4e6d2..415d730ee 100644 --- a/.github/workflows/zxc-code-analysis.yaml +++ b/.github/workflows/zxc-code-analysis.yaml @@ -314,26 +314,28 @@ jobs: name: ${{ inputs.e2e-node-local-ptt-coverage-report }} path: 'coverage/${{ inputs.e2e-node-local-ptt-test-subdir }}' - - name: Download E2E Node Add Coverage Report - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - if: ${{ (inputs.enable-codecov-analysis || inputs.enable-codacy-coverage) && inputs.enable-e2e-coverage-report && !cancelled() && !failure() }} - with: - name: ${{ inputs.e2e-node-add-coverage-report }} - path: 'coverage/${{ inputs.e2e-node-add-test-subdir }}' - - - name: Download E2E Node Add Local Coverage Report - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - if: ${{ (inputs.enable-codecov-analysis || inputs.enable-codacy-coverage) && inputs.enable-e2e-coverage-report && !cancelled() && !failure() }} - with: - name: ${{ inputs.e2e-node-add-local-coverage-report }} - path: 'coverage/${{ inputs.e2e-node-add-local-test-subdir }}' - - - name: Download E2E Node Add - Separate commands Coverage Report - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - if: ${{ (inputs.enable-codecov-analysis || inputs.enable-codacy-coverage) && inputs.enable-e2e-coverage-report && !cancelled() && !failure() }} - with: - name: ${{ inputs.e2e-node-add-separate-commands-coverage-report }} - path: 'coverage/${{ inputs.e2e-node-add-separate-commands-test-subdir }}' + # Node Add tests are disabled and are not viable for the amount of memory available. The teacher crashes during + # the reconnect attempt. +# - name: Download E2E Node Add Coverage Report +# uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 +# if: ${{ (inputs.enable-codecov-analysis || inputs.enable-codacy-coverage) && inputs.enable-e2e-coverage-report && !cancelled() && !failure() }} +# with: +# name: ${{ inputs.e2e-node-add-coverage-report }} +# path: 'coverage/${{ inputs.e2e-node-add-test-subdir }}' +# +# - name: Download E2E Node Add Local Coverage Report +# uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 +# if: ${{ (inputs.enable-codecov-analysis || inputs.enable-codacy-coverage) && inputs.enable-e2e-coverage-report && !cancelled() && !failure() }} +# with: +# name: ${{ inputs.e2e-node-add-local-coverage-report }} +# path: 'coverage/${{ inputs.e2e-node-add-local-test-subdir }}' +# +# - name: Download E2E Node Add - Separate commands Coverage Report +# uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 +# if: ${{ (inputs.enable-codecov-analysis || inputs.enable-codacy-coverage) && inputs.enable-e2e-coverage-report && !cancelled() && !failure() }} +# with: +# name: ${{ inputs.e2e-node-add-separate-commands-coverage-report }} +# path: 'coverage/${{ inputs.e2e-node-add-separate-commands-test-subdir }}' - name: Download E2E Node Update Coverage Report uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 diff --git a/eslint.config.mjs b/eslint.config.mjs index 5f3cff146..90fe24fd6 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -28,8 +28,8 @@ export default [ { ignores: ['docs/**/*', 'dist/*'], }, - { - files: ['test/**/*.ts', 'src/**/*.ts'], + { // all ts files + files: ['**/*.ts'], plugins: { headers: headers, tsdoc: tsdoc, @@ -69,6 +69,13 @@ export default [ 'space-before-function-paren': 'error', '@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/class-literal-property-style': 'off', + 'no-invalid-this': [ 'error', { capIsConstructor : false } ], + } + }, + { // test ts files + files: ['test/**/*.ts'], + rules: { + 'no-invalid-this': [ 'off', { } ], } }, { diff --git a/package.json b/package.json index 2fc119d1f..18468210a 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "check": "remark . --quiet --frail && eslint . --ignore-pattern 'docs/*' --ignore-pattern 'dist/*'; cd docs; jsdoc -c jsdoc.conf.json && tsc", "format": "remark . --quiet --frail --output && eslint --fix . --ignore-pattern 'docs/*' --ignore-pattern 'dist/*' && tsc", "test-setup": "./test/e2e/setup-e2e.sh", - "build": "tsc && node resources/post-build-script.js" + "build": "rm -Rf dist && tsc && node resources/post-build-script.js" }, "keywords": [ "solo", diff --git a/resources/post-build-script.js b/resources/post-build-script.js index 5c7874905..4d14f5ff6 100644 --- a/resources/post-build-script.js +++ b/resources/post-build-script.js @@ -20,7 +20,23 @@ function copyResources(srcDir, targetDir) { fs.cpSync(srcDir, targetDir, {recursive: true}) } +async function recursiveChmod(dir, mode) { + const files = await fs.promises.readdir(dir); + for (const file of files) { + const filePath = `${dir}/${file}`; + const stats = await fs.promises.stat(filePath); + if (stats.isDirectory()) { + await recursiveChmod(filePath, mode); + } else { + await fs.promises.chmod(filePath, mode); + } + } +} + +// Usage console.time('Copy package.json') copyPackageJson(srcPackageJsonFilePath, targetPackageJsonFilePath) console.time('Copy resources') copyResources(srcResourcesDir, targetResourcesDir) +console.time('Update permissions') +await recursiveChmod(distDir, 0o755); diff --git a/src/commands/flags.ts b/src/commands/flags.ts index 551e489c3..692abd6ab 100644 --- a/src/commands/flags.ts +++ b/src/commands/flags.ts @@ -802,6 +802,17 @@ export const grpcWebTlsKeyPath: CommandFlag = { } } +export const stakeAmounts: CommandFlag = { + constName: 'stakeAmounts', + name: 'stake-amounts', + definition: { + describe: + 'The amount to be staked in the same order you list the node aliases with multiple node staked values comma seperated', + defaultValue: '', + type: 'string' + } +} + export const allFlags: CommandFlag[] = [ accountId, amount, @@ -862,6 +873,7 @@ export const allFlags: CommandFlag[] = [ replicaCount, setAlias, settingTxt, + stakeAmounts, tlsClusterIssuerType, tlsPrivateKey, tlsPublicKey, diff --git a/src/commands/network.ts b/src/commands/network.ts index 5b4cc623d..c9956ee4a 100644 --- a/src/commands/network.ts +++ b/src/commands/network.ts @@ -335,8 +335,8 @@ export class NetworkCommand extends BaseCommand { title: `Check Node: ${chalk.yellow(nodeAlias)}`, task: async () => await self.k8.waitForPods([constants.POD_PHASE_RUNNING], [ - 'solo.hedera.com/type=network-node', - `solo.hedera.com/node-name=${nodeAlias}` + `solo.hedera.com/node-name=${nodeAlias}`, + 'solo.hedera.com/type=network-node' ], 1, constants.PODS_RUNNING_MAX_ATTEMPTS, constants.PODS_RUNNING_DELAY) }) } diff --git a/src/commands/node/configs.ts b/src/commands/node/configs.ts index 4fb56f996..dca534340 100644 --- a/src/commands/node/configs.ts +++ b/src/commands/node/configs.ts @@ -14,6 +14,7 @@ * limitations under the License. * */ +/* eslint-disable no-invalid-this */ import { FREEZE_ADMIN_ACCOUNT } from '../../core/constants.js' import { constants, Templates } from '../../core/index.js' import { PrivateKey } from '@hashgraph/sdk' diff --git a/src/commands/node/flags.ts b/src/commands/node/flags.ts index 231016a7c..4a8aa7122 100644 --- a/src/commands/node/flags.ts +++ b/src/commands/node/flags.ts @@ -230,7 +230,7 @@ export const STOP_FLAGS = { export const START_FLAGS = { requiredFlags: [ flags.namespace, - flags.releaseTag + flags.releaseTag, ], requiredFlagsWithDisabledPrompt: [ flags.app, @@ -238,7 +238,8 @@ export const START_FLAGS = { optionalFlags: [ flags.quiet, flags.nodeAliasesUnparsed, - flags.debugNodeAlias + flags.debugNodeAlias, + flags.stakeAmounts, ] } diff --git a/src/commands/node/handlers.ts b/src/commands/node/handlers.ts index dda674156..22a2d24f7 100644 --- a/src/commands/node/handlers.ts +++ b/src/commands/node/handlers.ts @@ -35,6 +35,7 @@ import type { SoloLogger } from '../../core/logging.js' import type { NodeCommand } from './index.js' import type { NodeCommandTasks } from './tasks.js' import { type Lease } from '../../core/lease/lease.js' +import { NodeSubcommandType } from '../../core/enumerations.js' export class NodeCommandHandlers { private readonly accountManager: AccountManager @@ -108,14 +109,13 @@ export class NodeCommandHandlers { deleteExecuteTaskList (argv: any) { return [ + this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.downloadNodeGeneratedFiles(), this.tasks.prepareStagingDirectory('existingNodeAliases'), - this.tasks.copyNodeKeysToSecrets(), this.tasks.refreshNodeList(), this.tasks.copyNodeKeysToSecrets(), - this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.getNodeLogsAndConfigs(), - this.tasks.updateChartWithConfigMap('Update chart to use new configMap'), + this.tasks.updateChartWithConfigMap('Delete network node', NodeSubcommandType.DELETE), this.tasks.killNodes(), this.tasks.sleep('Give time for pods to come up after being killed', 20000), this.tasks.checkNodePodsAreRunning(), @@ -126,7 +126,7 @@ export class NodeCommandHandlers { this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('allNodeAliases'), this.tasks.checkAllNodeProxiesAreActive(), - this.tasks.triggerStakeWeightCalculate(), + this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.DELETE), this.tasks.finalize() ] } @@ -159,12 +159,12 @@ export class NodeCommandHandlers { addExecuteTasks (argv: any) { return [ + this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.downloadNodeGeneratedFiles(), this.tasks.prepareStagingDirectory('allNodeAliases'), this.tasks.copyNodeKeysToSecrets(), - this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.getNodeLogsAndConfigs(), - this.tasks.updateChartWithConfigMap('Deploy new network node'), + this.tasks.updateChartWithConfigMap('Deploy new network node', NodeSubcommandType.ADD), this.tasks.killNodes(), this.tasks.checkNodePodsAreRunning(), this.tasks.populateServiceMap(), @@ -177,7 +177,7 @@ export class NodeCommandHandlers { this.tasks.checkAllNodesAreActive('allNodeAliases'), this.tasks.checkAllNodeProxiesAreActive(), this.tasks.stakeNewNode(), - this.tasks.triggerStakeWeightCalculate(), + this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.ADD), this.tasks.finalize() ] } @@ -202,13 +202,13 @@ export class NodeCommandHandlers { updateExecuteTasks (argv) { return [ + this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.downloadNodeGeneratedFiles(), this.tasks.prepareStagingDirectory('allNodeAliases'), this.tasks.copyNodeKeysToSecrets(), - this.tasks.checkAllNodesAreFrozen('existingNodeAliases'), this.tasks.getNodeLogsAndConfigs(), this.tasks.updateChartWithConfigMap( - 'Update chart to use new configMap due to account number change', + 'Update chart to use new configMap due to account number change', NodeSubcommandType.UPDATE, (ctx: any) => !ctx.config.newAccountNumber && !ctx.config.debugNodeAlias ), this.tasks.killNodesAndUpdateConfigMap(), @@ -219,7 +219,7 @@ export class NodeCommandHandlers { this.tasks.enablePortForwarding(), this.tasks.checkAllNodesAreActive('allNodeAliases'), this.tasks.checkAllNodeProxiesAreActive(), - this.tasks.triggerStakeWeightCalculate(), + this.tasks.triggerStakeWeightCalculate(NodeSubcommandType.UPDATE), this.tasks.finalize() ] } diff --git a/src/commands/node/tasks.ts b/src/commands/node/tasks.ts index c002afd81..f716c9fcd 100644 --- a/src/commands/node/tasks.ts +++ b/src/commands/node/tasks.ts @@ -30,7 +30,7 @@ import { import { DEFAULT_NETWORK_NODE_NAME, FREEZE_ADMIN_ACCOUNT, - HEDERA_NODE_DEFAULT_STAKE_AMOUNT, + HEDERA_NODE_DEFAULT_STAKE_AMOUNT, IGNORED_NODE_ACCOUNT_ID, LOCAL_HOST, SECONDS, TREASURY_ACCOUNT_ID @@ -66,7 +66,7 @@ import * as flags from '../flags.js' import { type SoloLogger } from '../../core/logging.js' import type { Listr, ListrTaskWrapper } from 'listr2' import { type NodeAlias, type NodeAliases, type PodName } from '../../types/aliases.js' -import { NodeStatusCodes, NodeStatusEnums } from '../../core/enumerations.js' +import { NodeStatusCodes, NodeStatusEnums, NodeSubcommandType } from '../../core/enumerations.js' import * as x509 from '@peculiar/x509' import { type NodeCommand } from './index.js' import type { NodeDeleteConfigClass, NodeRefreshConfigClass, NodeUpdateConfigClass } from './configs.js' @@ -88,9 +88,11 @@ export class NodeCommandTasks { private readonly prepareValuesFiles: any - constructor (opts: { logger: SoloLogger; accountManager: AccountManager; configManager: ConfigManager, - k8: K8, platformInstaller: PlatformInstaller, keyManager: KeyManager, profileManager: ProfileManager, - chartManager: ChartManager, certificateManager: CertificateManager, parent: NodeCommand} + constructor (opts: { + logger: SoloLogger; accountManager: AccountManager; configManager: ConfigManager, + k8: K8, platformInstaller: PlatformInstaller, keyManager: KeyManager, profileManager: ProfileManager, + chartManager: ChartManager, certificateManager: CertificateManager, parent: NodeCommand + } ) { if (!opts || !opts.accountManager) throw new IllegalArgumentError('An instance of core/AccountManager is required', opts.accountManager as any) if (!opts || !opts.configManager) throw new Error('An instance of core/ConfigManager is required') @@ -163,12 +165,12 @@ export class NodeCommandTasks { if (start === 0) { fileTransaction = new FileUpdateTransaction() - .setFileId(constants.UPGRADE_FILE_ID) - .setContents(zipBytesChunk) + .setFileId(constants.UPGRADE_FILE_ID) + .setContents(zipBytesChunk) } else { fileTransaction = new FileAppendTransaction() - .setFileId(constants.UPGRADE_FILE_ID) - .setContents(zipBytesChunk) + .setFileId(constants.UPGRADE_FILE_ID) + .setContents(zipBytesChunk) } const resp = await fileTransaction.execute(nodeClient) const receipt = await resp.getReceipt(nodeClient) @@ -213,6 +215,7 @@ export class NodeCommandTasks { throw new SoloError(`local build path does not exist: ${localDataLibBuildPath}`) } + const self = this subTasks.push({ title: `Copy local build to Node: ${chalk.yellow(nodeAlias)} from ${localDataLibBuildPath}`, task: async () => { @@ -221,12 +224,12 @@ export class NodeCommandTasks { return !(path.includes('data/keys') || path.includes( 'data/config')) } - await this.k8.copyTo(podName, constants.ROOT_CONTAINER, localDataLibBuildPath, + await self.k8.copyTo(podName, constants.ROOT_CONTAINER, localDataLibBuildPath, `${constants.HEDERA_HAPI_PATH}`, filterFunction) const testJsonFiles: string[] = this.configManager.getFlag(flags.appConfig)!.split(',') for (const jsonFile of testJsonFiles) { if (fs.existsSync(jsonFile)) { - await this.k8.copyTo(podName, constants.ROOT_CONTAINER, jsonFile, `${constants.HEDERA_HAPI_PATH}`) + await self.k8.copyTo(podName, constants.ROOT_CONTAINER, jsonFile, `${constants.HEDERA_HAPI_PATH}`) } } } @@ -240,7 +243,7 @@ export class NodeCommandTasks { } _fetchPlatformSoftware (nodeAliases: NodeAliases, podNames: Record, releaseTag: string, - task: ListrTaskWrapper, platformInstaller: PlatformInstaller + task: ListrTaskWrapper, platformInstaller: PlatformInstaller ) { const subTasks = [] for (const nodeAlias of nodeAliases) { @@ -248,7 +251,7 @@ export class NodeCommandTasks { subTasks.push({ title: `Update node: ${chalk.yellow(nodeAlias)} [ platformVersion = ${releaseTag} ]`, task: async () => - await platformInstaller.fetchPlatform(podName, releaseTag) + await platformInstaller.fetchPlatform(podName, releaseTag) }) } @@ -284,10 +287,10 @@ export class NodeCommandTasks { } async _checkNetworkNodeActiveness (namespace: string, nodeAlias: NodeAlias, task: ListrTaskWrapper, - title: string, index: number, status = NodeStatusCodes.ACTIVE, - maxAttempts = constants.NETWORK_NODE_ACTIVE_MAX_ATTEMPTS, - delay = constants.NETWORK_NODE_ACTIVE_DELAY, - timeout = constants.NETWORK_NODE_ACTIVE_TIMEOUT + title: string, index: number, status = NodeStatusCodes.ACTIVE, + maxAttempts = constants.NETWORK_NODE_ACTIVE_MAX_ATTEMPTS, + delay = constants.NETWORK_NODE_ACTIVE_DELAY, + timeout = constants.NETWORK_NODE_ACTIVE_TIMEOUT ) { nodeAlias = nodeAlias.trim() as NodeAlias const podName = Templates.renderNetworkPodName(nodeAlias) @@ -319,8 +322,8 @@ export class NodeCommandTasks { const text = await response.text() const statusLine = text - .split('\n') - .find(line => line.startsWith('platform_PlatformStatus')) + .split('\n') + .find(line => line.startsWith('platform_PlatformStatus')) if (!statusLine) { task.title = `${title} - status ${chalk.yellow('STARTING')}, attempt: ${chalk.blueBright(`${attempt}/${maxAttempts}`)}` @@ -342,7 +345,8 @@ export class NodeCommandTasks { task.title = `${title} - status ${chalk.yellow(NodeStatusEnums[statusNumber])}, attempt: ${chalk.blueBright(`${attempt}/${maxAttempts}`)}` } clearTimeout(timeoutId) - } catch {} // Catch all guard and fetch errors + } catch { + } // Catch all guard and fetch errors attempt++ clearTimeout(timeoutId) @@ -368,8 +372,8 @@ export class NodeCommandTasks { subTasks.push({ title: `Check proxy for node: ${chalk.yellow(nodeAlias)}`, task: async () => await this.k8.waitForPodReady( - [`app=haproxy-${nodeAlias}`, 'solo.hedera.com/type=haproxy'], - 1, constants.NETWORK_PROXY_MAX_ATTEMPTS, constants.NETWORK_PROXY_DELAY) + [`app=haproxy-${nodeAlias}`, 'solo.hedera.com/type=haproxy'], + 1, constants.NETWORK_PROXY_MAX_ATTEMPTS, constants.NETWORK_PROXY_DELAY) }) } @@ -387,10 +391,11 @@ export class NodeCommandTasks { * When generating a single key the alias in config.nodeAlias is used */ _generateGossipKeys (generateMultiple: boolean) { + const self = this return new Task('Generate gossip keys', (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config const nodeAliases = generateMultiple ? config.nodeAliases : [config.nodeAlias] - const subTasks = this.keyManager.taskGenerateGossipKeys(nodeAliases, config.keysDir, config.curDate) + const subTasks = self.keyManager.taskGenerateGossipKeys(nodeAliases, config.keysDir, config.curDate) // set up the sub-tasks return task.newListr(subTasks, { concurrent: false, @@ -407,10 +412,11 @@ export class NodeCommandTasks { * When generating a single key the alias in config.nodeAlias is used */ _generateGrpcTlsKeys (generateMultiple: boolean) { + const self = this return new Task('Generate gRPC TLS Keys', (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config const nodeAliases = generateMultiple ? config.nodeAliases : [config.nodeAlias] - const subTasks = this.keyManager.taskGenerateTLSKeys(nodeAliases, config.keysDir, config.curDate) + const subTasks = self.keyManager.taskGenerateTLSKeys(nodeAliases, config.keysDir, config.curDate) // set up the sub-tasks return task.newListr(subTasks, { concurrent: true, @@ -423,16 +429,17 @@ export class NodeCommandTasks { } copyGrpcTlsCertificates () { + const self = this return new Task('Copy gRPC TLS Certificates', - (ctx: { config: NodeAddConfigClass }, parentTask: ListrTaskWrapper) => - this.certificateManager.buildCopyTlsCertificatesTasks( - parentTask, - ctx.config.grpcTlsCertificatePath, - ctx.config.grpcWebTlsCertificatePath, - ctx.config.grpcTlsKeyPath, - ctx.config.grpcWebTlsKeyPath - ), - (ctx: any) => !ctx.config.grpcTlsCertificatePath && !ctx.config.grpcWebTlsCertificatePath + (ctx: { config: NodeAddConfigClass }, parentTask: ListrTaskWrapper) => + self.certificateManager.buildCopyTlsCertificatesTasks( + parentTask, + ctx.config.grpcTlsCertificatePath, + ctx.config.grpcWebTlsCertificatePath, + ctx.config.grpcTlsKeyPath, + ctx.config.grpcWebTlsKeyPath + ), + (ctx: any) => !ctx.config.grpcTlsCertificatePath && !ctx.config.grpcWebTlsCertificatePath ) } @@ -445,7 +452,7 @@ export class NodeCommandTasks { return (new Uint8Array(decodedDers[0])) } - async _addStake (namespace: string, accountId: string, nodeAlias: NodeAlias) { + async _addStake (namespace: string, accountId: string, nodeAlias: NodeAlias, stakeAmount: number = HEDERA_NODE_DEFAULT_STAKE_AMOUNT) { try { await this.accountManager.loadNodeClient(namespace) const client = this.accountManager._nodeClient @@ -455,24 +462,24 @@ export class NodeCommandTasks { // check balance const treasuryBalance = await new AccountBalanceQuery() - .setAccountId(TREASURY_ACCOUNT_ID) - .execute(client) + .setAccountId(TREASURY_ACCOUNT_ID) + .execute(client) this.logger.debug(`Account ${TREASURY_ACCOUNT_ID} balance: ${treasuryBalance.hbars}`) // get some initial balance - await this.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, accountId, HEDERA_NODE_DEFAULT_STAKE_AMOUNT + 1) + await this.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, accountId, stakeAmount) // check balance const balance = await new AccountBalanceQuery() - .setAccountId(accountId) - .execute(client) + .setAccountId(accountId) + .execute(client) this.logger.debug(`Account ${accountId} balance: ${balance.hbars}`) // Create the transaction const transaction = new AccountUpdateTransaction() - .setAccountId(accountId) - .setStakedNodeId(Templates.nodeIdFromNodeAlias(nodeAlias) - 1) - .freezeWith(client) + .setAccountId(accountId) + .setStakedNodeId(Templates.nodeIdFromNodeAlias(nodeAlias) - 1) + .freezeWith(client) // Sign the transaction with the account's private key const signTx = await transaction.sign(treasuryPrivateKey) @@ -492,10 +499,11 @@ export class NodeCommandTasks { } prepareUpgradeZip () { + const self = this return new Task('Prepare upgrade zip file for node upgrade process', async (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config - ctx.upgradeZipFile = await this._prepareUpgradeZip(config.stagingDir) - ctx.upgradeZipHash = await this._uploadUpgradeZip(ctx.upgradeZipFile, config.nodeClient) + ctx.upgradeZipFile = await self._prepareUpgradeZip(config.stagingDir) + ctx.upgradeZipHash = await self._uploadUpgradeZip(ctx.upgradeZipFile, config.nodeClient) }) } @@ -507,6 +515,7 @@ export class NodeCommandTasks { } checkExistingNodesStakedAmount () { + const self = this return new Task('Check existing nodes staked amount', async (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config @@ -514,79 +523,81 @@ export class NodeCommandTasks { const accountMap = getNodeAccountMap(config.existingNodeAliases) for (const nodeAlias of config.existingNodeAliases) { const accountId = accountMap.get(nodeAlias) - await this.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, accountId, 1) + await self.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, accountId, 1) } }) } sendPrepareUpgradeTransaction (): Task { + const self = this return new Task('Send prepare upgrade transaction', async (ctx: any, task: ListrTaskWrapper) => { const { upgradeZipHash } = ctx const { nodeClient, freezeAdminPrivateKey } = ctx.config try { // query the balance const balance = await new AccountBalanceQuery() - .setAccountId(FREEZE_ADMIN_ACCOUNT) - .execute(nodeClient) - this.logger.debug(`Freeze admin account balance: ${balance.hbars}`) + .setAccountId(FREEZE_ADMIN_ACCOUNT) + .execute(nodeClient) + self.logger.debug(`Freeze admin account balance: ${balance.hbars}`) // transfer some tiny amount to the freeze admin account - await this.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, FREEZE_ADMIN_ACCOUNT, 100000) + await self.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, FREEZE_ADMIN_ACCOUNT, 100000) // set operator of freeze transaction as freeze admin account nodeClient.setOperator(FREEZE_ADMIN_ACCOUNT, freezeAdminPrivateKey) const prepareUpgradeTx = await new FreezeTransaction() - .setFreezeType(FreezeType.PrepareUpgrade) - .setFileId(constants.UPGRADE_FILE_ID) - .setFileHash(upgradeZipHash) - .freezeWith(nodeClient) - .execute(nodeClient) + .setFreezeType(FreezeType.PrepareUpgrade) + .setFileId(constants.UPGRADE_FILE_ID) + .setFileHash(upgradeZipHash) + .freezeWith(nodeClient) + .execute(nodeClient) const prepareUpgradeReceipt = await prepareUpgradeTx.getReceipt(nodeClient) - this.logger.debug( - `sent prepare upgrade transaction [id: ${prepareUpgradeTx.transactionId.toString()}]`, - prepareUpgradeReceipt.status.toString() + self.logger.debug( + `sent prepare upgrade transaction [id: ${prepareUpgradeTx.transactionId.toString()}]`, + prepareUpgradeReceipt.status.toString() ) } catch (e: Error | any) { - this.logger.error(`Error in prepare upgrade: ${e.message}`, e) + self.logger.error(`Error in prepare upgrade: ${e.message}`, e) throw new SoloError(`Error in prepare upgrade: ${e.message}`, e) } }) } sendFreezeUpgradeTransaction (): Task { + const self = this return new Task('Send freeze upgrade transaction', async (ctx: any, task: ListrTaskWrapper) => { const { upgradeZipHash } = ctx const { freezeAdminPrivateKey, nodeClient } = ctx.config try { const futureDate = new Date() - this.logger.debug(`Current time: ${futureDate}`) + self.logger.debug(`Current time: ${futureDate}`) futureDate.setTime(futureDate.getTime() + 5000) // 5 seconds in the future - this.logger.debug(`Freeze time: ${futureDate}`) + self.logger.debug(`Freeze time: ${futureDate}`) // query the balance const balance = await new AccountBalanceQuery() - .setAccountId(FREEZE_ADMIN_ACCOUNT) - .execute(nodeClient) - this.logger.debug(`Freeze admin account balance: ${balance.hbars}`) + .setAccountId(FREEZE_ADMIN_ACCOUNT) + .execute(nodeClient) + self.logger.debug(`Freeze admin account balance: ${balance.hbars}`) nodeClient.setOperator(FREEZE_ADMIN_ACCOUNT, freezeAdminPrivateKey) const freezeUpgradeTx = await new FreezeTransaction() - .setFreezeType(FreezeType.FreezeUpgrade) - .setStartTimestamp(Timestamp.fromDate(futureDate)) - .setFileId(constants.UPGRADE_FILE_ID) - .setFileHash(upgradeZipHash) - .freezeWith(nodeClient) - .execute(nodeClient) + .setFreezeType(FreezeType.FreezeUpgrade) + .setStartTimestamp(Timestamp.fromDate(futureDate)) + .setFileId(constants.UPGRADE_FILE_ID) + .setFileHash(upgradeZipHash) + .freezeWith(nodeClient) + .execute(nodeClient) const freezeUpgradeReceipt = await freezeUpgradeTx.getReceipt(nodeClient) - this.logger.debug(`Upgrade frozen with transaction id: ${freezeUpgradeTx.transactionId.toString()}`, - freezeUpgradeReceipt.status.toString()) + self.logger.debug(`Upgrade frozen with transaction id: ${freezeUpgradeTx.transactionId.toString()}`, + freezeUpgradeReceipt.status.toString()) } catch (e: Error | any) { - this.logger.error(`Error in freeze upgrade: ${e.message}`, e) + self.logger.error(`Error in freeze upgrade: ${e.message}`, e) throw new SoloError(`Error in freeze upgrade: ${e.message}`, e) } }) @@ -594,26 +605,30 @@ export class NodeCommandTasks { /** Download generated config files and key files from the network node */ downloadNodeGeneratedFiles (): Task { + const self = this return new Task('Download generated files from an existing node', async (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config - const node1FullyQualifiedPodName = Templates.renderNetworkPodName(config.existingNodeAliases[0]) + // don't try to download from the same node we are deleting, it won't work + const nodeAlias = ctx.config.nodeAlias === config.existingNodeAliases[0] ? config.existingNodeAliases[1] : config.existingNodeAliases[0] + + const nodeFullyQualifiedPodName = Templates.renderNetworkPodName(nodeAlias) // copy the config.txt file from the node1 upgrade directory - await this.k8.copyFrom(node1FullyQualifiedPodName, constants.ROOT_CONTAINER, `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/config.txt`, config.stagingDir) + await self.k8.copyFrom(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/config.txt`, config.stagingDir) // if directory data/upgrade/current/data/keys does not exist then use data/upgrade/current let keyDir = `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/data/keys` - if (!await this.k8.hasDir(node1FullyQualifiedPodName, constants.ROOT_CONTAINER, keyDir)) { + if (!await self.k8.hasDir(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, keyDir)) { keyDir = `${constants.HEDERA_HAPI_PATH}/data/upgrade/current` } - const signedKeyFiles = (await this.k8.listDir(node1FullyQualifiedPodName, constants.ROOT_CONTAINER, keyDir)).filter(file => file.name.startsWith(constants.SIGNING_KEY_PREFIX)) - await this.k8.execContainer(node1FullyQualifiedPodName, constants.ROOT_CONTAINER, ['bash', '-c', `mkdir -p ${constants.HEDERA_HAPI_PATH}/data/keys_backup && cp -r ${keyDir} ${constants.HEDERA_HAPI_PATH}/data/keys_backup/`]) + const signedKeyFiles = (await self.k8.listDir(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, keyDir)).filter(file => file.name.startsWith(constants.SIGNING_KEY_PREFIX)) + await self.k8.execContainer(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, ['bash', '-c', `mkdir -p ${constants.HEDERA_HAPI_PATH}/data/keys_backup && cp -r ${keyDir} ${constants.HEDERA_HAPI_PATH}/data/keys_backup/`]) for (const signedKeyFile of signedKeyFiles) { - await this.k8.copyFrom(node1FullyQualifiedPodName, constants.ROOT_CONTAINER, `${keyDir}/${signedKeyFile.name}`, `${config.keysDir}`) + await self.k8.copyFrom(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, `${keyDir}/${signedKeyFile.name}`, `${config.keysDir}`) } - if (await this.k8.hasFile(node1FullyQualifiedPodName, constants.ROOT_CONTAINER, `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/application.properties`)) { - await this.k8.copyFrom(node1FullyQualifiedPodName, constants.ROOT_CONTAINER, `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/application.properties`, `${config.stagingDir}/templates`) + if (await self.k8.hasFile(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/application.properties`)) { + await self.k8.copyFrom(nodeFullyQualifiedPodName, constants.ROOT_CONTAINER, `${constants.HEDERA_HAPI_PATH}/data/upgrade/current/application.properties`, `${config.stagingDir}/templates`) } }) } @@ -624,11 +639,12 @@ export class NodeCommandTasks { ctx.config.podNames = {} const subTasks = [] + const self = this for (const nodeAlias of nodeAliases) { subTasks.push({ title: `Check network pod: ${chalk.yellow(nodeAlias)}`, task: async (ctx: any) => { - ctx.config.podNames[nodeAlias] = await this.checkNetworkNodePod(ctx.config.namespace, nodeAlias) + ctx.config.podNames[nodeAlias] = await self.checkNetworkNodePod(ctx.config.namespace, nodeAlias) } }) } @@ -644,15 +660,15 @@ export class NodeCommandTasks { /** Check if the network node pod is running */ async checkNetworkNodePod (namespace: string, nodeAlias: NodeAlias, - maxAttempts = constants.PODS_RUNNING_MAX_ATTEMPTS, - delay = constants.PODS_RUNNING_DELAY) { + maxAttempts = constants.PODS_RUNNING_MAX_ATTEMPTS, + delay = constants.PODS_RUNNING_DELAY) { nodeAlias = nodeAlias.trim() as NodeAlias const podName = Templates.renderNetworkPodName(nodeAlias) try { await this.k8.waitForPods([constants.POD_PHASE_RUNNING], [ - 'solo.hedera.com/type=network-node', - `solo.hedera.com/node-name=${nodeAlias}` + `solo.hedera.com/node-name=${nodeAlias}`, + 'solo.hedera.com/type=network-node' ], 1, maxAttempts, delay) return podName @@ -662,32 +678,35 @@ export class NodeCommandTasks { } identifyExistingNodes () { + const self = this return new Task('Identify existing network nodes', async (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config config.existingNodeAliases = [] - config.serviceMap = await this.accountManager.getNodeServiceMap(config.namespace) + config.serviceMap = await self.accountManager.getNodeServiceMap(config.namespace) for (const networkNodeServices of config.serviceMap.values()) { config.existingNodeAliases.push(networkNodeServices.nodeAlias) } config.allNodeAliases = [...config.existingNodeAliases] - return this.taskCheckNetworkNodePods(ctx, task, config.existingNodeAliases) + return self.taskCheckNetworkNodePods(ctx, task, config.existingNodeAliases) }) } identifyNetworkPods () { + const self = this return new Task('Identify network pods', (ctx: any, task: ListrTaskWrapper) => { - return this.taskCheckNetworkNodePods(ctx, task, ctx.config.nodeAliases) + return self.taskCheckNetworkNodePods(ctx, task, ctx.config.nodeAliases) }) } fetchPlatformSoftware (aliasesField: string) { + const self = this return new Task('Fetch platform software into network nodes', (ctx: any, task: ListrTaskWrapper) => { const { podNames, releaseTag, localBuildPath } = ctx.config if (localBuildPath !== '') { - return this._uploadPlatformSoftware(ctx.config[aliasesField], podNames, task, localBuildPath) + return self._uploadPlatformSoftware(ctx.config[aliasesField], podNames, task, localBuildPath) } - return this._fetchPlatformSoftware(ctx.config[aliasesField], podNames, releaseTag, task, this.platformInstaller) + return self._fetchPlatformSoftware(ctx.config[aliasesField], podNames, releaseTag, task, this.platformInstaller) }) } @@ -695,7 +714,7 @@ export class NodeCommandTasks { populateServiceMap () { return new Task('Populate serviceMap', async (ctx: any, task: ListrTaskWrapper) => { ctx.config.serviceMap = await this.accountManager.getNodeServiceMap( - ctx.config.namespace) + ctx.config.namespace) ctx.config.podNames[ctx.config.nodeAlias] = ctx.config.serviceMap.get(ctx.config.nodeAlias).nodePodName }) } @@ -708,7 +727,7 @@ export class NodeCommandTasks { subTasks.push({ title: `Node: ${chalk.yellow(nodeAlias)}`, task: () => - this.platformInstaller.taskSetup(podName) + this.platformInstaller.taskSetup(podName) }) } @@ -814,42 +833,58 @@ export class NodeCommandTasks { } // Update account manager and transfer hbar for staking purpose - triggerStakeWeightCalculate () { + triggerStakeWeightCalculate (transactionType : NodeSubcommandType) { + const self = this return new Task('Trigger stake weight calculate', async (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config - this.logger.info('sleep 60 seconds for the handler to be able to trigger the network node stake weight recalculate') + self.logger.info('sleep 60 seconds for the handler to be able to trigger the network node stake weight recalculate') await sleep(60 * SECONDS) const accountMap = getNodeAccountMap(config.allNodeAliases) - if (config.newAccountNumber) { - // update map with current account ids - accountMap.set(config.nodeAlias, config.newAccountNumber) + switch (transactionType) { + case NodeSubcommandType.ADD: + break + case NodeSubcommandType.UPDATE: + if (config.newAccountNumber) { + // update map with current account ids + accountMap.set(config.nodeAlias, config.newAccountNumber) - // update _nodeClient with the new service map since one of the account number has changed - await this.accountManager.refreshNodeClient(config.namespace) + // update _nodeClient with the new service map since one of the account number has changed + await self.accountManager.refreshNodeClient(config.namespace) + } + break + case NodeSubcommandType.DELETE: + if (config.nodeAlias) { + accountMap.delete(config.nodeAlias) + } } // send some write transactions to invoke the handler that will trigger the stake weight recalculate - for (const nodeAlias of config.allNodeAliases) { + for (const nodeAlias of accountMap.keys()) { const accountId = accountMap.get(nodeAlias) config.nodeClient.setOperator(TREASURY_ACCOUNT_ID, config.treasuryKey) - await this.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, accountId, 1) + await self.accountManager.transferAmount(constants.TREASURY_ACCOUNT_ID, accountId, 1) } }) } addNodeStakes () { + const self = this // @ts-ignore return new Task('Add node stakes', (ctx: any, task: ListrTaskWrapper) => { if (ctx.config.app === '' || ctx.config.app === constants.HEDERA_APP_NAME) { const subTasks = [] const accountMap = getNodeAccountMap(ctx.config.nodeAliases) + const stakeAmountParsed = ctx.config.stakeAmount ? splitFlagInput(ctx.config.stakeAmount) : [] + let nodeIndex = 0 for (const nodeAlias of ctx.config.nodeAliases) { const accountId = accountMap.get(nodeAlias) + const stakeAmount = stakeAmountParsed.length > 0 ? stakeAmountParsed[nodeIndex] : HEDERA_NODE_DEFAULT_STAKE_AMOUNT subTasks.push({ title: `Adding stake for node: ${chalk.yellow(nodeAlias)}`, - task: async () => await this._addStake(ctx.config.namespace, accountId, nodeAlias) + task: async () => await self._addStake(ctx.config.namespace, accountId, nodeAlias, +stakeAmount) }) + nodeIndex++ } // set up the sub-tasks @@ -864,7 +899,9 @@ export class NodeCommandTasks { } stakeNewNode () { + const self = this return new Task('Stake new node', async (ctx: any, task: ListrTaskWrapper) => { + await self.accountManager.refreshNodeClient(ctx.config.namespace, ctx.config.nodeAlias) await this._addStake(ctx.config.namespace, ctx.newNode.accountId, ctx.config.nodeAlias) }) } @@ -1055,37 +1092,39 @@ export class NodeCommandTasks { } sendNodeUpdateTransaction () { + const self = this return new Task('Send node update transaction', async (ctx: any, task: ListrTaskWrapper) => { const config: NodeUpdateConfigClass = ctx.config const nodeId = Templates.nodeIdFromNodeAlias(config.nodeAlias) - 1 - this.logger.info(`nodeId: ${nodeId}`) - this.logger.info(`config.newAccountNumber: ${config.newAccountNumber}`) + self.logger.info(`nodeId: ${nodeId}, config.newAccountNumber: ${config.newAccountNumber}`) + await self.accountManager.refreshNodeClient(config.namespace, config.nodeAlias) + config.nodeClient = await this.accountManager.loadNodeClient(config.namespace) try { const nodeUpdateTx = new NodeUpdateTransaction().setNodeId(nodeId) if (config.tlsPublicKey && config.tlsPrivateKey) { - this.logger.info(`config.tlsPublicKey: ${config.tlsPublicKey}`) - const tlsCertDer = this._loadPermCertificate(config.tlsPublicKey) + self.logger.info(`config.tlsPublicKey: ${config.tlsPublicKey}`) + const tlsCertDer = self._loadPermCertificate(config.tlsPublicKey) const tlsCertHash = crypto.createHash('sha384').update(tlsCertDer).digest() nodeUpdateTx.setCertificateHash(tlsCertHash) const publicKeyFile = Templates.renderTLSPemPublicKeyFile(config.nodeAlias) const privateKeyFile = Templates.renderTLSPemPrivateKeyFile(config.nodeAlias) - renameAndCopyFile(config.tlsPublicKey, publicKeyFile, config.keysDir, this.logger) - renameAndCopyFile(config.tlsPrivateKey, privateKeyFile, config.keysDir, this.logger) + renameAndCopyFile(config.tlsPublicKey, publicKeyFile, config.keysDir, self.logger) + renameAndCopyFile(config.tlsPrivateKey, privateKeyFile, config.keysDir, self.logger) } if (config.gossipPublicKey && config.gossipPrivateKey) { - this.logger.info(`config.gossipPublicKey: ${config.gossipPublicKey}`) - const signingCertDer = this._loadPermCertificate(config.gossipPublicKey) + self.logger.info(`config.gossipPublicKey: ${config.gossipPublicKey}`) + const signingCertDer = self._loadPermCertificate(config.gossipPublicKey) nodeUpdateTx.setGossipCaCertificate(signingCertDer) const publicKeyFile = Templates.renderGossipPemPublicKeyFile(config.nodeAlias) const privateKeyFile = Templates.renderGossipPemPrivateKeyFile(config.nodeAlias) - renameAndCopyFile(config.gossipPublicKey, publicKeyFile, config.keysDir, this.logger) - renameAndCopyFile(config.gossipPrivateKey, privateKeyFile, config.keysDir, this.logger) + renameAndCopyFile(config.gossipPublicKey, publicKeyFile, config.keysDir, self.logger) + renameAndCopyFile(config.gossipPrivateKey, privateKeyFile, config.keysDir, self.logger) } if (config.newAccountNumber) { @@ -1106,10 +1145,10 @@ export class NodeCommandTasks { const signedTx = await nodeUpdateTx.sign(config.adminKey) const txResp = await signedTx.execute(config.nodeClient) const nodeUpdateReceipt = await txResp.getReceipt(config.nodeClient) - this.logger.debug(`NodeUpdateReceipt: ${nodeUpdateReceipt.toString()}`) + self.logger.debug(`NodeUpdateReceipt: ${nodeUpdateReceipt.toString()}`) } catch (e) { - this.logger.error(`Error updating node to network: ${e.message}`, e) - this.logger.error(e.stack) + self.logger.error(`Error updating node to network: ${e.message}`, e) + self.logger.error(e.stack) throw new SoloError(`Error updating node to network: ${e.message}`, e) } }) @@ -1127,13 +1166,14 @@ export class NodeCommandTasks { }) } - updateChartWithConfigMap (title: string, skip: Function | boolean = false) { + updateChartWithConfigMap (title: string, transactionType: NodeSubcommandType, skip: Function | boolean = false) { + const self = this return new Task(title, async (ctx: any, task: ListrTaskWrapper) => { // Prepare parameter and update the network node chart const config = ctx.config if (!config.serviceMap) { - config.serviceMap = await this.accountManager.getNodeServiceMap(config.namespace) + config.serviceMap = await self.accountManager.getNodeServiceMap(config.namespace) } const index = config.existingNodeAliases.length @@ -1141,28 +1181,30 @@ export class NodeCommandTasks { let valuesArg = '' for (let i = 0; i < index; i++) { - if ((config.newAccountNumber && i !== nodeId) || !config.newAccountNumber) { // for the case of updating node - valuesArg += ` --set "hedera.nodes[${i}].accountId=${config.serviceMap.get(config.existingNodeAliases[i]).accountId}" --set "hedera.nodes[${i}].name=${config.existingNodeAliases[i]}"` - } else { + if (transactionType === NodeSubcommandType.UPDATE && config.newAccountNumber && i === nodeId) { // for the case of updating node // use new account number for this node id valuesArg += ` --set "hedera.nodes[${i}].accountId=${config.newAccountNumber}" --set "hedera.nodes[${i}].name=${config.existingNodeAliases[i]}"` + } else if (transactionType !== NodeSubcommandType.DELETE || i !== nodeId) { // for the case of deleting node + valuesArg += ` --set "hedera.nodes[${i}].accountId=${config.serviceMap.get(config.existingNodeAliases[i]).accountId}" --set "hedera.nodes[${i}].name=${config.existingNodeAliases[i]}"` + } else if (transactionType === NodeSubcommandType.DELETE && i === nodeId) { + valuesArg += ` --set "hedera.nodes[${i}].accountId=${IGNORED_NODE_ACCOUNT_ID}" --set "hedera.nodes[${i}].name=${config.existingNodeAliases[i]}"` } } // for the case of adding new node - if (ctx.newNode && ctx.newNode.accountId) { + if (transactionType === NodeSubcommandType.ADD && ctx.newNode && ctx.newNode.accountId) { valuesArg += ` --set "hedera.nodes[${index}].accountId=${ctx.newNode.accountId}" --set "hedera.nodes[${index}].name=${ctx.newNode.name}"` } - const profileValuesFile = await this.profileManager.prepareValuesForNodeAdd( + const profileValuesFile = await self.profileManager.prepareValuesForNodeAdd( path.join(config.stagingDir, 'config.txt'), path.join(config.stagingDir, 'templates', 'application.properties')) if (profileValuesFile) { - valuesArg += this.prepareValuesFiles(profileValuesFile) + valuesArg += self.prepareValuesFiles(profileValuesFile) } valuesArg = addDebugOptions(valuesArg, config.debugNodeAlias) - await this.chartManager.upgrade( + await self.chartManager.upgrade( config.namespace, constants.SOLO_DEPLOYMENT_CHART, constants.SOLO_TESTING_CHART_URL + constants.SOLO_DEPLOYMENT_CHART, valuesArg, @@ -1202,7 +1244,7 @@ export class NodeCommandTasks { return new Task('Kill nodes', async (ctx: any, task: ListrTaskWrapper) => { const config = ctx.config for (const service of config.serviceMap.values()) { - await this.k8.kubeClient.deleteNamespacedPod(service.nodePodName, config.namespace, undefined, undefined, 1) + await this.k8.killPod(service.nodePodName, config.namespace) } }) } @@ -1214,10 +1256,8 @@ export class NodeCommandTasks { config.serviceMap = await this.accountManager.getNodeServiceMap(config.namespace) for (const service of config.serviceMap.values()) { - await this.k8.kubeClient.deleteNamespacedPod(service.nodePodName, config.namespace, undefined, undefined, 1) + await this.k8.killPod(service.nodePodName, config.namespace) } - this.logger.info('sleep for 15 seconds to give time for pods to finish terminating') - await sleep(15 * SECONDS) // again, the pod names will change after the pods are killed config.serviceMap = await this.accountManager.getNodeServiceMap(config.namespace) @@ -1238,8 +1278,8 @@ export class NodeCommandTasks { title: `Check Node: ${chalk.yellow(nodeAlias)}`, task: async () => await this.k8.waitForPods([constants.POD_PHASE_RUNNING], [ + `solo.hedera.com/node-name=${nodeAlias}`, 'solo.hedera.com/type=network-node', - `solo.hedera.com/node-name=${nodeAlias}` ], 1, constants.PODS_RUNNING_MAX_ATTEMPTS, constants.PODS_RUNNING_DELAY) // timeout 15 minutes }) } diff --git a/src/core/account_manager.ts b/src/core/account_manager.ts index 0c855dfe9..a3e3c5004 100644 --- a/src/core/account_manager.ts +++ b/src/core/account_manager.ts @@ -46,6 +46,7 @@ import { type SoloLogger } from './logging.js' import { type K8 } from './k8.js' import { type AccountIdWithKeyPairObject, type ExtendedNetServer } from '../types/index.js' import { type NodeAlias, type PodName } from '../types/aliases.js' +import { IGNORED_NODE_ACCOUNT_ID } from './constants.js' const REASON_FAILED_TO_GET_KEYS = 'failed to get keys for accountId' const REASON_SKIPPED = 'skipped since it does not have a genesis key' @@ -151,7 +152,9 @@ export class AccountManager { * @param namespace - the namespace of the network */ async loadNodeClient (namespace: string) { + this.logger.debug(`loading node client: [!this._nodeClient=${!this._nodeClient}, this._nodeClient.isClientShutDown=${this._nodeClient?.isClientShutDown}]`) if (!this._nodeClient || this._nodeClient.isClientShutDown) { + this.logger.debug(`refreshing node client: [!this._nodeClient=${!this._nodeClient}, this._nodeClient.isClientShutDown=${this._nodeClient?.isClientShutDown}]`) await this.refreshNodeClient(namespace) } @@ -161,14 +164,15 @@ export class AccountManager { /** * loads and initializes the Node Client * @param namespace - the namespace of the network + * @param skipNodeAlias - the node alias to skip */ - async refreshNodeClient (namespace: string) { + async refreshNodeClient (namespace: string, skipNodeAlias? : NodeAlias) { await this.close() const treasuryAccountInfo = await this.getTreasuryAccountKeys(namespace) const networkNodeServicesMap = await this.getNodeServiceMap(namespace) this._nodeClient = await this._getNodeClient(namespace, - networkNodeServicesMap, treasuryAccountInfo.accountId, treasuryAccountInfo.privateKey) + networkNodeServicesMap, treasuryAccountInfo.accountId, treasuryAccountInfo.privateKey, skipNodeAlias) } /** @@ -213,19 +217,16 @@ export class AccountManager { * @param operatorKey - the private key of the operator of the transactions * @returns a node client that can be used to call transactions */ - async _getNodeClient (namespace: string, networkNodeServicesMap: Map, operatorId: string, - operatorKey: string, useFirstNodeOnly = true) { + async _getNodeClient (namespace: string, networkNodeServicesMap: Map, operatorId: string, operatorKey: string, skipNodeAlias: string) { let nodes = {} try { let localPort = constants.LOCAL_NODE_START_PORT for (const networkNodeService of networkNodeServicesMap.values()) { - const addlNode = await this.configureNodeAccess(networkNodeService, localPort, networkNodeServicesMap.size) - nodes = { ...nodes, ...addlNode } - localPort++ - - if (useFirstNodeOnly) { - break + if (networkNodeService.accountId !== IGNORED_NODE_ACCOUNT_ID && networkNodeService.nodeAlias !== skipNodeAlias) { + const addlNode = await this.configureNodeAccess(networkNodeService, localPort, networkNodeServicesMap.size) + nodes = { ...nodes, ...addlNode } + localPort++ } } @@ -637,6 +638,7 @@ export class AccountManager { const transaction = new TransferTransaction() .addHbarTransfer(fromAccountId, new Hbar(-1 * hbarAmount)) .addHbarTransfer(toAccountId, new Hbar(hbarAmount)) + .freezeWith(this._nodeClient) // @ts-ignore const txResponse = await transaction.execute(this._nodeClient) diff --git a/src/core/config_manager.ts b/src/core/config_manager.ts index cde1d143f..09aef5f45 100644 --- a/src/core/config_manager.ts +++ b/src/core/config_manager.ts @@ -85,12 +85,10 @@ export class ConfigManager { this.logger.debug(`Resolving directory path for '${flag.name}': ${val}, to: ${paths.resolve(val)}, note: ~/ is not supported`) val = paths.resolve(val) } - this.logger.debug(`Setting flag '${flag.name}' of type '${flag.definition.type}': ${val}`) this.config.flags[flag.name] = `${val}` // force convert to string break case 'number': - this.logger.debug(`Setting flag '${flag.name}' of type '${flag.definition.type}': ${val}`) try { if (flags.integerFlags.has(flag.name)) { this.config.flags[flag.name] = Number.parseInt(val) @@ -103,7 +101,6 @@ export class ConfigManager { break case 'boolean': - this.logger.debug(`Setting flag '${flag.name}' of type '${flag.definition.type}': ${val}`) this.config.flags[flag.name] = (val === true) || (val === 'true') // use comparison to enforce boolean value break @@ -119,6 +116,15 @@ export class ConfigManager { } this.config.updatedAt = new Date().toISOString() + let flagMessage = '' + for (const key of Object.keys(this.config.flags)) { + if (this.config.flags[key]) { + flagMessage += `${key}=${this.config.flags[key]}, ` + } + } + if (flagMessage) { + this.logger.debug(`Updated config with flags: ${flagMessage}`) + } } } diff --git a/src/core/constants.ts b/src/core/constants.ts index af485d0de..325700131 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -144,10 +144,10 @@ export const ALL_PROFILES = [PROFILE_LOCAL, PROFILE_TINY, PROFILE_SMALL, PROFILE export const DEFAULT_PROFILE_FILE = path.join(SOLO_CACHE_DIR, 'profiles', 'custom-spec.yaml') // ------ Hedera SDK Related ------ -export const NODE_CLIENT_MAX_ATTEMPTS = +process.env.NODE_CLIENT_MAX_ATTEMPTS || 60 +export const NODE_CLIENT_MAX_ATTEMPTS = +process.env.NODE_CLIENT_MAX_ATTEMPTS || 600 export const NODE_CLIENT_MIN_BACKOFF = +process.env.NODE_CLIENT_MIN_BACKOFF || 1000 export const NODE_CLIENT_MAX_BACKOFF = +process.env.NODE_CLIENT_MAX_BACKOFF || 1000 -export const NODE_CLIENT_REQUEST_TIMEOUT = +process.env.NODE_CLIENT_REQUEST_TIMEOUT || 120000 +export const NODE_CLIENT_REQUEST_TIMEOUT = +process.env.NODE_CLIENT_REQUEST_TIMEOUT || 600000 // ---- New Node Related ---- export const ENDPOINT_TYPE_IP = 'IP' @@ -180,3 +180,4 @@ export const RELAY_PODS_READY_DELAY = +process.env.RELAY_PODS_READY_DELAY || 1_0 export const DEFAULT_LOCAL_CONFIG_FILE = 'local-config.yaml' +export const IGNORED_NODE_ACCOUNT_ID = '0.0.0' diff --git a/src/core/enumerations.ts b/src/core/enumerations.ts index 3b8b2af41..f5a1ac5d0 100644 --- a/src/core/enumerations.ts +++ b/src/core/enumerations.ts @@ -51,3 +51,9 @@ export enum GrpcProxyTlsEnums { GRPC, GRPC_WEB, } + +export enum NodeSubcommandType { + ADD, + DELETE, + UPDATE, +} diff --git a/src/core/helpers.ts b/src/core/helpers.ts index c50e4e02d..e5c166743 100644 --- a/src/core/helpers.ts +++ b/src/core/helpers.ts @@ -46,7 +46,7 @@ export function parseNodeAliases (input: string): NodeAliases { export function splitFlagInput (input: string, separator = ',') { if (typeof input !== 'string') { - throw new SoloError('input is not a comma separated string') + throw new SoloError(`input [input='${input}'] is not a comma separated string`) } return input @@ -204,7 +204,9 @@ async function getNodeLog (pod: V1Pod, namespace: string, timeString: string, k8 const scriptName = 'support-zip.sh' const sourcePath = path.join(constants.RESOURCES_DIR, scriptName) // script source path await k8.copyTo(podName, ROOT_CONTAINER, sourcePath, `${HEDERA_HAPI_PATH}`) - await k8.execContainer(podName, ROOT_CONTAINER, `chmod 0755 ${HEDERA_HAPI_PATH}/${scriptName}`) + await sleep(1000) // wait for the script to sync to the file system + await k8.execContainer(podName, ROOT_CONTAINER, ['bash', '-c', `sync ${HEDERA_HAPI_PATH} && sudo chown hedera:hedera ${HEDERA_HAPI_PATH}/${scriptName}`]) + await k8.execContainer(podName, ROOT_CONTAINER, ['bash', '-c', `sudo chmod 0755 ${HEDERA_HAPI_PATH}/${scriptName}`]) await k8.execContainer(podName, ROOT_CONTAINER, `${HEDERA_HAPI_PATH}/${scriptName}`) await k8.copyFrom(podName, ROOT_CONTAINER, `${HEDERA_HAPI_PATH}/data/${podName}.zip`, targetDir) } catch (e: Error | any) { diff --git a/src/core/k8.ts b/src/core/k8.ts index df3ec38ce..c25486594 100644 --- a/src/core/k8.ts +++ b/src/core/k8.ts @@ -33,6 +33,7 @@ import type * as WebSocket from 'ws' import type { PodName } from '../types/aliases.js' import type { ExtendedNetServer, LocalContextObject } from '../types/index.js' import type * as http from 'node:http' +import { MINUTES } from './constants.js' interface TDirectoryData {directory: boolean; owner: string; group: string; size: string; modifiedAt: string; name: string} @@ -193,7 +194,13 @@ export class K8 { undefined, undefined, undefined, - fieldSelector + fieldSelector, + undefined, + undefined, + undefined, + undefined, + undefined, + 5 * MINUTES ) return this.filterItem(resp.body.items, { name }) @@ -212,7 +219,12 @@ export class K8 { undefined, undefined, undefined, - labelSelector + labelSelector, + undefined, + undefined, + undefined, + undefined, + 5 * MINUTES ) return result.body.items @@ -265,7 +277,13 @@ export class K8 { undefined, undefined, undefined, - fieldSelector + fieldSelector, + undefined, + undefined, + undefined, + undefined, + undefined, + 5 * MINUTES ) return this.filterItem(resp.body.items, { name }) @@ -915,14 +933,12 @@ export class K8 { const ns = this._getNamespace() const labelSelector = labels.join(',') - this.logger.debug(`WaitForPod [namespace:${ns}, labelSelector: ${labelSelector}, maxAttempts: ${maxAttempts}]`) + this.logger.debug(`WaitForPod [labelSelector: ${labelSelector}, namespace:${ns}, maxAttempts: ${maxAttempts}]`) return new Promise((resolve, reject) => { let attempts = 0 const check = async (resolve: (items: k8s.V1Pod[]) => void, reject: (reason?: any) => void) => { - this.logger.debug(`Checking for pod [namespace:${ns}, labelSelector: ${labelSelector}] [attempt: ${attempts}/${maxAttempts}]`) - // wait for the pod to be available with the given status and labels const resp = await this.kubeClient.listNamespacedPod( ns, @@ -932,10 +948,14 @@ export class K8 { undefined, undefined, labelSelector, - podCount + podCount, + undefined, + undefined, + undefined, + 5 * MINUTES ) - this.logger.debug(`${resp.body?.items?.length}/${podCount} pod found [namespace:${ns}, labelSelector: ${labelSelector}] [attempt: ${attempts}/${maxAttempts}]`) + this.logger.debug(`[attempt: ${attempts}/${maxAttempts}] ${resp.body?.items?.length}/${podCount} pod found [labelSelector: ${labelSelector}, namespace:${ns}]`) if (resp.body?.items?.length === podCount) { let phaseMatchCount = 0 let predicateMatchCount = 0 @@ -1026,7 +1046,12 @@ export class K8 { undefined, undefined, undefined, - labelSelector + labelSelector, + undefined, + undefined, + undefined, + undefined, + 5 * MINUTES, ) for (const item of resp.body.items) { @@ -1051,7 +1076,12 @@ export class K8 { undefined, undefined, undefined, - labelSelector + labelSelector, + undefined, + undefined, + undefined, + undefined, + 5 * MINUTES, ) for (const item of resp.body.items) { @@ -1084,8 +1114,19 @@ export class K8 { * objects must be base64 decoded */ async getSecret (namespace: string, labelSelector: string) { - const result = await this.kubeClient.listNamespacedSecret(namespace, - undefined, undefined, undefined, undefined, labelSelector) + const result = await this.kubeClient.listNamespacedSecret( + namespace, + undefined, + undefined, + undefined, + undefined, + labelSelector, + undefined, + undefined, + undefined, + undefined, + 5 * MINUTES + ) if (result.response.statusCode === 200 && result.body.items && result.body.items.length > 0) { const secretObject = result.body.items[0] @@ -1242,4 +1283,36 @@ export class K8 { fs.rmSync(tmpFile) } } + + /** + * Get a pod by name and namespace, will check every 1 second until the pod is no longer found. + * Can throw a SoloError if there is an error while deleting the pod. + * @param podName - the name of the pod + * @param namespace - the namespace of the pod + */ + async killPod (podName: string, namespace: string) { + try { + const result = await this.kubeClient.deleteNamespacedPod(podName, namespace, undefined, undefined, 1) + if (result.response.statusCode !== 200) { + throw new SoloError(`Failed to delete pod ${podName} in namespace ${namespace}: statusCode: ${result.response.statusCode}`) + } + let podExists = true + while (podExists) { + const pod = await this.getPodByName(podName) + if (!pod?.metadata?.deletionTimestamp) { + podExists = false + } else { + await sleep(1000) + } + } + } catch (e) { + const errorMessage = `Failed to delete pod ${podName} in namespace ${namespace}: ${e.message}` + if (e.body?.code === 404 || e.response?.body?.code === 404) { + this.logger.info(`Pod not found: ${errorMessage}`, e) + return + } + this.logger.error(errorMessage, e) + throw new SoloError(errorMessage, e) + } + } } diff --git a/src/core/logging.ts b/src/core/logging.ts index 73fe4967f..80cc603cd 100644 --- a/src/core/logging.ts +++ b/src/core/logging.ts @@ -139,7 +139,7 @@ export class SoloLogger { } console.log(chalk.red('***********************************************************************************')) - this.debug(err.message, { error: err.message, stacktrace: stack }) + this.error(err.message, err) } error (msg: any, ...args: any) { diff --git a/src/core/platform_installer.ts b/src/core/platform_installer.ts index bee8bd4fc..f5629b053 100644 --- a/src/core/platform_installer.ts +++ b/src/core/platform_installer.ts @@ -210,16 +210,8 @@ export class PlatformInstaller { if (!destPath) throw new MissingArgumentError('destPath is required') const recursiveFlag = recursive ? '-R' : '' - try { - await this.k8.execContainer(podName, container, `chown ${recursiveFlag} hedera:hedera ${destPath}`) - } catch (e: Error | any) { - // ignore error, can't change settings on files that come from configMaps or secrets - } - try { - await this.k8.execContainer(podName, container, `chmod ${recursiveFlag} ${mode} ${destPath}`) - } catch (e: Error | any) { - // ignore error, can't change settings on files that come from configMaps or secrets - } + await this.k8.execContainer(podName, container, ['bash','-c', `chown ${recursiveFlag} hedera:hedera ${destPath} 2>/dev/null || true`]) + await this.k8.execContainer(podName, container, ['bash','-c', `chmod ${recursiveFlag} ${mode} ${destPath} 2>/dev/null || true`]) return true } diff --git a/test/e2e/commands/node_delete.test.ts b/test/e2e/commands/node_delete.test.ts index dc421635a..150e6df38 100644 --- a/test/e2e/commands/node_delete.test.ts +++ b/test/e2e/commands/node_delete.test.ts @@ -34,8 +34,9 @@ import * as NodeCommandConfigs from '../../../src/commands/node/configs.js' const namespace = 'node-delete' const nodeAlias = 'node1' const argv = getDefaultArgv() -argv[flags.nodeAliasesUnparsed.name] = 'node1,node2,node3,node4' +argv[flags.nodeAliasesUnparsed.name] = 'node1,node2' argv[flags.nodeAlias.name] = nodeAlias +argv[flags.stakeAmounts.name] = '1,1000' argv[flags.generateGossipKeys.name] = true argv[flags.generateTlsKeys.name] = true argv[flags.persistentVolumeClaims.name] = true diff --git a/test/e2e/commands/separate_node_add.test.ts b/test/e2e/commands/separate_node_add.test.ts index 5ec1e5ef0..b607bdfa5 100644 --- a/test/e2e/commands/separate_node_add.test.ts +++ b/test/e2e/commands/separate_node_add.test.ts @@ -33,7 +33,8 @@ import { MINUTES } from '../../../src/core/constants.js' const defaultTimeout = 2 * MINUTES const namespace = 'node-add-separated' const argv = getDefaultArgv() -argv[flags.nodeAliasesUnparsed.name] = 'node1,node2,node3' +argv[flags.nodeAliasesUnparsed.name] = 'node1,node2' +argv[flags.stakeAmounts.name] = '1500,1' argv[flags.generateGossipKeys.name] = true argv[flags.generateTlsKeys.name] = true // set the env variable SOLO_CHARTS_DIR if developer wants to use local Solo charts diff --git a/test/e2e/commands/separate_node_delete.test.ts b/test/e2e/commands/separate_node_delete.test.ts index b45a80ff9..2863238a5 100644 --- a/test/e2e/commands/separate_node_delete.test.ts +++ b/test/e2e/commands/separate_node_delete.test.ts @@ -34,8 +34,9 @@ import * as NodeCommandConfigs from '../../../src/commands/node/configs.js' const namespace = 'node-delete-separate' const nodeAlias = 'node1' as NodeAlias const argv = getDefaultArgv() -argv[flags.nodeAliasesUnparsed.name] = 'node1,node2,node3,node4' +argv[flags.nodeAliasesUnparsed.name] = 'node1,node2' argv[flags.nodeAlias.name] = nodeAlias +argv[flags.stakeAmounts.name] = '1,1000' argv[flags.generateGossipKeys.name] = true argv[flags.generateTlsKeys.name] = true argv[flags.persistentVolumeClaims.name] = true diff --git a/test/e2e/integration/core/k8_e2e.test.ts b/test/e2e/integration/core/k8_e2e.test.ts index 8b4dd5090..7aa91b623 100644 --- a/test/e2e/integration/core/k8_e2e.test.ts +++ b/test/e2e/integration/core/k8_e2e.test.ts @@ -47,6 +47,28 @@ import type { PodName } from '../../../../src/types/aliases.js' const defaultTimeout = 2 * MINUTES +async function createPod (podName: PodName, containerName: string, podLabelValue: string, testNamespace: string, k8: K8): Promise { + const v1Pod = new V1Pod() + const v1Metadata = new V1ObjectMeta() + v1Metadata.name = podName as PodName + v1Metadata.namespace = testNamespace + v1Metadata.labels = { app: podLabelValue } + v1Pod.metadata = v1Metadata + const v1Container = new V1Container() + v1Container.name = containerName + v1Container.image = 'alpine:latest' + v1Container.command = ['/bin/sh', '-c', 'apk update && apk upgrade && apk add --update bash && sleep 7200'] + const v1Probe = new V1Probe() + const v1ExecAction = new V1ExecAction() + v1ExecAction.command = ['bash', '-c', 'exit 0'] + v1Probe.exec = v1ExecAction + v1Container.startupProbe = v1Probe + const v1Spec = new V1PodSpec() + v1Spec.containers = [v1Container] + v1Pod.spec = v1Spec + await k8.kubeClient.createNamespacedPod(testNamespace, v1Pod) +} + describe('K8', () => { const testLogger = logging.NewLogger('debug', true) const configManager = new ConfigManager(testLogger) @@ -66,25 +88,7 @@ describe('K8', () => { if (!await k8.hasNamespace(testNamespace)) { await k8.createNamespace(testNamespace) } - const v1Pod = new V1Pod() - const v1Metadata = new V1ObjectMeta() - v1Metadata.name = podName as PodName - v1Metadata.namespace = testNamespace - v1Metadata.labels = { app: podLabelValue } - v1Pod.metadata = v1Metadata - const v1Container = new V1Container() - v1Container.name = containerName - v1Container.image = 'alpine:latest' - v1Container.command = ['/bin/sh', '-c', 'apk update && apk upgrade && apk add --update bash && sleep 7200'] - const v1Probe = new V1Probe() - const v1ExecAction = new V1ExecAction() - v1ExecAction.command = ['bash', '-c', 'exit 0'] - v1Probe.exec = v1ExecAction - v1Container.startupProbe = v1Probe - const v1Spec = new V1PodSpec() - v1Spec.containers = [v1Container] - v1Pod.spec = v1Spec - await k8.kubeClient.createNamespacedPod(testNamespace, v1Pod) + await createPod(podName, containerName, podLabelValue, testNamespace, k8) const v1Svc = new V1Service() const v1SvcMetadata = new V1ObjectMeta() v1SvcMetadata.name = serviceName @@ -107,7 +111,7 @@ describe('K8', () => { after(async function () { this.timeout(defaultTimeout) try { - await k8.kubeClient.deleteNamespacedPod(podName, testNamespace, undefined, undefined, 1) + await k8.killPod(podName, testNamespace) argv[flags.namespace.name] = constants.SOLO_SETUP_NAMESPACE configManager.update(argv) } catch (e) { @@ -276,4 +280,13 @@ describe('K8', () => { await k8.deletePvc(v1Pvc.name, testNamespace) } }).timeout(defaultTimeout) + + it('should be able to kill a pod', async () => { + const podName = `test-pod-${uuid4()}` as PodName + const podLabelValue = `test-${uuid4()}` + await createPod(podName, containerName, podLabelValue, testNamespace, k8) + await k8.killPod(podName, testNamespace) + const newPods = await k8.getPodsByLabel([`app=${podLabelValue}`]) + expect(newPods).to.have.lengthOf(0) + }) }) diff --git a/test/test_add.ts b/test/test_add.ts index f14e18536..e8a174a89 100644 --- a/test/test_add.ts +++ b/test/test_add.ts @@ -39,7 +39,8 @@ export function testNodeAdd (localBuildPath: string, testDescription = 'Node add const suffix = localBuildPath.substring(0, 5) const namespace = 'node-add' + suffix const argv = getDefaultArgv() - argv[flags.nodeAliasesUnparsed.name] = 'node1,node2,node3' + argv[flags.nodeAliasesUnparsed.name] = 'node1,node2' + argv[flags.stakeAmounts.name] = '1500,1' argv[flags.generateGossipKeys.name] = true argv[flags.generateTlsKeys.name] = true // set the env variable SOLO_CHARTS_DIR if developer wants to use local Solo charts diff --git a/test/test_util.ts b/test/test_util.ts index d30e0f399..4d024a5a8 100644 --- a/test/test_util.ts +++ b/test/test_util.ts @@ -224,7 +224,7 @@ export function e2eTestSuite ( }) after(async function () { - this.timeout(3 * MINUTES) + this.timeout(5 * MINUTES) await getNodeLogs(k8, namespace) bootstrapResp.opts.logger.showUser(`------------------------- END: bootstrap (${testName}) ----------------------------`) }) @@ -272,7 +272,7 @@ export function e2eTestSuite ( flags.grpcWebTlsKeyPath.constName, 'chartPath' ]) - }).timeout(3 * MINUTES) + }).timeout(5 * MINUTES) if (startNodes) { it('should succeed with node setup command', async () => { @@ -298,13 +298,13 @@ export function e2eTestSuite ( }).timeout(30 * MINUTES) it('node log command should work', async () => { - await expect(nodeCmd.handlers.logs(argv)).to.eventually.be.ok + expect(await nodeCmd.handlers.logs(argv)).to.be.true const soloLogPath = path.join(SOLO_LOGS_DIR, 'solo.log') const soloLog = fs.readFileSync(soloLogPath, 'utf8') expect(soloLog).to.not.have.string(NODE_LOG_FAILURE_MSG) - }).timeout(30 * MINUTES) + }).timeout(5 * MINUTES) } })