From 89a1a1b36aaddf87491a724de7311c9c503d54b7 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Thu, 16 Nov 2023 14:15:57 -0500 Subject: [PATCH] feat: release develop (#84) Co-authored-by: Micaiah Reid Co-authored-by: deantchi <21262275+deantchi@users.noreply.github.com> Co-authored-by: Charlie <2747302+CharlieC3@users.noreply.github.com> --- .github/workflows/ci.yaml | 311 ++++++++ .releaserc | 22 + Cargo.lock | 35 +- Cargo.toml | 14 +- Config.toml | 5 + Dockerfile | 14 +- README.md | 22 +- examples/new-network-minimal.example.json | 61 -- examples/new-network.example.json | 425 +++++++++-- scripts/deploy-api.sh | 2 +- scripts/get-logs.sh | 10 +- scripts/redeploy-api.sh | 2 +- src/api_config.rs | 50 ++ src/config.rs | 447 +++++------- src/lib.rs | 690 ++++++++++++++---- src/main.rs | 80 +- src/resources/configmap.rs | 24 +- src/resources/deployment.rs | 17 + src/resources/mod.rs | 8 +- src/resources/pod.rs | 8 +- src/resources/pvc.rs | 4 +- src/resources/service.rs | 28 +- src/resources/stateful_set.rs | 15 + src/resources/tests.rs | 62 +- src/responder.rs | 105 ++- src/routes.rs | 46 +- src/template_parser.rs | 55 +- src/tests/fixtures/deployment-plan.yaml | 99 ++- src/tests/fixtures/network-manifest.yaml | 208 +++++- src/tests/fixtures/project-manifest.yaml | 33 +- src/tests/fixtures/stacks-devnet-config.json | 420 +++++++++-- src/tests/mod.rs | 199 +++-- ...tcoind-chain-coordinator-pod.template.yaml | 75 -- ...nd-chain-coordinator-service.template.yaml | 25 - templates/bitcoind-configmap.template.yaml | 7 - ...rd-deployment-plan-configmap.template.yaml | 7 - ...chain-coord-devnet-configmap.template.yaml | 7 - ...in-coord-namespace-configmap.template.yaml | 7 - ...-coord-project-dir-configmap.template.yaml | 7 - ...d-project-manifest-configmap.template.yaml | 7 - templates/ci/stacks-devnet-api.template.yaml | 81 ++ templates/configmaps/bitcoind.template.yaml | 12 + .../chain-coord-deployment-plan.template.yaml | 12 + .../chain-coord-devnet.template.yaml | 12 + .../chain-coord-project-dir.template.yaml | 12 + ...chain-coord-project-manifest.template.yaml | 12 + .../stacks-blockchain-api-pg.template.yaml | 12 + .../stacks-blockchain-api.template.yaml | 12 + .../stacks-blockchain.template.yaml | 12 + .../bitcoind-chain-coordinator.template.yaml | 114 +++ .../stacks-blockchain.template.yaml | 70 ++ templates/initial-config/storage-class.yaml | 2 +- .../bitcoind-chain-coordinator.template.yaml | 34 + .../stacks-blockchain-api.template.yaml | 30 + .../services/stacks-blockchain.template.yaml | 24 + templates/stacks-api-configmap.template.yaml | 7 - templates/stacks-api-pod.template.yaml | 41 -- ...tacks-api-postgres-configmap.template.yaml | 7 - templates/stacks-api-pvc.template.yaml | 15 - templates/stacks-api-service.template.yaml | 21 - templates/stacks-devnet-api.template.yaml | 37 +- templates/stacks-node-configmap.template.yaml | 7 - templates/stacks-node-pod.template.yaml | 38 - templates/stacks-node-service.template.yaml | 15 - .../stacks-blockchain-api.template.yaml | 87 +++ 65 files changed, 3137 insertions(+), 1252 deletions(-) create mode 100644 .github/workflows/ci.yaml create mode 100644 .releaserc delete mode 100644 examples/new-network-minimal.example.json create mode 100644 src/api_config.rs create mode 100644 src/resources/deployment.rs create mode 100644 src/resources/stateful_set.rs delete mode 100644 templates/bitcoind-chain-coordinator-pod.template.yaml delete mode 100644 templates/bitcoind-chain-coordinator-service.template.yaml delete mode 100644 templates/bitcoind-configmap.template.yaml delete mode 100644 templates/chain-coord-deployment-plan-configmap.template.yaml delete mode 100644 templates/chain-coord-devnet-configmap.template.yaml delete mode 100644 templates/chain-coord-namespace-configmap.template.yaml delete mode 100644 templates/chain-coord-project-dir-configmap.template.yaml delete mode 100644 templates/chain-coord-project-manifest-configmap.template.yaml create mode 100644 templates/ci/stacks-devnet-api.template.yaml create mode 100644 templates/configmaps/bitcoind.template.yaml create mode 100644 templates/configmaps/chain-coord-deployment-plan.template.yaml create mode 100644 templates/configmaps/chain-coord-devnet.template.yaml create mode 100644 templates/configmaps/chain-coord-project-dir.template.yaml create mode 100644 templates/configmaps/chain-coord-project-manifest.template.yaml create mode 100644 templates/configmaps/stacks-blockchain-api-pg.template.yaml create mode 100644 templates/configmaps/stacks-blockchain-api.template.yaml create mode 100644 templates/configmaps/stacks-blockchain.template.yaml create mode 100644 templates/deployments/bitcoind-chain-coordinator.template.yaml create mode 100644 templates/deployments/stacks-blockchain.template.yaml create mode 100644 templates/services/bitcoind-chain-coordinator.template.yaml create mode 100644 templates/services/stacks-blockchain-api.template.yaml create mode 100644 templates/services/stacks-blockchain.template.yaml delete mode 100644 templates/stacks-api-configmap.template.yaml delete mode 100644 templates/stacks-api-pod.template.yaml delete mode 100644 templates/stacks-api-postgres-configmap.template.yaml delete mode 100644 templates/stacks-api-pvc.template.yaml delete mode 100644 templates/stacks-api-service.template.yaml delete mode 100644 templates/stacks-node-configmap.template.yaml delete mode 100644 templates/stacks-node-pod.template.yaml delete mode 100644 templates/stacks-node-service.template.yaml create mode 100644 templates/stateful-sets/stacks-blockchain-api.template.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..9880335 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,311 @@ +name: CI + +on: + push: + branches: + - main + - develop + paths-ignore: + - '**/CHANGELOG.md' + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.docker_meta.outputs.version }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker Meta + uses: docker/metadata-action@v5 + id: docker_meta + with: + images: | + hirosystems/${{ github.event.repository.name }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=raw,value=latest,enable={{is_default_branch}} + + - name: Create artifact directory + run: mkdir -p /tmp/artifacts + + - name: Build/Save Image + uses: docker/build-push-action@v5 + with: + context: . + tags: ${{ steps.docker_meta.outputs.tags }}, + labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + outputs: type=docker,dest=/tmp/artifacts/myimage.tar + + - name: Save docker artifact + uses: actions/upload-artifact@v3 + with: + name: docker-image + path: /tmp/artifacts/myimage.tar + + k8s-tests: + runs-on: ubuntu-latest + needs: build + env: + VERSION: ${{ needs.build.outputs.version }} + + steps: + - name: Read version into env var + run: | + echo "Extracted version tag: ${VERSION}" + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build k8s cluster + uses: nolar/setup-k3d-k3s@v1 + with: + version: v1.26 + k3d-name: k3d-kube + k3d-args: "--no-lb --no-rollback --k3s-arg --disable=traefik,servicelb,metrics-server@server:*" + + - name: Pull docker image artifact from previous docker job + uses: actions/download-artifact@v3 + with: + name: docker-image + path: /tmp/artifacts + + - name: Load image + run: | + docker load --input /tmp/artifacts/myimage.tar + docker tag hirosystems/stacks-devnet-api:${VERSION} hirosystems/stacks-devnet-api:ci + docker image ls -a + + - name: Deploy k8s manifests + run: | + k3d image import hirosystems/stacks-devnet-api:ci -c k3d-kube + kubectl create namespace devnet + kubectl create configmap stacks-devnet-api --from-file=./Config.toml --namespace devnet + kubectl apply -f ./templates/ci/stacks-devnet-api.template.yaml + echo "sleep for 30 sec" + sleep 30 + + - name: Sanity check on k8s resources deployed + run: | + kubectl get all --all-namespaces + kubectl -n devnet get cm + kubectl -n devnet describe po stacks-devnet-api + kubectl -n devnet logs stacks-devnet-api + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Cache cargo + uses: actions/cache@v3 + with: + path: ~/.cargo/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + + - name: Install and run cargo-tarpaulin + run: | + cargo install cargo-tarpaulin + cargo --version + cargo tarpaulin --out lcov --features k8s_tests + + - name: Upload to codecov.io + uses: codecov/codecov-action@v3 + with: + token: ${{secrets.CODECOV_TOKEN}} + + build-publish-release: + runs-on: ubuntu-latest + needs: + - build + - k8s-tests + outputs: + docker_image_digest: ${{ steps.docker_push.outputs.digest }} + new_release_published: ${{ steps.semantic.outputs.new_release_published }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Semantic Release + uses: cycjimmy/semantic-release-action@v4 + id: semantic + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SEMANTIC_RELEASE_PACKAGE: ${{ github.event.repository.name }} + with: + semantic_version: 19 + extra_plugins: | + @semantic-release/changelog@6.0.3 + @semantic-release/git@10.0.1 + conventional-changelog-conventionalcommits@6.1.0 + + - name: Checkout tag + if: steps.semantic.outputs.new_release_version != '' + uses: actions/checkout@v4 + with: + persist-credentials: false + ref: v${{ steps.semantic.outputs.new_release_version }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker Meta + uses: docker/metadata-action@v5 + id: docker_meta + with: + images: | + hirosystems/${{ github.event.repository.name }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}},value=${{ steps.semantic.outputs.new_release_version }},enable=${{ steps.semantic.outputs.new_release_version != '' }} + type=semver,pattern={{major}}.{{minor}},value=${{ steps.semantic.outputs.new_release_version }},enable=${{ steps.semantic.outputs.new_release_version != '' }} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Log in to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Build/Push Image + uses: docker/build-push-action@v5 + id: docker_push + with: + context: . + tags: ${{ steps.docker_meta.outputs.tags }}, + labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + # Only push if (there's a new release on main branch, or if building a non-main branch) and (Only run on non-PR events or only PRs that aren't from forks) + push: ${{ (github.ref != 'refs/heads/main' || steps.semantic.outputs.new_release_version != '') && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} + + deploy-dev: + runs-on: ubuntu-latest + needs: + - build-publish-release + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + env: + DEPLOY_ENV: dev + environment: + name: Development + url: https://platform.dev.hiro.so/ + steps: + - name: Checkout actions repo + uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.GH_TOKEN }} + repository: ${{ secrets.DEVOPS_ACTIONS_REPO }} + + - name: Deploy Stacks Devnet API + uses: ./actions/deploy + with: + docker_tag: ${{ needs.build-publish-release.outputs.docker_image_digest }} + k8s_repo: k8s-platform + k8s_branch: main + file_pattern: manifests/api/stacks-devnet-api/${{ env.DEPLOY_ENV }}/base/kustomization.yaml + gh_token: ${{ secrets.GH_TOKEN }} + + auto-approve-dev: + runs-on: ubuntu-latest + if: needs.build-publish-release.outputs.new_release_published == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) + needs: + - build-publish-release + steps: + - name: Approve pending deployment + run: | + sleep 5 + ENV_ID=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/stacks-devnet-api/actions/runs/${{ github.run_id }}/pending_deployments" | jq -r '.[0].environment.id // empty') + if [[ -n "${ENV_ID}" ]]; then + curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/stacks-devnet-api/actions/runs/${{ github.run_id }}/pending_deployments" -d "{\"environment_ids\":[${ENV_ID}],\"state\":\"approved\",\"comment\":\"auto approve\"}" + fi + + deploy-staging: + runs-on: ubuntu-latest + needs: + - build-publish-release + - deploy-dev + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + env: + DEPLOY_ENV: stg + environment: + name: Staging + url: https://platform.stg.hiro.so/ + steps: + - name: Checkout actions repo + uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.GH_TOKEN }} + repository: ${{ secrets.DEVOPS_ACTIONS_REPO }} + + - name: Deploy Stacks Devnet API + uses: ./actions/deploy + with: + docker_tag: ${{ needs.build-publish-release.outputs.docker_image_digest }} + k8s_repo: k8s-platform + k8s_branch: main + file_pattern: manifests/api/stacks-devnet-api/${{ env.DEPLOY_ENV }}/base/kustomization.yaml + gh_token: ${{ secrets.GH_TOKEN }} + + auto-approve-staging: + runs-on: ubuntu-latest + if: needs.build-publish-release.outputs.new_release_published == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) + needs: + - build-publish-release + - deploy-dev + steps: + - name: Approve pending deployment + run: | + sleep 5 + ENV_ID=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/stacks-devnet-api/actions/runs/${{ github.run_id }}/pending_deployments" | jq -r '.[0].environment.id // empty') + if [[ -n "${ENV_ID}" ]]; then + curl -s -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/hirosystems/stacks-devnet-api/actions/runs/${{ github.run_id }}/pending_deployments" -d "{\"environment_ids\":[${ENV_ID}],\"state\":\"approved\",\"comment\":\"auto approve\"}" + fi + + deploy-prod: + runs-on: ubuntu-latest + if: needs.build-publish-release.outputs.new_release_published == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) + needs: + - build-publish-release + - deploy-staging + env: + DEPLOY_ENV: prd + environment: + name: Production + url: https://platform.hiro.so/ + steps: + - name: Checkout actions repo + uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.GH_TOKEN }} + repository: ${{ secrets.DEVOPS_ACTIONS_REPO }} + + - name: Deploy Stacks Devnet API + uses: ./actions/deploy + with: + docker_tag: ${{ needs.build-publish-release.outputs.docker_image_digest }} + k8s_repo: k8s-platform + k8s_branch: main + file_pattern: manifests/api/stacks-devnet-api/${{ env.DEPLOY_ENV }}/base/kustomization.yaml + gh_token: ${{ secrets.GH_TOKEN }} diff --git a/.releaserc b/.releaserc new file mode 100644 index 0000000..76466b6 --- /dev/null +++ b/.releaserc @@ -0,0 +1,22 @@ +{ + "branches": [ + "main" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits" + } + ], + "@semantic-release/github", + "@semantic-release/changelog", + "@semantic-release/git" + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8795095..d78dc7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,9 +193,9 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64-compat" @@ -399,11 +399,12 @@ dependencies = [ [[package]] name = "clarinet-deployments" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d559d56b906d2af1a6f4552607e059d9383f5961f062c62e8df743755b59a86f" +checksum = "e5b484bdc8880ac7cc3fff38d1cd17d957685af68f8dde2bba82878c48381bb4" dependencies = [ "base58 0.2.0", + "base64 0.21.4", "bitcoin", "bitcoincore-rpc", "bitcoincore-rpc-json", @@ -422,9 +423,9 @@ dependencies = [ [[package]] name = "clarinet-files" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1464355d97840afd89fcc04c4361a4d06200de1ddf4a311b649744c7e67d0155" +checksum = "7dc81af3686bcf35e8f8bfe844473f1af3372a7e148561378f6598a871dc018b" dependencies = [ "bip39", "bitcoin", @@ -434,6 +435,7 @@ dependencies = [ "libsecp256k1 0.7.1", "serde", "serde_derive", + "serde_json", "tiny-hderive", "toml", "url", @@ -453,9 +455,9 @@ dependencies = [ [[package]] name = "clarity-repl" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2d096ff86def33801c5ad13dc222befa42118326a80cfc1cf34d9ee1abc70b" +checksum = "7ce75a1ed41a078a67f78abe73c84c241331044304831af8db8b7056dcef46fc" dependencies = [ "ansi_term", "atty", @@ -1419,7 +1421,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd990069640f9db34b3b0f7a1afc62a05ffaa3be9b66aa3c313f58346df7f788" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "chrono", "http", @@ -2166,7 +2168,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bytes", "encoding_rs", "futures-core", @@ -2327,7 +2329,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", ] [[package]] @@ -2528,9 +2530,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "indexmap 2.0.0", "itoa", @@ -2840,7 +2842,6 @@ dependencies = [ name = "stacks-devnet-api" version = "0.1.0" dependencies = [ - "base64 0.21.2", "chainhook-types", "clarinet-deployments", "clarinet-files", @@ -2868,9 +2869,9 @@ dependencies = [ [[package]] name = "stacks-rpc-client" -version = "1.0.7" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417254594e89666f68c76267a620d3751ae6604e39c97f3abb57a845390bc1a0" +checksum = "12eef9ff174e8345b414abbfa5ed7e271e772d1e6b2c3eaa9f7f02209c87f48e" dependencies = [ "clarity-repl", "hmac 0.12.1", @@ -3346,7 +3347,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ac8060a61f8758a61562f6fb53ba3cbe1ca906f001df2e53cccddcdbee91e7c" dependencies = [ - "base64 0.21.2", + "base64 0.21.4", "bitflags 2.3.3", "bytes", "futures-core", diff --git a/Cargo.toml b/Cargo.toml index 895c2fa..cb663ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,12 +25,11 @@ http-body = "0.4.5" hiro-system-kit = {version = "0.1.0", features = ["log"]} strum_macros = "0.24.3" strum = "0.24.1" -base64 = "0.21.2" -clarity-vm = "2.1.1" -clarity-repl = "1.6.4" -clarinet-files = "1.0.0" -chainhook-types = "1.0.1" -clarinet-deployments = "1.0.1" +clarity-vm = "2.1.1" +clarity-repl = "1.8.0" +clarinet-files = {version = "1.0.3" } +chainhook-types = "1.0" +clarinet-deployments = {version = "1.0.3" } toml = "0.5.9" [dev-dependencies] @@ -38,3 +37,6 @@ tower-test = "0.4.0" test-case = "3.1.0" rand = "0.8.5" serial_test = "2.0.0" + +[features] +k8s_tests = [] \ No newline at end of file diff --git a/Config.toml b/Config.toml index 3d004fd..c8cf001 100644 --- a/Config.toml +++ b/Config.toml @@ -1,2 +1,7 @@ +[http_response] allowed_origins = ["*"] allowed_methods = ["DELETE", "GET", "OPTIONS", "POST", "HEAD"] + +[auth] +auth_header = "x-auth-request-user" +namespace_prefix = "platform-" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e98f24c..d2278f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,13 @@ -FROM arm64v8/rust:1.67 as builder - -WORKDIR ./ -COPY . ./ +FROM rust:bullseye as builder +RUN apt update && apt install -y ca-certificates pkg-config libssl-dev +WORKDIR /src +COPY . /src +RUN mkdir /out RUN cargo build --release --manifest-path ./Cargo.toml +RUN cp target/release/stacks-devnet-api /out FROM gcr.io/distroless/cc -COPY --from=builder target/release/stacks-devnet-api / +COPY --from=builder /out/ /bin/ -ENTRYPOINT ["./stacks-devnet-api"] \ No newline at end of file +CMD ["stacks-devnet-api"] \ No newline at end of file diff --git a/README.md b/README.md index 1d472d5..41a16cf 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,20 @@ You should now be ready to deploy this service to your local Kubernetes cluster! The `Config.toml` at the root directory of the project can be used to control some settings. This same file can be used to update both the stable and development build. The following settings are supported: - `allowed_origins` - this setting is an array of strings and is used to set what origins are allowed in cross-origin requests. For example, `allowed_origins = ["*"]` allows any origins to make requests to this service, while `allowed_origins = ["localhost:3002", "dev.platform.so"]` will only allow requests from the two specified hosts. - `allowed_methods` - this setting is an array of strings that sets what HTTP methods can be made to this server. + - `auth_header` - all requests to the API specify a network id which indicates the network that is being modified. An auth header is checked on all requests to ensure that the value of the auth header matches the id of the request. This configuration value dictates the name of that auth header + - `namespace_prefix` - the user's id that is used for the auth header differs slightly from the network id that is used to differentiate devnets. This value is used to determine how to mutate a user id to create a namespace. For example, if the namespace prefix is `zzz-platform`, and a user makes a requests with an auth header value of `auth0|test-namespace`, the devnet API will ensure that the request is trying to create or update a devnet with namespace `zzz-platform-auth0-test-namespace`. + +## Environment Variables +The following environment variables can be provided at runtime to further configure the API: + - `KUBE_CONTEXT="" stacks-devnet-api` + - With this flag, you can specify the context that will be used for all requests. + - Default: the devnet API will deploy assets to the user's default Kubernetes context. Run `kubectl config get-context` to see your default. + - `CONFIG_PATH="/path/to/config" stacks-devnet-api` + - Use this flag to specify the path of the `Config.toml` + - Default: in development mode (building the app using `cargo build`), the default location is `./Config.toml`; in release mode (building the app using `cargo build --release`), the default location is `/etc/config/Config.toml` ## Deploying the Stable Version + In your terminal, run ``` ./scripts/deploy-api.sh @@ -46,11 +58,11 @@ metadata: name: stacks-devnet-api namespace: devnet spec: - serviceAccountName: stacks-devnet-api-service-account + serviceAccountName: stacks-devnet-api containers: - command: - ./stacks-devnet-api - name: stacks-devnet-api-container + name: stacks-devnet-api - image: quay.io/hirosystems/stacks-devnet-api:latest - imagePullPolicy: Always + image: stacks-devnet-api:latest @@ -82,9 +94,9 @@ When the service has been deployed to your Kubernetes cluster, it should be reac "bitcoin_chain_tip": 116 } ``` - - `GET/POST localhost:8477/api/v1/network//stacks-node/*` - Forwards `*` to the underlying stacks node pod of the devnet. If not all devnet assets exist for the given namespace, a 404 error will be returned. + - `GET/POST localhost:8477/api/v1/network//stacks-blockchain/*` - Forwards `*` to the underlying stacks node pod of the devnet. If not all devnet assets exist for the given namespace, a 404 error will be returned. - `GET/POST localhost:8477/api/v1/network//bitcoin-node/*` - Forwards `*` to the underlying bitcoin node pod of the devnet. If not all devnet assets exist for the given namespace, a 404 error will be returned. -- `GET/POST localhost:8477/api/v1/network//stacks-api/*` - Forwards `*` to the underlying stacks api pod of the devnet. If not all devnet assets exist for the given namespace, a 404 error will be returned. +- `GET/POST localhost:8477/api/v1/network//stacks-blockchain-api/*` - Forwards `*` to the underlying stacks api pod of the devnet. If not all devnet assets exist for the given namespace, a 404 error will be returned. ## Bugs and feature requests @@ -114,4 +126,4 @@ Join our community and stay connected with the latest updates and discussions: - [Visit hiro.so](https://www.hiro.so/) for updates and subcribing to the mailing list. -- Follow [Hiro on Twitter.](https://twitter.com/hirosystems) \ No newline at end of file +- Follow [Hiro on Twitter.](https://twitter.com/hirosystems) diff --git a/examples/new-network-minimal.example.json b/examples/new-network-minimal.example.json deleted file mode 100644 index 05f70d4..0000000 --- a/examples/new-network-minimal.example.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "namespace": "test-namespace", - "bitcoin_node_username": "test-username", - "bitcoin_node_password": "test-password", - "project_manifest": { - "name": "px", - "description": "my description", - "authors": ["test1", "test2"], - "requirements": ["test1", "test2"] - }, - "accounts": [ - { - "name": "deployer", - "mnemonic": "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw", - "balance": 100000000000000, - "stx_address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" - }, - { - "name": "wallet_1", - "mnemonic": "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild", - "balance": 100000000000000, - "stx_address": "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5" - } - ], - "deployment_plan": { - "id": 0, - "name": "Devnet deployment", - "network": "devnet", - "stacks-node": "http://localhost:20443", - "bitcoin-node": "http://px-devnet:px-devnet@localhost:18443", - "plan": { - "batches": [ - { - "id": 0, - "transactions": [ - { - "contract-publish": { - "contract-name": "px", - "expected-sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - "cost": 18060, - "path": "contracts/px.clar", - "anchor-block-only": true, - "clarity-version": 2 - } - } - ], - "epoch": "2.1" - } - ] - } - }, - "contracts": [ - { - "name": "px", - "source": "XG47OyB0aXRsZTogcHhcbjs7IHZlcnNpb246XG47OyBzdW1tYXJ5OlxuOzsgZGVzY3JpcHRpb246IEFsbG93cyB1c2VycyB0byBwYXkgdG8gdXBkYXRlIGRhdGEgaW4gYSBtYXRyaXguIFxuOzsgIEVhY2ggbWF0cml4IHZhbHVlIG11c3QgYmUgYSBoZXhhZGVjaW1hbCB2YWx1ZSBmcm9tIDB4MDAwMDAwIHRvIDB4ZmZmZmZmLCByZXByZXNlbnRpbmcgYSBjb2xvciB0byBiZSBkaXNwbGF5ZWQgb24gYSBncmlkIGluIGEgd2ViIHBhZ2UuIFxuOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuXG5cbjs7IHRyYWl0c1xuOztcblxuOzsgdG9rZW4gZGVmaW5pdGlvbnNcbjs7IFxuXG47OyBjb25zdGFudHNcbjs7XG4oZGVmaW5lLWNvbnN0YW50IE1BWF9MT0MgdTEwMClcbihkZWZpbmUtY29uc3RhbnQgTUFYX1ZBTCAweGZmZmZmZilcbihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMClcbihkZWZpbmUtY29uc3RhbnQgQUxMX0xPQ1MgKGxpc3QgdTAgdTEgdTIgdTMgdTQgdTUgdTYgdTcgdTggdTkgdTEwIHUxMSB1MTIgdTEzIHUxNCB1MTUgdTE2IHUxNyB1MTggdTE5IHUyMCB1MjEgdTIyIHUyMyB1MjQgdTI1IHUyNiB1MjcgdTI4IHUyOSB1MzAgdTMxIHUzMiB1MzMgdTM0IHUzNSB1MzYgdTM3IHUzOCB1MzkgdTQwIHU0MSB1NDIgdTQzIHU0NCB1NDUgdTQ2IHU0NyB1NDggdTQ5IHU1MCB1NTEgdTUyIHU1MyB1NTQgdTU1IHU1NiB1NTcgdTU4IHU1OSB1NjAgdTYxIHU2MiB1NjMgdTY0IHU2NSB1NjYgdTY3IHU2OCB1NjkgdTcwIHU3MSB1NzIgdTczIHU3NCB1NzUgdTc2IHU3NyB1NzggdTc5IHU4MCB1ODEgdTgyIHU4MyB1ODQgdTg1IHU4NiB1ODcgdTg4IHU4OSB1OTAgdTkxIHU5MiB1OTMgdTk0IHU5NSB1OTYgdTk3IHU5OCB1OTkpKVxuOzsgZGF0YSB2YXJzXG47O1xuXG47OyBkYXRhIG1hcHNcbjs7XG4oZGVmaW5lLW1hcCBwaXhlbHMgdWludCAoYnVmZiAzKSlcblxuOzsgcHVibGljIGZ1bmN0aW9uc1xuOztcbihkZWZpbmUtcHVibGljIChzZXQtdmFsdWUtYXQgKGxvYyB1aW50KSAodmFsdWUgKGJ1ZmYgMykpKSBcbiAgICAoYmVnaW4gXG4gICAgICAgIChpZiAoPj0gbG9jIE1BWF9MT0MpXG4gICAgICAgICAgICAoZXJyIFwiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy5cIilcbiAgICAgICAgICAgIChpZiAoPiB2YWx1ZSBNQVhfVkFMKVxuICAgICAgICAgICAgICAgIChlcnIgXCJWYWx1ZSBtdXN0IGJlIGxlc3MgdGhhbiAweGZmZmZmZi5cIilcbiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTClcbiAgICAgICAgICAgICAgICAgICAgKGVyciBcIlZhbHVlIG11c3QgYmUgZ3JlYXRlciB0aGFuIDB4MDAwMDAwLlwiKVxuICAgICAgICAgICAgICAgICAgICAob2sgKG1hcC1zZXQgcGl4ZWxzIGxvYyB2YWx1ZSkpXG4gICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgKVxuICAgICAgICApXG4gICAgKVxuKVxuOzsgcmVhZCBvbmx5IGZ1bmN0aW9uc1xuOztcblxuKGRlZmluZS1yZWFkLW9ubHkgKGdldC12YWx1ZS1hdCAobG9jIHVpbnQpKVxuICAgIChpZiAoPj0gbG9jIE1BWF9MT0MpXG4gICAgICAgIChlcnIgXCJPdXQgb2YgYm91bmRzLlwiKVxuICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSlcbiAgICApXG4pXG5cbihkZWZpbmUtcmVhZC1vbmx5IChnZXQtYWxsKSBcbiAgICAobWFwIGdldC12YWx1ZS1hdCBBTExfTE9DUylcbilcblxuKGRlZmluZS1yZWFkLW9ubHkgKGdlbmVzaXMtdGltZSAoaGVpZ2h0IHVpbnQpKVxuICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpXG4pXG47OyBwcml2YXRlIGZ1bmN0aW9uc1xuOztcbg==", - "clarity_version": 2, - "epoch": 2.1, - "deployer": null - } - ] -} diff --git a/examples/new-network.example.json b/examples/new-network.example.json index 08de413..92c5931 100644 --- a/examples/new-network.example.json +++ b/examples/new-network.example.json @@ -1,80 +1,369 @@ { - "namespace": "test-namespace1", - "stacks_node_wait_time_for_microblocks": 50, - "stacks_node_first_attempt_time_ms": 500, - "stacks_node_subsequent_attempt_time_ms": 1000, - "bitcoin_node_username": "test-username", - "bitcoin_node_password": "test-password", - "miner_mnemonic": "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild", - "miner_derivation_path": "m/44'/5757'/0'/0/0", - "miner_coinbase_recipient": "ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ", - "faucet_mnemonic": "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform", - "faucet_derivation_path": "m/44'/5757'/0'/0/0", - "bitcoin_controller_block_time": 0, - "bitcoin_controller_automining_disabled": true, - "disable_bitcoin_explorer": true, - "disable_stacks_explorer": true, - "disable_stacks_api": false, - "epoch_2_0": 100, - "epoch_2_05": 102, - "epoch_2_1": 106, - "epoch_2_2": 120, - "pox_2_activation": 112, - "pox_2_unlock_height": 112, - "project_manifest": { - "name": "px", - "description": "my description", - "authors": ["test1", "test2"], - "requirements": ["test1", "test2"] - }, - "accounts": [ - { - "name": "deployer", - "mnemonic": "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw", - "balance": 100000000000000, - "stx_address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" - }, - { - "name": "wallet_1", - "mnemonic": "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild", - "balance": 100000000000000, - "stx_address": "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5" - } - ], "deployment_plan": { "id": 0, "name": "Devnet deployment", "network": "devnet", - "stacks-node": "http://localhost:20443", - "bitcoin-node": "http://px-devnet:px-devnet@localhost:18443", - "plan": { - "batches": [ - { - "id": 0, - "transactions": [ - { - "contract-publish": { - "contract-name": "px", - "expected-sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - "cost": 18060, - "path": "contracts/px.clar", - "anchor-block-only": true, - "clarity-version": 2 - } + "stacks_node": "http://localhost:20443", + "bitcoin_node": "http://px-devnet:px-devnet@localhost:18443", + "genesis": null, + "batches": [ + { + "id": 0, + "transactions": [ + { + "transaction_type": "RequirementPublish", + "contract_id": "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "KGRlZmluZS10cmFpdCBuZnQtdHJhaXQKICAoCiAgICA7OyBMYXN0IHRva2VuIElELCBsaW1pdGVkIHRvIHVpbnQgcmFuZ2UKICAgIChnZXQtbGFzdC10b2tlbi1pZCAoKSAocmVzcG9uc2UgdWludCB1aW50KSkKCiAgICA7OyBVUkkgZm9yIG1ldGFkYXRhIGFzc29jaWF0ZWQgd2l0aCB0aGUgdG9rZW4KICAgIChnZXQtdG9rZW4tdXJpICh1aW50KSAocmVzcG9uc2UgKG9wdGlvbmFsIChzdHJpbmctYXNjaWkgMjU2KSkgdWludCkpCgogICAgIDs7IE93bmVyIG9mIGEgZ2l2ZW4gdG9rZW4gaWRlbnRpZmllcgogICAgKGdldC1vd25lciAodWludCkgKHJlc3BvbnNlIChvcHRpb25hbCBwcmluY2lwYWwpIHVpbnQpKQoKICAgIDs7IFRyYW5zZmVyIGZyb20gdGhlIHNlbmRlciB0byBhIG5ldyBwcmluY2lwYWwKICAgICh0cmFuc2ZlciAodWludCBwcmluY2lwYWwgcHJpbmNpcGFsKSAocmVzcG9uc2UgYm9vbCB1aW50KSkKICApCik=", + "clarity_version": 1, + "cost": 4670, + "location": { + "path": "./.cache/requirements/ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait.clar" } - ], - "epoch": "2.1" + } + ], + "epoch": "2.0" + }, + { + "id": 1, + "transactions": [ + { + "transaction_type": "RequirementPublish", + "contract_id": "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "KGRlZmluZS10cmFpdCBzaXAtMDEwLXRyYWl0CiAgKAogICAgOzsgVHJhbnNmZXIgZnJvbSB0aGUgY2FsbGVyIHRvIGEgbmV3IHByaW5jaXBhbAogICAgKHRyYW5zZmVyICh1aW50IHByaW5jaXBhbCBwcmluY2lwYWwgKG9wdGlvbmFsIChidWZmIDM0KSkpIChyZXNwb25zZSBib29sIHVpbnQpKQoKICAgIDs7IHRoZSBodW1hbiByZWFkYWJsZSBuYW1lIG9mIHRoZSB0b2tlbgogICAgKGdldC1uYW1lICgpIChyZXNwb25zZSAoc3RyaW5nLWFzY2lpIDMyKSB1aW50KSkKCiAgICA7OyB0aGUgdGlja2VyIHN5bWJvbCwgb3IgZW1wdHkgaWYgbm9uZQogICAgKGdldC1zeW1ib2wgKCkgKHJlc3BvbnNlIChzdHJpbmctYXNjaWkgMzIpIHVpbnQpKQoKICAgIDs7IHRoZSBudW1iZXIgb2YgZGVjaW1hbHMgdXNlZCwgZS5nLiA2IHdvdWxkIG1lYW4gMV8wMDBfMDAwIHJlcHJlc2VudHMgMSB0b2tlbgogICAgKGdldC1kZWNpbWFscyAoKSAocmVzcG9uc2UgdWludCB1aW50KSkKCiAgICA7OyB0aGUgYmFsYW5jZSBvZiB0aGUgcGFzc2VkIHByaW5jaXBhbAogICAgKGdldC1iYWxhbmNlIChwcmluY2lwYWwpIChyZXNwb25zZSB1aW50IHVpbnQpKQoKICAgIDs7IHRoZSBjdXJyZW50IHRvdGFsIHN1cHBseSAod2hpY2ggZG9lcyBub3QgbmVlZCB0byBiZSBhIGNvbnN0YW50KQogICAgKGdldC10b3RhbC1zdXBwbHkgKCkgKHJlc3BvbnNlIHVpbnQgdWludCkpCgogICAgOzsgYW4gb3B0aW9uYWwgVVJJIHRoYXQgcmVwcmVzZW50cyBtZXRhZGF0YSBvZiB0aGlzIHRva2VuCiAgICAoZ2V0LXRva2VuLXVyaSAoKSAocmVzcG9uc2UgKG9wdGlvbmFsIChzdHJpbmctdXRmOCAyNTYpKSB1aW50KSkKICApCik=", + "clarity_version": 1, + "cost": 8390, + "location": { + "path": "./.cache/requirements/ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard.clar" + } + } + ], + "epoch": "2.05" + }, + { + "id": 2, + "transactions": [ + { + "transaction_type": "RequirementPublish", + "contract_id": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-traits-v1", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "OzsgSW4gb3JkZXIgdG8gc3VwcG9ydCB3aXRoZHJhd2luZyBhbiBhc3NldCB0aGF0IHdhcyBtaW50ZWQgb24gYSBzdWJuZXQsIHRoZQo7OyBMMSBjb250cmFjdCBtdXN0IGltcGxlbWVudCB0aGlzIHRyYWl0LgooZGVmaW5lLXRyYWl0IG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQKICAoCiAgICA7OyBQcm9jZXNzIGEgd2l0aGRyYXdhbCBmcm9tIHRoZSBzdWJuZXQgZm9yIGFuIGFzc2V0IHdoaWNoIGRvZXMgbm90IHlldAogICAgOzsgZXhpc3Qgb24gdGhpcyBuZXR3b3JrLCBhbmQgdGh1cyByZXF1aXJlcyBhIG1pbnQuCiAgICAobWludC1mcm9tLXN1Ym5ldAogICAgICAoCiAgICAgICAgdWludCAgICAgICA7OyBhc3NldC1pZCAoTkZUKSBvciBhbW91bnQgKEZUKQogICAgICAgIHByaW5jaXBhbCAgOzsgc2VuZGVyCiAgICAgICAgcHJpbmNpcGFsICA7OyByZWNpcGllbnQKICAgICAgKQogICAgICAocmVzcG9uc2UgYm9vbCB1aW50KQogICAgKQogICkKKQ==", + "clarity_version": 2, + "cost": 4810, + "location": { + "path": "./.cache/requirements/ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-traits-v1.clar" + } + }, + { + "transaction_type": "RequirementPublish", + "contract_id": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1-2", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "OzsgVGhlIC5zdWJuZXQgY29udHJhY3QKCihkZWZpbmUtY29uc3RhbnQgQ09OVFJBQ1RfQUREUkVTUyAoYXMtY29udHJhY3QgdHgtc2VuZGVyKSkKCjs7IEVycm9yIGNvZGVzCihkZWZpbmUtY29uc3RhbnQgRVJSX0JMT0NLX0FMUkVBRFlfQ09NTUlUVEVEIDEpCihkZWZpbmUtY29uc3RhbnQgRVJSX0lOVkFMSURfTUlORVIgMikKKGRlZmluZS1jb25zdGFudCBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQgMykKKGRlZmluZS1jb25zdGFudCBFUlJfVFJBTlNGRVJfRkFJTEVEIDQpCihkZWZpbmUtY29uc3RhbnQgRVJSX0RJU0FMTE9XRURfQVNTRVQgNSkKKGRlZmluZS1jb25zdGFudCBFUlJfQVNTRVRfQUxSRUFEWV9BTExPV0VEIDYpCihkZWZpbmUtY29uc3RhbnQgRVJSX01FUktMRV9ST09UX0RPRVNfTk9UX01BVENIIDcpCihkZWZpbmUtY29uc3RhbnQgRVJSX0lOVkFMSURfTUVSS0xFX1JPT1QgOCkKKGRlZmluZS1jb25zdGFudCBFUlJfV0lUSERSQVdBTF9BTFJFQURZX1BST0NFU1NFRCA5KQooZGVmaW5lLWNvbnN0YW50IEVSUl9WQUxJREFUSU9OX0ZBSUxFRCAxMCkKOzs7IFRoZSB2YWx1ZSBzdXBwbGllZCBmb3IgYHRhcmdldC1jaGFpbi10aXBgIGRvZXMgbm90IG1hdGNoIHRoZSBjdXJyZW50IGNoYWluIHRpcC4KKGRlZmluZS1jb25zdGFudCBFUlJfSU5WQUxJRF9DSEFJTl9USVAgMTEpCjs7OyBUaGUgY29udHJhY3Qgd2FzIGNhbGxlZCBiZWZvcmUgcmVhY2hpbmcgdGhpcy1jaGFpbiBoZWlnaHQgcmVhY2hlcyAxLgooZGVmaW5lLWNvbnN0YW50IEVSUl9DQUxMRURfVE9PX0VBUkxZIDEyKQooZGVmaW5lLWNvbnN0YW50IEVSUl9NSU5UX0ZBSUxFRCAxMykKKGRlZmluZS1jb25zdGFudCBFUlJfQVRURU1QVF9UT19UUkFOU0ZFUl9aRVJPX0FNT1VOVCAxNCkKKGRlZmluZS1jb25zdGFudCBFUlJfSU5fQ09NUFVUQVRJT04gMTUpCjs7IFRoZSBjb250cmFjdCBkb2VzIG5vdCBvd24gdGhpcyBORlQgdG8gd2l0aGRyYXcgaXQuCihkZWZpbmUtY29uc3RhbnQgRVJSX05GVF9OT1RfT1dORURfQllfQ09OVFJBQ1QgMTYpCihkZWZpbmUtY29uc3RhbnQgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQgMzApCgo7OyBNYXAgZnJvbSBTdGFja3MgYmxvY2sgaGVpZ2h0IHRvIGJsb2NrIGNvbW1pdAooZGVmaW5lLW1hcCBibG9jay1jb21taXRzIHVpbnQgKGJ1ZmYgMzIpKQo7OyBNYXAgcmVjb3JkaW5nIHdpdGhkcmF3YWwgcm9vdHMKKGRlZmluZS1tYXAgd2l0aGRyYXdhbC1yb290cy1tYXAgKGJ1ZmYgMzIpIGJvb2wpCjs7IE1hcCByZWNvcmRpbmcgcHJvY2Vzc2VkIHdpdGhkcmF3YWwgbGVhdmVzCihkZWZpbmUtbWFwIHByb2Nlc3NlZC13aXRoZHJhd2FsLWxlYXZlcy1tYXAgeyB3aXRoZHJhd2FsLWxlYWYtaGFzaDogKGJ1ZmYgMzIpLCB3aXRoZHJhd2FsLXJvb3QtaGFzaDogKGJ1ZmYgMzIpIH0gYm9vbCkKCjs7IHByaW5jaXBhbCB0aGF0IGNhbiBjb21taXQgYmxvY2tzCihkZWZpbmUtZGF0YS12YXIgbWluZXIgcHJpbmNpcGFsIHR4LXNlbmRlcikKOzsgcHJpbmNpcGFsIHRoYXQgY2FuIHJlZ2lzdGVyIGNvbnRyYWN0cwooZGVmaW5lLWRhdGEtdmFyIGFkbWluIHByaW5jaXBhbCB0eC1zZW5kZXIpCgo7OyBNYXAgb2YgYWxsb3dlZCBjb250cmFjdHMgZm9yIGFzc2V0IHRyYW5zZmVycyAtIG1hcHMgTDEgY29udHJhY3QgcHJpbmNpcGFsIHRvIEwyIGNvbnRyYWN0IHByaW5jaXBhbAooZGVmaW5lLW1hcCBhbGxvd2VkLWNvbnRyYWN0cyBwcmluY2lwYWwgcHJpbmNpcGFsKQoKOzsgVXNlIHRyYWl0IGRlY2xhcmF0aW9ucwoodXNlLXRyYWl0IG5mdC10cmFpdCAnU1QxTlhCSzNLNVlZTUQ2RkQ0MU1WTlAzSlMxR0FCWjhUUlZYMDIzUFQubmZ0LXRyYWl0Lm5mdC10cmFpdCkKKHVzZS10cmFpdCBmdC10cmFpdCAnU1QxTlhCSzNLNVlZTUQ2RkQ0MU1WTlAzSlMxR0FCWjhUUlZYMDIzUFQuc2lwLTAxMC10cmFpdC1mdC1zdGFuZGFyZC5zaXAtMDEwLXRyYWl0KQoodXNlLXRyYWl0IG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQgLnN1Ym5ldC10cmFpdHMtdjEubWludC1mcm9tLXN1Ym5ldC10cmFpdCkKCjs7IFVwZGF0ZSB0aGUgbWluZXIgZm9yIHRoaXMgY29udHJhY3QuCihkZWZpbmUtcHVibGljICh1cGRhdGUtbWluZXIgKG5ldy1taW5lciBwcmluY2lwYWwpKQogICAgKGJlZ2luCiAgICAgICAgKGFzc2VydHMhIChpcy1lcSB0eC1zZW5kZXIgKHZhci1nZXQgbWluZXIpKSAoZXJyIEVSUl9JTlZBTElEX01JTkVSKSkKICAgICAgICAob2sgKHZhci1zZXQgbWluZXIgbmV3LW1pbmVyKSkKICAgICkKKQoKOzsgUmVnaXN0ZXIgYSBuZXcgRlQgY29udHJhY3QgdG8gYmUgc3VwcG9ydGVkIGJ5IHRoaXMgc3VibmV0LgooZGVmaW5lLXB1YmxpYyAocmVnaXN0ZXItbmV3LWZ0LWNvbnRyYWN0IChmdC1jb250cmFjdCA8ZnQtdHJhaXQ+KSAobDItY29udHJhY3QgcHJpbmNpcGFsKSkKICAgIChiZWdpbgogICAgICAgIDs7IFZlcmlmeSB0aGF0IHR4LXNlbmRlciBpcyBhbiBhdXRob3JpemVkIGFkbWluCiAgICAgICAgKGFzc2VydHMhIChpcy1hZG1pbiB0eC1zZW5kZXIpIChlcnIgRVJSX0lOVkFMSURfTUlORVIpKQoKICAgICAgICA7OyBTZXQgdXAgdGhlIGFzc2V0cyB0aGF0IHRoZSBjb250cmFjdCBpcyBhbGxvd2VkIHRvIHRyYW5zZmVyCiAgICAgICAgKGFzc2VydHMhIChtYXAtaW5zZXJ0IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkgbDItY29udHJhY3QpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX0FTU0VUX0FMUkVBRFlfQUxMT1dFRCkpCgogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAicmVnaXN0ZXItY29udHJhY3QiLAogICAgICAgICAgICBhc3NldC10eXBlOiAiZnQiLAogICAgICAgICAgICBsMS1jb250cmFjdDogKGNvbnRyYWN0LW9mIGZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgbDItY29udHJhY3Q6IGwyLWNvbnRyYWN0CiAgICAgICAgfSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IFJlZ2lzdGVyIGEgbmV3IE5GVCBjb250cmFjdCB0byBiZSBzdXBwb3J0ZWQgYnkgdGhpcyBzdWJuZXQuCihkZWZpbmUtcHVibGljIChyZWdpc3Rlci1uZXctbmZ0LWNvbnRyYWN0IChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pIChsMi1jb250cmFjdCBwcmluY2lwYWwpKQogICAgKGJlZ2luCiAgICAgICAgOzsgVmVyaWZ5IHRoYXQgdHgtc2VuZGVyIGlzIGFuIGF1dGhvcml6ZWQgYWRtaW4KICAgICAgICAoYXNzZXJ0cyEgKGlzLWFkbWluIHR4LXNlbmRlcikgKGVyciBFUlJfSU5WQUxJRF9NSU5FUikpCgogICAgICAgIDs7IFNldCB1cCB0aGUgYXNzZXRzIHRoYXQgdGhlIGNvbnRyYWN0IGlzIGFsbG93ZWQgdG8gdHJhbnNmZXIKICAgICAgICAoYXNzZXJ0cyEgKG1hcC1pbnNlcnQgYWxsb3dlZC1jb250cmFjdHMgKGNvbnRyYWN0LW9mIG5mdC1jb250cmFjdCkgbDItY29udHJhY3QpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX0FTU0VUX0FMUkVBRFlfQUxMT1dFRCkpCgogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAicmVnaXN0ZXItY29udHJhY3QiLAogICAgICAgICAgICBhc3NldC10eXBlOiAibmZ0IiwKICAgICAgICAgICAgbDEtY29udHJhY3Q6IChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpLAogICAgICAgICAgICBsMi1jb250cmFjdDogbDItY29udHJhY3QKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKOzsgSGVscGVyIGZ1bmN0aW9uOiByZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGdpdmVuIHByaW5jaXBhbCBpcyBhIG1pbmVyCjs7IFJldHVybnMgYm9vbAooZGVmaW5lLXByaXZhdGUgKGlzLW1pbmVyIChtaW5lci10by1jaGVjayBwcmluY2lwYWwpKQogICAgKGlzLWVxIG1pbmVyLXRvLWNoZWNrICh2YXItZ2V0IG1pbmVyKSkKKQoKOzsgSGVscGVyIGZ1bmN0aW9uOiByZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGdpdmVuIHByaW5jaXBhbCBpcyBhbiBhZG1pbgo7OyBSZXR1cm5zIGJvb2wKKGRlZmluZS1wcml2YXRlIChpcy1hZG1pbiAoYWRkci10by1jaGVjayBwcmluY2lwYWwpKQogICAgKGlzLWVxIGFkZHItdG8tY2hlY2sgKHZhci1nZXQgYWRtaW4pKQopCgo7OyBIZWxwZXIgZnVuY3Rpb246IGRldGVybWluZXMgd2hldGhlciB0aGUgY29tbWl0LWJsb2NrIG9wZXJhdGlvbiBzYXRpc2ZpZXMgcHJlLWNvbmRpdGlvbnMKOzsgbGlzdGVkIGluIGBjb21taXQtYmxvY2tgLgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wcml2YXRlIChjYW4tY29tbWl0LWJsb2NrPyAoY29tbWl0LWJsb2NrLWhlaWdodCB1aW50KSAgKHRhcmdldC1jaGFpbi10aXAgKGJ1ZmYgMzIpKSkKICAgIChiZWdpbgogICAgICAgIDs7IGNoZWNrIG5vIGJsb2NrIGhhcyBiZWVuIGNvbW1pdHRlZCBhdCB0aGlzIGhlaWdodAogICAgICAgIChhc3NlcnRzISAoaXMtbm9uZSAobWFwLWdldD8gYmxvY2stY29tbWl0cyBjb21taXQtYmxvY2staGVpZ2h0KSkgKGVyciBFUlJfQkxPQ0tfQUxSRUFEWV9DT01NSVRURUQpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IGB0YXJnZXQtY2hhaW4tdGlwYCBtYXRjaGVzIHRoZSBidXJuIGNoYWluIHRpcAogICAgICAgIChhc3NlcnRzISAoaXMtZXEKICAgICAgICAgICAgdGFyZ2V0LWNoYWluLXRpcAogICAgICAgICAgICAodW53cmFwISAoZ2V0LWJsb2NrLWluZm8/IGlkLWhlYWRlci1oYXNoICgtIGJsb2NrLWhlaWdodCB1MSkpIChlcnIgRVJSX0NBTExFRF9UT09fRUFSTFkpKSApCiAgICAgICAgICAgIChlcnIgRVJSX0lOVkFMSURfQ0hBSU5fVElQKSkKCiAgICAgICAgOzsgY2hlY2sgdGhhdCB0aGUgdHggc2VuZGVyIGlzIG9uZSBvZiB0aGUgbWluZXJzCiAgICAgICAgKGFzc2VydHMhIChpcy1taW5lciB0eC1zZW5kZXIpIChlcnIgRVJSX0lOVkFMSURfTUlORVIpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSBtaW5lciBjYWxsZWQgdGhpcyBjb250cmFjdCBkaXJlY3RseQogICAgICAgIChhc3NlcnRzISAoaXMtbWluZXIgY29udHJhY3QtY2FsbGVyKSAoZXJyIEVSUl9JTlZBTElEX01JTkVSKSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEhlbHBlciBmdW5jdGlvbjogbW9kaWZpZXMgdGhlIGJsb2NrLWNvbW1pdHMgbWFwIHdpdGggYSBuZXcgY29tbWl0IGFuZCBwcmludHMgcmVsYXRlZCBpbmZvCjs7IFJldHVybnMgcmVzcG9uc2U8KGJ1ZmYgMzIpLCA/PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLWNvbW1pdC1ibG9jawogICAgICAgIChibG9jayAoYnVmZiAzMikpCiAgICAgICAgKGNvbW1pdC1ibG9jay1oZWlnaHQgdWludCkKICAgICAgICAodGFyZ2V0LWJ1cm4tYmxvY2staGVpZ2h0IHVpbnQpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICApCiAgICAoYmVnaW4KICAgICAgICAobWFwLXNldCBibG9jay1jb21taXRzIGNvbW1pdC1ibG9jay1oZWlnaHQgYmxvY2spCiAgICAgICAgKG1hcC1zZXQgd2l0aGRyYXdhbC1yb290cy1tYXAgd2l0aGRyYXdhbC1yb290IHRydWUpCiAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgZXZlbnQ6ICJibG9jay1jb21taXQiLAogICAgICAgICAgICBibG9jay1jb21taXQ6IGJsb2NrLAogICAgICAgICAgICBibG9jay1oZWlnaHQ6IGNvbW1pdC1ibG9jay1oZWlnaHQsCiAgICAgICAgICAgIHdpdGhkcmF3YWwtcm9vdDogd2l0aGRyYXdhbC1yb290LAogICAgICAgICAgICB0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQ6IHRhcmdldC1idXJuLWJsb2NrLWhlaWdodAogICAgICAgIH0pCiAgICAgICAgKG9rIGJsb2NrKQogICAgKQopCgo7OyBUaGUgc3VibmV0IG1pbmVyIGNhbGxzIHRoaXMgZnVuY3Rpb24gdG8gY29tbWl0IGEgYmxvY2sgYXQgYSBwYXJ0aWN1bGFyIGhlaWdodC4KOzsgYGJsb2NrYCBpcyB0aGUgaGFzaCBvZiB0aGUgYmxvY2sgYmVpbmcgc3VibWl0dGVkLgo7OyBgdGFyZ2V0LWNoYWluLXRpcGAgaXMgdGhlIGBpZC1oZWFkZXItaGFzaGAgb2YgdGhlIGJ1cm4gYmxvY2sgKGkuZS4sIGJsb2NrIG9uCjs7ICAgIHRoaXMgY2hhaW4pIHRoYXQgdGhlIG1pbmVyIGludGVuZHMgdG8gYnVpbGQgb2ZmLgo7Owo7OyBGYWlscyBpZjoKOzsgIDEpIHdlIGhhdmUgYWxyZWFkeSBjb21taXR0ZWQgYXQgdGhpcyBibG9jayBoZWlnaHQKOzsgIDIpIGB0YXJnZXQtY2hhaW4tdGlwYCBpcyBub3QgdGhlIGJ1cm4gY2hhaW4gdGlwIChpLmUuLCBvbiB0aGlzIGNoYWluKQo7OyAgMykgdGhlIHNlbmRlciBpcyBub3QgYSBtaW5lcgooZGVmaW5lLXB1YmxpYyAoY29tbWl0LWJsb2NrCiAgICAgICAgKGJsb2NrIChidWZmIDMyKSkKICAgICAgICAoY29tbWl0LWJsb2NrLWhlaWdodCB1aW50KQogICAgICAgICh0YXJnZXQtY2hhaW4tdGlwIChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICkKICAgIChsZXQgKCh0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQgYmxvY2staGVpZ2h0KSkKICAgICAgICAodHJ5ISAoY2FuLWNvbW1pdC1ibG9jaz8gdGFyZ2V0LWJ1cm4tYmxvY2staGVpZ2h0IHRhcmdldC1jaGFpbi10aXApKQogICAgICAgIChpbm5lci1jb21taXQtYmxvY2sgYmxvY2sgY29tbWl0LWJsb2NrLWhlaWdodCB0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQgd2l0aGRyYXdhbC1yb290KQogICAgKQopCgo7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Cjs7IEZPUiBORlQgQVNTRVQgVFJBTlNGRVJTCgo7OyBIZWxwZXIgZnVuY3Rpb24gdGhhdCB0cmFuc2ZlcnMgdGhlIHNwZWNpZmllZCBORlQgZnJvbSB0aGUgZ2l2ZW4gc2VuZGVyIHRvIHRoZSBnaXZlbiByZWNpcGllbnQuCjs7IFJldHVybnMgcmVzcG9uc2U8Ym9vbCwgaW50PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChjb250cmFjdC1jYWxsPyBuZnQtY29udHJhY3QgdHJhbnNmZXIgaWQgc2VuZGVyIHJlY2lwaWVudCkpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICApCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdHJhbnNmZXIgc3VjY2VlZGVkCiAgICAgICAgKGFzc2VydHMhIHRyYW5zZmVyLXJlc3VsdCAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtbWludC1jb250cmFjdCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChhcy1jb250cmFjdCAoY29udHJhY3QtY2FsbD8gbmZ0LW1pbnQtY29udHJhY3QgbWludC1mcm9tLXN1Ym5ldCBpZCBzZW5kZXIgcmVjaXBpZW50KSkpCiAgICAgICAgICAgIChtaW50LXJlc3VsdCAodW53cmFwISBjYWxsLXJlc3VsdCAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICkKICAgICAgICA7OyBDaGVjayB0aGF0IHRoZSB0cmFuc2ZlciBzdWNjZWVkZWQKICAgICAgICAoYXNzZXJ0cyEgbWludC1yZXN1bHQgKGVyciBFUlJfTUlOVF9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci10cmFuc2Zlci1vci1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKG5mdC1taW50LWNvbnRyYWN0IDxtaW50LWZyb20tc3VibmV0LXRyYWl0PikKICAgICAgICAoaWQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY2FsbC1yZXN1bHQgKGNvbnRyYWN0LWNhbGw/IG5mdC1jb250cmFjdCBnZXQtb3duZXIgaWQpKQogICAgICAgICAgICAobmZ0LW93bmVyICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgICAgIChjb250cmFjdC1vd25zLW5mdCAoaXMtZXEgbmZ0LW93bmVyIChzb21lIENPTlRSQUNUX0FERFJFU1MpKSkKICAgICAgICAgICAgKG5vLW93bmVyIChpcy1lcSBuZnQtb3duZXIgbm9uZSkpCiAgICAgICAgKQoKICAgICAgICAoaWYgY29udHJhY3Qtb3ducy1uZnQKICAgICAgICAgICAgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICAgICAgICAgIChpZiBuby1vd25lcgogICAgICAgICAgICAgICAgOzsgVHJ5IG1pbnRpbmcgdGhlIGFzc2V0IGlmIHRoZXJlIGlzIG5vIGV4aXN0aW5nIG93bmVyIG9mIHRoaXMgTkZUCiAgICAgICAgICAgICAgICAoaW5uZXItbWludC1uZnQtYXNzZXQgbmZ0LW1pbnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICAgICAgICAgICAgICA7OyBJbiB0aGlzIGNhc2UsIGEgcHJpbmNpcGFsIG90aGVyIHRoYW4gdGhpcyBjb250cmFjdCBvd25zIHRoaXMgTkZULCBzbyBtaW50aW5nIGlzIG5vdCBwb3NzaWJsZQogICAgICAgICAgICAgICAgKGVyciBFUlJfTUlOVF9GQUlMRUQpCiAgICAgICAgICAgICkKICAgICAgICApCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIGRlcG9zaXQgYW4gTkZUIGludG8gdGhlIGNvbnRyYWN0Lgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChkZXBvc2l0LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgICAgIChzdWJuZXQtY29udHJhY3QtaWQgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpKSAoZXJyIEVSUl9ESVNBTExPV0VEX0FTU0VUKSkpCiAgICAgICAgKQoKICAgICAgICA7OyBUcnkgdG8gdHJhbnNmZXIgdGhlIE5GVCB0byB0aGlzIGNvbnRyYWN0CiAgICAgICAgKGFzc2VydHMhICh0cnkhIChpbm5lci10cmFuc2Zlci1uZnQtYXNzZXQgbmZ0LWNvbnRyYWN0IGlkIHNlbmRlciBDT05UUkFDVF9BRERSRVNTKSkgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkKCiAgICAgICAgOzsgRW1pdCBhIHByaW50IGV2ZW50IC0gdGhlIG5vZGUgY29uc3VtZXMgdGhpcwogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAiZGVwb3NpdC1uZnQiLAogICAgICAgICAgICBsMS1jb250cmFjdC1pZDogKGFzLWNvbnRyYWN0IG5mdC1jb250cmFjdCksCiAgICAgICAgICAgIG5mdC1pZDogaWQsCiAgICAgICAgICAgIHNlbmRlcjogc2VuZGVyLAogICAgICAgICAgICBzdWJuZXQtY29udHJhY3QtaWQ6IHN1Ym5ldC1jb250cmFjdC1pZCwKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7IEhlbHBlciBmdW5jdGlvbiBmb3IgYHdpdGhkcmF3LW5mdC1hc3NldGAKOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChpbm5lci13aXRoZHJhdy1uZnQtYXNzZXQKICAgICAgICAobmZ0LWNvbnRyYWN0IDxuZnQtdHJhaXQ+KQogICAgICAgIChsMi1jb250cmFjdCBwcmluY2lwYWwpCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKG5mdC1taW50LWNvbnRyYWN0IChvcHRpb25hbCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pKQogICAgICAgICh3aXRoZHJhd2FsLXJvb3QgKGJ1ZmYgMzIpKQogICAgICAgICh3aXRoZHJhd2FsLWxlYWYtaGFzaCAoYnVmZiAzMikpCiAgICAgICAgKHNpYmxpbmctaGFzaGVzIChsaXN0IDUwIHsKICAgICAgICAgICAgaGFzaDogKGJ1ZmYgMzIpLAogICAgICAgICAgICBpcy1sZWZ0LXNpZGU6IGJvb2wsCiAgICAgICAgfSkpCiAgICApCiAgICAobGV0ICgoaGFzaGVzLWFyZS12YWxpZCAoY2hlY2std2l0aGRyYXdhbC1oYXNoZXMgd2l0aGRyYXdhbC1yb290IHdpdGhkcmF3YWwtbGVhZi1oYXNoIHNpYmxpbmctaGFzaGVzKSkpCgogICAgICAgIChhc3NlcnRzISAodHJ5ISBoYXNoZXMtYXJlLXZhbGlkKSAoZXJyIEVSUl9WQUxJREFUSU9OX0ZBSUxFRCkpCgogICAgICAgIDs7IGNoZWNrIHRoYXQgdGhlIHdpdGhkcmF3YWwgcmVxdWVzdCBkYXRhIG1hdGNoZXMgdGhlIHN1cHBsaWVkIGxlYWYgaGFzaAogICAgICAgIChhc3NlcnRzISAoaXMtZXEgd2l0aGRyYXdhbC1sZWFmLWhhc2gKICAgICAgICAgICAgICAgICAgICAgICAgIChsZWFmLWhhc2gtd2l0aGRyYXctbmZ0IGwyLWNvbnRyYWN0IGlkIHJlY2lwaWVudCB3aXRoZHJhd2FsLWlkIGhlaWdodCkpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEKICAgICAgICAgICAgICAgIChtYXRjaCBuZnQtbWludC1jb250cmFjdAogICAgICAgICAgICAgICAgICAgIG1pbnQtY29udHJhY3QgKGFzLWNvbnRyYWN0IChpbm5lci10cmFuc2Zlci1vci1taW50LW5mdC1hc3NldCBuZnQtY29udHJhY3QgbWludC1jb250cmFjdCBpZCByZWNpcGllbnQpKQogICAgICAgICAgICAgICAgICAgIChhcy1jb250cmFjdCAoaW5uZXItdHJhbnNmZXItd2l0aG91dC1taW50LW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgcmVjaXBpZW50KSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKGZpbmlzaC13aXRoZHJhdyB7IHdpdGhkcmF3YWwtbGVhZi1oYXNoOiB3aXRoZHJhd2FsLWxlYWYtaGFzaCwgd2l0aGRyYXdhbC1yb290LWhhc2g6IHdpdGhkcmF3YWwtcm9vdCB9KQogICAgICAgICAgICAoZXJyIEVSUl9XSVRIRFJBV0FMX0FMUkVBRFlfUFJPQ0VTU0VEKQogICAgICAgICkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIHdpdGhkcmF3IHRoZSBzcGVjaWZpZWQgTkZUIGZyb20gdGhpcyBjb250cmFjdC4KOzsgSW4gb3JkZXIgZm9yIHRoaXMgd2l0aGRyYXdhbCB0byBnbyB0aHJvdWdoLCB0aGUgZ2l2ZW4gd2l0aGRyYXdhbCBtdXN0IGhhdmUgYmVlbiBpbmNsdWRlZAo7OyBpbiBhIHdpdGhkcmF3YWwgTWVya2xlIHRyZWUgYSBzdWJuZXQgbWluZXIgc3VibWl0dGVkLiBUaGUgdXNlciBtdXN0IHByb3ZpZGUgdGhlIGxlYWYKOzsgaGFzaCBvZiB0aGVpciB3aXRoZHJhd2FsIGFuZCB0aGUgcm9vdCBoYXNoIG9mIHRoZSBzcGVjaWZpYyBNZXJrbGUgdHJlZSB0aGVpciB3aXRoZHJhd2FsCjs7IGlzIGluY2x1ZGVkIGluLiBUaGV5IG11c3QgYWxzbyBwcm92aWRlIGEgbGlzdCBvZiBzaWJsaW5nIGhhc2hlcy4gVGhlIHdpdGhkcmF3IGZ1bmN0aW9uCjs7IHVzZXMgdGhlIHByb3ZpZGVkIGhhc2hlcyB0byBlbnN1cmUgdGhlIHJlcXVlc3RlZCB3aXRoZHJhd2FsIGlzIHZhbGlkLgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljICh3aXRoZHJhdy1uZnQtYXNzZXQKICAgICAgICAobmZ0LWNvbnRyYWN0IDxuZnQtdHJhaXQ+KQogICAgICAgIChpZCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgICh3aXRoZHJhd2FsLWlkIHVpbnQpCiAgICAgICAgKGhlaWdodCB1aW50KQogICAgICAgIChuZnQtbWludC1jb250cmFjdCAob3B0aW9uYWwgPG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQ+KSkKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1sZWFmLWhhc2ggKGJ1ZmYgMzIpKQogICAgICAgIChzaWJsaW5nLWhhc2hlcyAobGlzdCA1MCB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIGFzc2V0IGJlbG9uZ3MgdG8gdGhlIGFsbG93ZWQtY29udHJhY3RzIG1hcAogICAgICAgICAgICAobDItY29udHJhY3QgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpKSAoZXJyIEVSUl9ESVNBTExPV0VEX0FTU0VUKSkpCiAgICAgICAgKQogICAgICAgIChhc3NlcnRzIQogICAgICAgICAgICAodHJ5ISAoaW5uZXItd2l0aGRyYXctbmZ0LWFzc2V0CiAgICAgICAgICAgICAgICBuZnQtY29udHJhY3QKICAgICAgICAgICAgICAgIGwyLWNvbnRyYWN0CiAgICAgICAgICAgICAgICBpZAogICAgICAgICAgICAgICAgcmVjaXBpZW50CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLWlkCiAgICAgICAgICAgICAgICBoZWlnaHQKICAgICAgICAgICAgICAgIG5mdC1taW50LWNvbnRyYWN0CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLXJvb3QKICAgICAgICAgICAgICAgIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICBzaWJsaW5nLWhhc2hlcwogICAgICAgICAgICApKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAocHJpbnQgewogICAgICAgICAgICBldmVudDogIndpdGhkcmF3LW5mdCIsCiAgICAgICAgICAgIGwxLWNvbnRyYWN0LWlkOiAoYXMtY29udHJhY3QgbmZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgbmZ0LWlkOiBpZCwKICAgICAgICAgICAgcmVjaXBpZW50OiByZWNpcGllbnQKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7IExpa2UgYGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtbmZ0LWFzc2V0IGJ1dCB3aXRob3V0IGFsbG93aW5nIG9yIHJlcXVpcmluZyBhIG1pbnQgZnVuY3Rpb24uIEluIG9yZGVyIHRvIHdpdGhkcmF3LCB0aGUgdXNlciBtdXN0Cjs7IGhhdmUgdGhlIGFwcHJvcHJpYXRlIGJhbGFuY2UuCihkZWZpbmUtcHJpdmF0ZSAoaW5uZXItdHJhbnNmZXItd2l0aG91dC1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChjb250cmFjdC1jYWxsPyBuZnQtY29udHJhY3QgZ2V0LW93bmVyIGlkKSkKICAgICAgICAgICAgKG5mdC1vd25lciAodW53cmFwISBjYWxsLXJlc3VsdCAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICAgICAoY29udHJhY3Qtb3ducy1uZnQgKGlzLWVxIG5mdC1vd25lciAoc29tZSBDT05UUkFDVF9BRERSRVNTKSkpCiAgICAgICAgKQoKICAgICAgICAoYXNzZXJ0cyEgY29udHJhY3Qtb3ducy1uZnQgKGVyciBFUlJfTkZUX05PVF9PV05FRF9CWV9DT05UUkFDVCkpCiAgICAgICAgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICApCikKCjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OzsKOzsgRk9SIEZVTkdJQkxFIFRPS0VOIEFTU0VUIFRSQU5TRkVSUwoKOzsgSGVscGVyIGZ1bmN0aW9uIHRoYXQgdHJhbnNmZXJzIGEgc3BlY2lmaWVkIGFtb3VudCBvZiB0aGUgZnVuZ2libGUgdG9rZW4gZnJvbSB0aGUgZ2l2ZW4gc2VuZGVyIHRvIHRoZSBnaXZlbiByZWNpcGllbnQuCjs7IFJldHVybnMgcmVzcG9uc2U8Ym9vbCwgaW50PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLWZ0LWFzc2V0CiAgICAgICAgKGZ0LWNvbnRyYWN0IDxmdC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChzZW5kZXIgcHJpbmNpcGFsKQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoY29udHJhY3QtY2FsbD8gZnQtY29udHJhY3QgdHJhbnNmZXIgYW1vdW50IHNlbmRlciByZWNpcGllbnQgbWVtbykpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICApCiAgICAgICAgOzsgRklYTUU6IFNJUC0wMTAgZG9lc24ndCByZXF1aXJlIHRoYXQgdHJhbnNmZXIgcmV0dXJucyAob2sgdHJ1ZSkgb24gc3VjY2Vzcywgc28gaXMgdGhpcyBjaGVjayBuZWNlc3Nhcnk/CiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdHJhbnNmZXIgc3VjY2VlZGVkCiAgICAgICAgKGFzc2VydHMhIHRyYW5zZmVyLXJlc3VsdCAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci1taW50LWZ0LWFzc2V0CiAgICAgICAgKGZ0LW1pbnQtY29udHJhY3QgPG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQ+KQogICAgICAgIChhbW91bnQgdWludCkKICAgICAgICAoc2VuZGVyIHByaW5jaXBhbCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY2FsbC1yZXN1bHQgKGFzLWNvbnRyYWN0IChjb250cmFjdC1jYWxsPyBmdC1taW50LWNvbnRyYWN0IG1pbnQtZnJvbS1zdWJuZXQgYW1vdW50IHNlbmRlciByZWNpcGllbnQpKSkKICAgICAgICAgICAgKG1pbnQtcmVzdWx0ICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgKQogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRyYW5zZmVyIHN1Y2NlZWRlZAogICAgICAgIChhc3NlcnRzISBtaW50LXJlc3VsdCAoZXJyIEVSUl9NSU5UX0ZBSUxFRCkpCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoZnQtbWludC1jb250cmFjdCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoY29udHJhY3QtY2FsbD8gZnQtY29udHJhY3QgZ2V0LWJhbGFuY2UgQ09OVFJBQ1RfQUREUkVTUykpCiAgICAgICAgICAgIChjb250cmFjdC1mdC1iYWxhbmNlICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgICAgIChjb250cmFjdC1vd25zLWVub3VnaCAoPj0gY29udHJhY3QtZnQtYmFsYW5jZSBhbW91bnQpKQogICAgICAgICAgICAoYW1vdW50LXRvLXRyYW5zZmVyIChpZiBjb250cmFjdC1vd25zLWVub3VnaCBhbW91bnQgY29udHJhY3QtZnQtYmFsYW5jZSkpCiAgICAgICAgICAgIChhbW91bnQtdG8tbWludCAoLSBhbW91bnQgYW1vdW50LXRvLXRyYW5zZmVyKSkKICAgICAgICApCgogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRvdGFsIGJhbGFuY2UgYmV0d2VlbiB0aGUgdHJhbnNmZXIgYW5kIG1pbnQgaXMgZXF1YWwgdG8gdGhlIG9yaWdpbmFsIGJhbGFuY2UKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIGFtb3VudCAoKyBhbW91bnQtdG8tdHJhbnNmZXIgYW1vdW50LXRvLW1pbnQpKSAoZXJyIEVSUl9JTl9DT01QVVRBVElPTikpCgogICAgICAgIChhbmQKICAgICAgICAgICAgKD4gYW1vdW50LXRvLXRyYW5zZmVyIHUwKQogICAgICAgICAgICAodHJ5ISAoaW5uZXItdHJhbnNmZXItZnQtYXNzZXQgZnQtY29udHJhY3QgYW1vdW50LXRvLXRyYW5zZmVyIENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICkKICAgICAgICAoYW5kCiAgICAgICAgICAgICg+IGFtb3VudC10by1taW50IHUwKQogICAgICAgICAgICAodHJ5ISAoaW5uZXItbWludC1mdC1hc3NldCBmdC1taW50LWNvbnRyYWN0IGFtb3VudC10by1taW50IENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50KSkKICAgICAgICApCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgo7OyBBIHVzZXIgY2FsbHMgdGhpcyBmdW5jdGlvbiB0byBkZXBvc2l0IGEgZnVuZ2libGUgdG9rZW4gaW50byB0aGUgY29udHJhY3QuCjs7IFRoZSBmdW5jdGlvbiBlbWl0cyBhIHByaW50IHdpdGggZGV0YWlscyBvZiB0aGlzIGV2ZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wdWJsaWMgKGRlcG9zaXQtZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKG1lbW8gKG9wdGlvbmFsIChidWZmIDM0KSkpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgICAgIChzdWJuZXQtY29udHJhY3QtaWQgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkpIChlcnIgRVJSX0RJU0FMTE9XRURfQVNTRVQpKSkKICAgICAgICApCiAgICAgICAgOzsgVHJ5IHRvIHRyYW5zZmVyIHRoZSBGVCB0byB0aGlzIGNvbnRyYWN0CiAgICAgICAgKGFzc2VydHMhICh0cnkhIChpbm5lci10cmFuc2Zlci1mdC1hc3NldCBmdC1jb250cmFjdCBhbW91bnQgc2VuZGVyIENPTlRSQUNUX0FERFJFU1MgbWVtbykpIChlcnIgRVJSX1RSQU5TRkVSX0ZBSUxFRCkpCgogICAgICAgIChsZXQgKAogICAgICAgICAgICAgICAgKGZ0LW5hbWUgKHVud3JhcCEgKGNvbnRyYWN0LWNhbGw/IGZ0LWNvbnRyYWN0IGdldC1uYW1lKSAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICAgICApCiAgICAgICAgICAgIDs7IEVtaXQgYSBwcmludCBldmVudCAtIHRoZSBub2RlIGNvbnN1bWVzIHRoaXMKICAgICAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgICAgIGV2ZW50OiAiZGVwb3NpdC1mdCIsCiAgICAgICAgICAgICAgICBsMS1jb250cmFjdC1pZDogKGFzLWNvbnRyYWN0IGZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgICAgIGZ0LW5hbWU6IGZ0LW5hbWUsCiAgICAgICAgICAgICAgICBmdC1hbW91bnQ6IGFtb3VudCwKICAgICAgICAgICAgICAgIHNlbmRlcjogc2VuZGVyLAogICAgICAgICAgICAgICAgc3VibmV0LWNvbnRyYWN0LWlkOiBzdWJuZXQtY29udHJhY3QtaWQsCiAgICAgICAgICAgIH0pCiAgICAgICAgKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKOzsgVGhpcyBmdW5jdGlvbiBwZXJmb3JtcyB2YWxpZGl0eSBjaGVja3MgcmVsYXRlZCB0byB0aGUgd2l0aGRyYXdhbCBhbmQgcGVyZm9ybXMgdGhlIHdpdGhkcmF3YWwgYXMgd2VsbC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHJpdmF0ZSAoaW5uZXItd2l0aGRyYXctZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKG1lbW8gKG9wdGlvbmFsIChidWZmIDM0KSkpCiAgICAgICAgKGZ0LW1pbnQtY29udHJhY3QgKG9wdGlvbmFsIDxtaW50LWZyb20tc3VibmV0LXRyYWl0PikpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICAgICAgKHdpdGhkcmF3YWwtbGVhZi1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoZXMgKGxpc3QgNTAgewogICAgICAgICAgICBoYXNoOiAoYnVmZiAzMiksCiAgICAgICAgICAgIGlzLWxlZnQtc2lkZTogYm9vbCwKICAgICAgICB9KSkKICAgICkKICAgIChsZXQgKChoYXNoZXMtYXJlLXZhbGlkIChjaGVjay13aXRoZHJhd2FsLWhhc2hlcyB3aXRoZHJhd2FsLXJvb3Qgd2l0aGRyYXdhbC1sZWFmLWhhc2ggc2libGluZy1oYXNoZXMpKSkKICAgICAgICAoYXNzZXJ0cyEgKHRyeSEgaGFzaGVzLWFyZS12YWxpZCkgKGVyciBFUlJfVkFMSURBVElPTl9GQUlMRUQpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSB3aXRoZHJhd2FsIHJlcXVlc3QgZGF0YSBtYXRjaGVzIHRoZSBzdXBwbGllZCBsZWFmIGhhc2gKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICAgICAgICAgICAobGVhZi1oYXNoLXdpdGhkcmF3LWZ0IChjb250cmFjdC1vZiBmdC1jb250cmFjdCkgYW1vdW50IHJlY2lwaWVudCB3aXRoZHJhd2FsLWlkIGhlaWdodCkpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEKICAgICAgICAgICAgICAgIChtYXRjaCBmdC1taW50LWNvbnRyYWN0CiAgICAgICAgICAgICAgICAgICAgbWludC1jb250cmFjdCAoYXMtY29udHJhY3QgKGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtZnQtYXNzZXQgZnQtY29udHJhY3QgbWludC1jb250cmFjdCBhbW91bnQgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICAgICAgICAgICAgIChhcy1jb250cmFjdCAoaW5uZXItdHJhbnNmZXItZnQtYXNzZXQgZnQtY29udHJhY3QgYW1vdW50IENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICApCiAgICAgICAgICAgIChlcnIgRVJSX1RSQU5TRkVSX0ZBSUxFRCkKICAgICAgICApCgogICAgICAgIChhc3NlcnRzIQogICAgICAgICAgKGZpbmlzaC13aXRoZHJhdyB7IHdpdGhkcmF3YWwtbGVhZi1oYXNoOiB3aXRoZHJhd2FsLWxlYWYtaGFzaCwgd2l0aGRyYXdhbC1yb290LWhhc2g6IHdpdGhkcmF3YWwtcm9vdCB9KQogICAgICAgICAgKGVyciBFUlJfV0lUSERSQVdBTF9BTFJFQURZX1BST0NFU1NFRCkpCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgo7OyBBIHVzZXIgY2FuIGNhbGwgdGhpcyBmdW5jdGlvbiB0byB3aXRoZHJhdyBzb21lIGFtb3VudCBvZiBhIGZ1bmdpYmxlIHRva2VuIGFzc2V0IGZyb20gdGhlCjs7IGNvbnRyYWN0IGFuZCBzZW5kIGl0IHRvIGEgcmVjaXBpZW50Lgo7OyBJbiBvcmRlciBmb3IgdGhpcyB3aXRoZHJhd2FsIHRvIGdvIHRocm91Z2gsIHRoZSBnaXZlbiB3aXRoZHJhd2FsIG11c3QgaGF2ZSBiZWVuIGluY2x1ZGVkCjs7IGluIGEgd2l0aGRyYXdhbCBNZXJrbGUgdHJlZSBhIHN1Ym5ldCBtaW5lciBzdWJtaXR0ZWQuIFRoZSB1c2VyIG11c3QgcHJvdmlkZSB0aGUgbGVhZgo7OyBoYXNoIG9mIHRoZWlyIHdpdGhkcmF3YWwgYW5kIHRoZSByb290IGhhc2ggb2YgdGhlIHNwZWNpZmljIE1lcmtsZSB0cmVlIHRoZWlyIHdpdGhkcmF3YWwKOzsgaXMgaW5jbHVkZWQgaW4uIFRoZXkgbXVzdCBhbHNvIHByb3ZpZGUgYSBsaXN0IG9mIHNpYmxpbmcgaGFzaGVzLiBUaGUgd2l0aGRyYXcgZnVuY3Rpb24KOzsgdXNlcyB0aGUgcHJvdmlkZWQgaGFzaGVzIHRvIGVuc3VyZSB0aGUgcmVxdWVzdGVkIHdpdGhkcmF3YWwgaXMgdmFsaWQuCjs7IFRoZSBmdW5jdGlvbiBlbWl0cyBhIHByaW50IHdpdGggZGV0YWlscyBvZiB0aGlzIGV2ZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wdWJsaWMgKHdpdGhkcmF3LWZ0LWFzc2V0CiAgICAgICAgKGZ0LWNvbnRyYWN0IDxmdC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgICh3aXRoZHJhd2FsLWlkIHVpbnQpCiAgICAgICAgKGhlaWdodCB1aW50KQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgICAgIChmdC1taW50LWNvbnRyYWN0IChvcHRpb25hbCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pKQogICAgICAgICh3aXRoZHJhd2FsLXJvb3QgKGJ1ZmYgMzIpKQogICAgICAgICh3aXRoZHJhd2FsLWxlYWYtaGFzaCAoYnVmZiAzMikpCiAgICAgICAgKHNpYmxpbmctaGFzaGVzIChsaXN0IDUwIHsKICAgICAgICAgICAgaGFzaDogKGJ1ZmYgMzIpLAogICAgICAgICAgICBpcy1sZWZ0LXNpZGU6IGJvb2wsCiAgICAgICAgfSkpCiAgICApCiAgICAoYmVnaW4KICAgICAgICA7OyBDaGVjayB0aGF0IHRoZSB3aXRoZHJhdyBhbW91bnQgaXMgcG9zaXRpdmUKICAgICAgICAoYXNzZXJ0cyEgKD4gYW1vdW50IHUwKSAoZXJyIEVSUl9BVFRFTVBUX1RPX1RSQU5TRkVSX1pFUk9fQU1PVU5UKSkKCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkpIChlcnIgRVJSX0RJU0FMTE9XRURfQVNTRVQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEgKGlubmVyLXdpdGhkcmF3LWZ0LWFzc2V0CiAgICAgICAgICAgICAgICBmdC1jb250cmFjdAogICAgICAgICAgICAgICAgYW1vdW50CiAgICAgICAgICAgICAgICByZWNpcGllbnQKICAgICAgICAgICAgICAgIHdpdGhkcmF3YWwtaWQKICAgICAgICAgICAgICAgIGhlaWdodAogICAgICAgICAgICAgICAgbWVtbwogICAgICAgICAgICAgICAgZnQtbWludC1jb250cmFjdAogICAgICAgICAgICAgICAgd2l0aGRyYXdhbC1yb290CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLWxlYWYtaGFzaAogICAgICAgICAgICAgICAgc2libGluZy1oYXNoZXMpKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICAobGV0ICgKICAgICAgICAgICAgICAgIChmdC1uYW1lICh1bndyYXAhIChjb250cmFjdC1jYWxsPyBmdC1jb250cmFjdCBnZXQtbmFtZSkgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICAgICAgKQogICAgICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgICAgIGV2ZW50OiAid2l0aGRyYXctZnQiLAogICAgICAgICAgICAgICAgbDEtY29udHJhY3QtaWQ6IChhcy1jb250cmFjdCBmdC1jb250cmFjdCksCiAgICAgICAgICAgICAgICBmdC1uYW1lOiBmdC1uYW1lLAogICAgICAgICAgICAgICAgZnQtYW1vdW50OiBhbW91bnQsCiAgICAgICAgICAgICAgICByZWNpcGllbnQ6IHJlY2lwaWVudCwKICAgICAgICAgICAgfSkKICAgICAgICApCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgoKOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Cjs7IEZPUiBTVFggVFJBTlNGRVJTCgoKOzsgSGVscGVyIGZ1bmN0aW9uIHRoYXQgdHJhbnNmZXJzIHRoZSBnaXZlbiBhbW91bnQgZnJvbSB0aGUgc3BlY2lmaWVkIGZ1bmdpYmxlIHRva2VuIGZyb20gdGhlIGdpdmVuIHNlbmRlciB0byB0aGUgZ2l2ZW4gcmVjaXBpZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wcml2YXRlIChpbm5lci10cmFuc2Zlci1zdHggKGFtb3VudCB1aW50KSAoc2VuZGVyIHByaW5jaXBhbCkgKHJlY2lwaWVudCBwcmluY2lwYWwpKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoc3R4LXRyYW5zZmVyPyBhbW91bnQgc2VuZGVyIHJlY2lwaWVudCkpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkpCiAgICAgICAgKQogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRyYW5zZmVyIHN1Y2NlZWRlZAogICAgICAgIChhc3NlcnRzISB0cmFuc2Zlci1yZXN1bHQgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIGRlcG9zaXQgU1RYIGludG8gdGhlIGNvbnRyYWN0Lgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChkZXBvc2l0LXN0eCAoYW1vdW50IHVpbnQpIChzZW5kZXIgcHJpbmNpcGFsKSkKICAgIChiZWdpbgogICAgICAgIDs7IFRyeSB0byB0cmFuc2ZlciB0aGUgU1RYIHRvIHRoaXMgY29udHJhY3QKICAgICAgICAoYXNzZXJ0cyEgKHRyeSEgKGlubmVyLXRyYW5zZmVyLXN0eCBhbW91bnQgc2VuZGVyIENPTlRSQUNUX0FERFJFU1MpKSAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQgLSB0aGUgbm9kZSBjb25zdW1lcyB0aGlzCiAgICAgICAgKHByaW50IHsgZXZlbnQ6ICJkZXBvc2l0LXN0eCIsIHNlbmRlcjogc2VuZGVyLCBhbW91bnQ6IGFtb3VudCB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGxlYWYtaGFzaC13aXRoZHJhdy1zdHgKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICApCiAgICAoc2hhNTEyLzI1NiAoY29uY2F0IDB4MDAgKHVud3JhcC1wYW5pYyAodG8tY29uc2Vuc3VzLWJ1ZmY/CiAgICAgICAgewogICAgICAgICAgICB0eXBlOiAic3R4IiwKICAgICAgICAgICAgYW1vdW50OiBhbW91bnQsCiAgICAgICAgICAgIHJlY2lwaWVudDogcmVjaXBpZW50LAogICAgICAgICAgICB3aXRoZHJhd2FsLWlkOiB3aXRoZHJhd2FsLWlkLAogICAgICAgICAgICBoZWlnaHQ6IGhlaWdodAogICAgICAgIH0pKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGxlYWYtaGFzaC13aXRoZHJhdy1uZnQKICAgICAgICAoYXNzZXQtY29udHJhY3QgcHJpbmNpcGFsKQogICAgICAgIChuZnQtaWQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICAgICAod2l0aGRyYXdhbC1pZCB1aW50KQogICAgICAgIChoZWlnaHQgdWludCkKICAgICkKICAgIChzaGE1MTIvMjU2IChjb25jYXQgMHgwMCAodW53cmFwLXBhbmljICh0by1jb25zZW5zdXMtYnVmZj8KICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJuZnQiLAogICAgICAgICAgICBuZnQtaWQ6IG5mdC1pZCwKICAgICAgICAgICAgYXNzZXQtY29udHJhY3Q6IGFzc2V0LWNvbnRyYWN0LAogICAgICAgICAgICByZWNpcGllbnQ6IHJlY2lwaWVudCwKICAgICAgICAgICAgd2l0aGRyYXdhbC1pZDogd2l0aGRyYXdhbC1pZCwKICAgICAgICAgICAgaGVpZ2h0OiBoZWlnaHQKICAgICAgICB9KSkpCiAgICApCikKCihkZWZpbmUtcmVhZC1vbmx5IChsZWFmLWhhc2gtd2l0aGRyYXctZnQKICAgICAgICAoYXNzZXQtY29udHJhY3QgcHJpbmNpcGFsKQogICAgICAgIChhbW91bnQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICAgICAod2l0aGRyYXdhbC1pZCB1aW50KQogICAgICAgIChoZWlnaHQgdWludCkKICAgICkKICAgIChzaGE1MTIvMjU2IChjb25jYXQgMHgwMCAodW53cmFwLXBhbmljICh0by1jb25zZW5zdXMtYnVmZj8KICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJmdCIsCiAgICAgICAgICAgIGFtb3VudDogYW1vdW50LAogICAgICAgICAgICBhc3NldC1jb250cmFjdDogYXNzZXQtY29udHJhY3QsCiAgICAgICAgICAgIHJlY2lwaWVudDogcmVjaXBpZW50LAogICAgICAgICAgICB3aXRoZHJhd2FsLWlkOiB3aXRoZHJhd2FsLWlkLAogICAgICAgICAgICBoZWlnaHQ6IGhlaWdodAogICAgICAgIH0pKSkKICAgICkKKQoKOzsgQSB1c2VyIGNhbGxzIHRoaXMgZnVuY3Rpb24gdG8gd2l0aGRyYXcgU1RYIGZyb20gdGhpcyBjb250cmFjdC4KOzsgSW4gb3JkZXIgZm9yIHRoaXMgd2l0aGRyYXdhbCB0byBnbyB0aHJvdWdoLCB0aGUgZ2l2ZW4gd2l0aGRyYXdhbCBtdXN0IGhhdmUgYmVlbiBpbmNsdWRlZAo7OyBpbiBhIHdpdGhkcmF3YWwgTWVya2xlIHRyZWUgYSBzdWJuZXQgbWluZXIgc3VibWl0dGVkLiBUaGUgdXNlciBtdXN0IHByb3ZpZGUgdGhlIGxlYWYKOzsgaGFzaCBvZiB0aGVpciB3aXRoZHJhd2FsIGFuZCB0aGUgcm9vdCBoYXNoIG9mIHRoZSBzcGVjaWZpYyBNZXJrbGUgdHJlZSB0aGVpciB3aXRoZHJhd2FsCjs7IGlzIGluY2x1ZGVkIGluLiBUaGV5IG11c3QgYWxzbyBwcm92aWRlIGEgbGlzdCBvZiBzaWJsaW5nIGhhc2hlcy4gVGhlIHdpdGhkcmF3IGZ1bmN0aW9uCjs7IHVzZXMgdGhlIHByb3ZpZGVkIGhhc2hlcyB0byBlbnN1cmUgdGhlIHJlcXVlc3RlZCB3aXRoZHJhd2FsIGlzIHZhbGlkLgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljICh3aXRoZHJhdy1zdHgKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICAgICAgKHdpdGhkcmF3YWwtbGVhZi1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoZXMgKGxpc3QgNTAgewogICAgICAgICAgICBoYXNoOiAoYnVmZiAzMiksCiAgICAgICAgICAgIGlzLWxlZnQtc2lkZTogYm9vbCwKICAgICAgICB9KSkKICAgICkKICAgIChsZXQgKChoYXNoZXMtYXJlLXZhbGlkIChjaGVjay13aXRoZHJhd2FsLWhhc2hlcyB3aXRoZHJhd2FsLXJvb3Qgd2l0aGRyYXdhbC1sZWFmLWhhc2ggc2libGluZy1oYXNoZXMpKSkKCiAgICAgICAgKGFzc2VydHMhICh0cnkhIGhhc2hlcy1hcmUtdmFsaWQpIChlcnIgRVJSX1ZBTElEQVRJT05fRkFJTEVEKSkKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSB3aXRoZHJhd2FsIHJlcXVlc3QgZGF0YSBtYXRjaGVzIHRoZSBzdXBwbGllZCBsZWFmIGhhc2gKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICAgICAgICAgICAobGVhZi1oYXNoLXdpdGhkcmF3LXN0eCBhbW91bnQgcmVjaXBpZW50IHdpdGhkcmF3YWwtaWQgaGVpZ2h0KSkKICAgICAgICAgICAgICAgICAgKGVyciBFUlJfVkFMSURBVElPTl9MRUFGX0ZBSUxFRCkpCgogICAgICAgIChhc3NlcnRzISAodHJ5ISAoYXMtY29udHJhY3QgKGlubmVyLXRyYW5zZmVyLXN0eCBhbW91bnQgdHgtc2VuZGVyIHJlY2lwaWVudCkpKSAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgIChmaW5pc2gtd2l0aGRyYXcgeyB3aXRoZHJhd2FsLWxlYWYtaGFzaDogd2l0aGRyYXdhbC1sZWFmLWhhc2gsIHdpdGhkcmF3YWwtcm9vdC1oYXNoOiB3aXRoZHJhd2FsLXJvb3QgfSkKICAgICAgICAgIChlcnIgRVJSX1dJVEhEUkFXQUxfQUxSRUFEWV9QUk9DRVNTRUQpKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAocHJpbnQgeyBldmVudDogIndpdGhkcmF3LXN0eCIsIHJlY2lwaWVudDogcmVjaXBpZW50LCBhbW91bnQ6IGFtb3VudCB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Owo7OyBHRU5FUkFMIFdJVEhEUkFXQUwgRlVOQ1RJT05TCgo7OyBUaGlzIGZ1bmN0aW9uIGNvbmNhdHMgdGhlIHR3byBnaXZlbiBoYXNoZXMgaW4gdGhlIGNvcnJlY3Qgb3JkZXIuIEl0IGFsc28gcHJlcGVuZHMgdGhlIGJ1ZmYgYDB4MDFgLCB3aGljaCBpcwo7OyBhIHRhZyBkZW5vdGluZyBhIG5vZGUgKHZlcnN1cyBhIGxlYWYpLgo7OyBSZXR1cm5zIGEgYnVmZgooZGVmaW5lLXByaXZhdGUgKGNyZWF0ZS1ub2RlLWhhc2gKICAgICAgICAoY3Vyci1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoIChidWZmIDMyKSkKICAgICAgICAoaXMtc2libGluZy1sZWZ0LXNpZGUgYm9vbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY29uY2F0dGVkLWhhc2ggKGlmIGlzLXNpYmxpbmctbGVmdC1zaWRlCiAgICAgICAgICAgICAgICAgICAgKGNvbmNhdCBzaWJsaW5nLWhhc2ggY3Vyci1oYXNoKQogICAgICAgICAgICAgICAgICAgIChjb25jYXQgY3Vyci1oYXNoIHNpYmxpbmctaGFzaCkKICAgICAgICAgICAgICAgICkpCiAgICAgICAgICApCgogICAgICAgICAgKGNvbmNhdCAweDAxIGNvbmNhdHRlZC1oYXNoKQogICAgKQopCgo7OyBUaGlzIGZ1bmN0aW9uIGhhc2hlcyB0aGUgY3VyciBoYXNoIHdpdGggaXRzIHNpYmxpbmcgaGFzaC4KOzsgUmV0dXJucyAoYnVmZiAzMikKKGRlZmluZS1wcml2YXRlIChoYXNoLWhlbHAKICAgICAgICAoc2libGluZyB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pCiAgICAgICAgKGN1cnItbm9kZS1oYXNoIChidWZmIDMyKSkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoc2libGluZy1oYXNoIChnZXQgaGFzaCBzaWJsaW5nKSkKICAgICAgICAgICAgKGlzLXNpYmxpbmctbGVmdC1zaWRlIChnZXQgaXMtbGVmdC1zaWRlIHNpYmxpbmcpKQogICAgICAgICAgICAobmV3LWJ1ZmYgKGNyZWF0ZS1ub2RlLWhhc2ggY3Vyci1ub2RlLWhhc2ggc2libGluZy1oYXNoIGlzLXNpYmxpbmctbGVmdC1zaWRlKSkKICAgICAgICApCiAgICAgICAoc2hhNTEyLzI1NiBuZXctYnVmZikKICAgICkKKQoKOzsgVGhpcyBmdW5jdGlvbiBjaGVja3M6Cjs7ICAtIFRoYXQgdGhlIHByb3ZpZGVkIHdpdGhkcmF3YWwgcm9vdCBtYXRjaGVzIGEgcHJldmlvdXNseSBzdWJtaXR0ZWQgb25lIChwYXNzZWQgdG8gdGhlIGZ1bmN0aW9uIGBjb21taXQtYmxvY2tgKQo7OyAgLSBUaGF0IHRoZSBjb21wdXRlZCB3aXRoZHJhd2FsIHJvb3QgbWF0Y2hlcyBhIHByZXZpb3VzIHZhbGlkIHdpdGhkcmF3YWwgcm9vdAo7OyAgLSBUaGF0IHRoZSBnaXZlbiB3aXRoZHJhd2FsIGxlYWYgaGFzaCBoYXMgbm90IGJlZW4gcHJldmlvdXNseSBwcm9jZXNzZWQKOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHJpdmF0ZSAoY2hlY2std2l0aGRyYXdhbC1oYXNoZXMKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1sZWFmLWhhc2ggKGJ1ZmYgMzIpKQogICAgICAgIChzaWJsaW5nLWhhc2hlcyAobGlzdCA1MCB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pKQogICAgKQogICAgKGJlZ2luCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdXNlciBzdWJtaXR0ZWQgYSB2YWxpZCB3aXRoZHJhd2FsIHJvb3QKICAgICAgICAoYXNzZXJ0cyEgKGlzLXNvbWUgKG1hcC1nZXQ/IHdpdGhkcmF3YWwtcm9vdHMtbWFwIHdpdGhkcmF3YWwtcm9vdCkpIChlcnIgRVJSX0lOVkFMSURfTUVSS0xFX1JPT1QpKQoKICAgICAgICA7OyBDaGVjayB0aGF0IHRoaXMgd2l0aGRyYXdhbCBsZWFmIGhhcyBub3QgYmVlbiBwcm9jZXNzZWQgYmVmb3JlCiAgICAgICAgKGFzc2VydHMhCiAgICAgICAgICAgIChpcy1ub25lCiAgICAgICAgICAgICAobWFwLWdldD8gcHJvY2Vzc2VkLXdpdGhkcmF3YWwtbGVhdmVzLW1hcAogICAgICAgICAgICAgICAgICAgICAgIHsgd2l0aGRyYXdhbC1sZWFmLWhhc2g6IHdpdGhkcmF3YWwtbGVhZi1oYXNoLCB3aXRoZHJhd2FsLXJvb3QtaGFzaDogd2l0aGRyYXdhbC1yb290IH0pKQogICAgICAgICAgICAoZXJyIEVSUl9XSVRIRFJBV0FMX0FMUkVBRFlfUFJPQ0VTU0VEKSkKCiAgICAgICAgKGxldCAoKGNhbGN1bGF0ZWQtd2l0aGRyYXdhbC1yb290IChmb2xkIGhhc2gtaGVscCBzaWJsaW5nLWhhc2hlcyB3aXRoZHJhd2FsLWxlYWYtaGFzaCkpCiAgICAgICAgICAgICAgKHJvb3RzLW1hdGNoIChpcy1lcSBjYWxjdWxhdGVkLXdpdGhkcmF3YWwtcm9vdCB3aXRoZHJhd2FsLXJvb3QpKSkKICAgICAgICAgICAgIChpZiByb290cy1tYXRjaAogICAgICAgICAgICAgICAgKG9rIHRydWUpCiAgICAgICAgICAgICAgICAoZXJyIEVSUl9NRVJLTEVfUk9PVF9ET0VTX05PVF9NQVRDSCkpCiAgICAgICAgKQogICAgKQopCgo7OyBUaGlzIGZ1bmN0aW9uIHNob3VsZCBiZSBjYWxsZWQgYWZ0ZXIgdGhlIGFzc2V0IGluIHF1ZXN0aW9uIGhhcyBiZWVuIHRyYW5zZmVycmVkLgo7OyBJdCBhZGRzIHRoZSB3aXRoZHJhd2FsIGxlYWYgaGFzaCB0byBhIG1hcCBvZiBwcm9jZXNzZWQgbGVhdmVzLiBUaGlzIGVuc3VyZXMgdGhhdAo7OyB0aGlzIHdpdGhkcmF3YWwgbGVhZiBjYW4ndCBiZSB1c2VkIGFnYWluIHRvIHdpdGhkcmF3IGFkZGl0aW9uYWwgZnVuZHMuCjs7IFJldHVybnMgYm9vbAooZGVmaW5lLXByaXZhdGUgKGZpbmlzaC13aXRoZHJhdwogICAgICAgICh3aXRoZHJhdy1pbmZvIHsKICAgICAgICAgICAgd2l0aGRyYXdhbC1sZWFmLWhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgd2l0aGRyYXdhbC1yb290LWhhc2g6IChidWZmIDMyKQogICAgICAgIH0pCiAgICApCiAgICAobWFwLWluc2VydCBwcm9jZXNzZWQtd2l0aGRyYXdhbC1sZWF2ZXMtbWFwIHdpdGhkcmF3LWluZm8gdHJ1ZSkKKQo=", + "clarity_version": 2, + "cost": 290960, + "location": { + "path": "./.cache/requirements/ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1-2.clar" + } + }, + { + "transaction_type": "ContractPublish", + "contract_name": "px", + "expected_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "location": { + "path": "contracts/px.clar" + }, + "source": "Cjs7IHRpdGxlOiBweAo7OyB2ZXJzaW9uOgo7OyBzdW1tYXJ5Ogo7OyBkZXNjcmlwdGlvbjogQWxsb3dzIHVzZXJzIHRvIHBheSB0byB1cGRhdGUgZGF0YSBpbiBhIG1hdHJpeC4gCjs7ICBFYWNoIG1hdHJpeCB2YWx1ZSBtdXN0IGJlIGEgaGV4YWRlY2ltYWwgdmFsdWUgZnJvbSAweDAwMDAwMCB0byAweGZmZmZmZiwgcmVwcmVzZW50aW5nIGEgY29sb3IgdG8gYmUgZGlzcGxheWVkIG9uIGEgZ3JpZCBpbiBhIHdlYiBwYWdlLiAKOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuCgo7OyB0cmFpdHMKOzsKCjs7IHRva2VuIGRlZmluaXRpb25zCjs7IAoKOzsgY29uc3RhbnRzCjs7CihkZWZpbmUtY29uc3RhbnQgTUFYX0xPQyB1MTAwKQooZGVmaW5lLWNvbnN0YW50IE1BWF9WQUwgMHhmZmZmZmYpCihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMCkKKGRlZmluZS1jb25zdGFudCBBTExfTE9DUyAobGlzdCB1MCB1MSB1MiB1MyB1NCB1NSB1NiB1NyB1OCB1OSB1MTAgdTExIHUxMiB1MTMgdTE0IHUxNSB1MTYgdTE3IHUxOCB1MTkgdTIwIHUyMSB1MjIgdTIzIHUyNCB1MjUgdTI2IHUyNyB1MjggdTI5IHUzMCB1MzEgdTMyIHUzMyB1MzQgdTM1IHUzNiB1MzcgdTM4IHUzOSB1NDAgdTQxIHU0MiB1NDMgdTQ0IHU0NSB1NDYgdTQ3IHU0OCB1NDkgdTUwIHU1MSB1NTIgdTUzIHU1NCB1NTUgdTU2IHU1NyB1NTggdTU5IHU2MCB1NjEgdTYyIHU2MyB1NjQgdTY1IHU2NiB1NjcgdTY4IHU2OSB1NzAgdTcxIHU3MiB1NzMgdTc0IHU3NSB1NzYgdTc3IHU3OCB1NzkgdTgwIHU4MSB1ODIgdTgzIHU4NCB1ODUgdTg2IHU4NyB1ODggdTg5IHU5MCB1OTEgdTkyIHU5MyB1OTQgdTk1IHU5NiB1OTcgdTk4IHU5OSkpCjs7IGRhdGEgdmFycwo7OwoKOzsgZGF0YSBtYXBzCjs7CihkZWZpbmUtbWFwIHBpeGVscyB1aW50IChidWZmIDMpKQoKOzsgcHVibGljIGZ1bmN0aW9ucwo7OwooZGVmaW5lLXB1YmxpYyAoc2V0LXZhbHVlLWF0IChsb2MgdWludCkgKHZhbHVlIChidWZmIDMpKSkgCiAgICAoYmVnaW4gCiAgICAgICAgKGlmICg+PSBsb2MgTUFYX0xPQykKICAgICAgICAgICAgKGVyciAiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy4iKQogICAgICAgICAgICAoaWYgKD4gdmFsdWUgTUFYX1ZBTCkKICAgICAgICAgICAgICAgIChlcnIgIlZhbHVlIG11c3QgYmUgbGVzcyB0aGFuIDB4ZmZmZmZmLiIpCiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTCkKICAgICAgICAgICAgICAgICAgICAoZXJyICJWYWx1ZSBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAweDAwMDAwMC4iKQogICAgICAgICAgICAgICAgICAgIChvayAobWFwLXNldCBwaXhlbHMgbG9jIHZhbHVlKSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICkKICAgICkKKQo7OyByZWFkIG9ubHkgZnVuY3Rpb25zCjs7CgooZGVmaW5lLXJlYWQtb25seSAoZ2V0LXZhbHVlLWF0IChsb2MgdWludCkpCiAgICAoaWYgKD49IGxvYyBNQVhfTE9DKQogICAgICAgIChlcnIgIk91dCBvZiBib3VuZHMuIikKICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGdldC1hbGwpIAogICAgKG1hcCBnZXQtdmFsdWUtYXQgQUxMX0xPQ1MpCikKCihkZWZpbmUtcmVhZC1vbmx5IChnZW5lc2lzLXRpbWUgKGhlaWdodCB1aW50KSkKICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpCikKOzsgcHJpdmF0ZSBmdW5jdGlvbnMKOzsK", + "clarity_version": 2, + "cost": 18060, + "anchor_block_only": true + } + ], + "epoch": "2.1" + }, + { + "id": 3, + "transactions": [ + { + "transaction_type": "ContractCall", + "contract_id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.px", + "expected_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "method": "set-value-at", + "parameters": ["u0", "0xfffffa"], + "cost": 2240, + "anchor_block_only": false + }, + { + "transaction_type": "StxTransfer", + "expected_sender": "ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB", + "recipient": "ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0", + "mstx_amount": 200, + "memo": "0x00000000000000000000000000000000000000000000000000000000000000000000", + "cost": 2240, + "anchor_block_only": true + } + ], + "epoch": "2.1" + } + ], + "contracts": [ + { + "contract_id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.px", + "path": "/Users/micaiahreid/work/stx-px/contracts/px.clar", + "source": "Cjs7IHRpdGxlOiBweAo7OyB2ZXJzaW9uOgo7OyBzdW1tYXJ5Ogo7OyBkZXNjcmlwdGlvbjogQWxsb3dzIHVzZXJzIHRvIHBheSB0byB1cGRhdGUgZGF0YSBpbiBhIG1hdHJpeC4gCjs7ICBFYWNoIG1hdHJpeCB2YWx1ZSBtdXN0IGJlIGEgaGV4YWRlY2ltYWwgdmFsdWUgZnJvbSAweDAwMDAwMCB0byAweGZmZmZmZiwgcmVwcmVzZW50aW5nIGEgY29sb3IgdG8gYmUgZGlzcGxheWVkIG9uIGEgZ3JpZCBpbiBhIHdlYiBwYWdlLiAKOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuCgo7OyB0cmFpdHMKOzsKCjs7IHRva2VuIGRlZmluaXRpb25zCjs7IAoKOzsgY29uc3RhbnRzCjs7CihkZWZpbmUtY29uc3RhbnQgTUFYX0xPQyB1MTAwKQooZGVmaW5lLWNvbnN0YW50IE1BWF9WQUwgMHhmZmZmZmYpCihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMCkKKGRlZmluZS1jb25zdGFudCBBTExfTE9DUyAobGlzdCB1MCB1MSB1MiB1MyB1NCB1NSB1NiB1NyB1OCB1OSB1MTAgdTExIHUxMiB1MTMgdTE0IHUxNSB1MTYgdTE3IHUxOCB1MTkgdTIwIHUyMSB1MjIgdTIzIHUyNCB1MjUgdTI2IHUyNyB1MjggdTI5IHUzMCB1MzEgdTMyIHUzMyB1MzQgdTM1IHUzNiB1MzcgdTM4IHUzOSB1NDAgdTQxIHU0MiB1NDMgdTQ0IHU0NSB1NDYgdTQ3IHU0OCB1NDkgdTUwIHU1MSB1NTIgdTUzIHU1NCB1NTUgdTU2IHU1NyB1NTggdTU5IHU2MCB1NjEgdTYyIHU2MyB1NjQgdTY1IHU2NiB1NjcgdTY4IHU2OSB1NzAgdTcxIHU3MiB1NzMgdTc0IHU3NSB1NzYgdTc3IHU3OCB1NzkgdTgwIHU4MSB1ODIgdTgzIHU4NCB1ODUgdTg2IHU4NyB1ODggdTg5IHU5MCB1OTEgdTkyIHU5MyB1OTQgdTk1IHU5NiB1OTcgdTk4IHU5OSkpCjs7IGRhdGEgdmFycwo7OwoKOzsgZGF0YSBtYXBzCjs7CihkZWZpbmUtbWFwIHBpeGVscyB1aW50IChidWZmIDMpKQoKOzsgcHVibGljIGZ1bmN0aW9ucwo7OwooZGVmaW5lLXB1YmxpYyAoc2V0LXZhbHVlLWF0IChsb2MgdWludCkgKHZhbHVlIChidWZmIDMpKSkgCiAgICAoYmVnaW4gCiAgICAgICAgKGlmICg+PSBsb2MgTUFYX0xPQykKICAgICAgICAgICAgKGVyciAiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy4iKQogICAgICAgICAgICAoaWYgKD4gdmFsdWUgTUFYX1ZBTCkKICAgICAgICAgICAgICAgIChlcnIgIlZhbHVlIG11c3QgYmUgbGVzcyB0aGFuIDB4ZmZmZmZmLiIpCiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTCkKICAgICAgICAgICAgICAgICAgICAoZXJyICJWYWx1ZSBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAweDAwMDAwMC4iKQogICAgICAgICAgICAgICAgICAgIChvayAobWFwLXNldCBwaXhlbHMgbG9jIHZhbHVlKSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICkKICAgICkKKQo7OyByZWFkIG9ubHkgZnVuY3Rpb25zCjs7CgooZGVmaW5lLXJlYWQtb25seSAoZ2V0LXZhbHVlLWF0IChsb2MgdWludCkpCiAgICAoaWYgKD49IGxvYyBNQVhfTE9DKQogICAgICAgIChlcnIgIk91dCBvZiBib3VuZHMuIikKICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGdldC1hbGwpIAogICAgKG1hcCBnZXQtdmFsdWUtYXQgQUxMX0xPQ1MpCikKCihkZWZpbmUtcmVhZC1vbmx5IChnZW5lc2lzLXRpbWUgKGhlaWdodCB1aW50KSkKICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpCikKOzsgcHJpdmF0ZSBmdW5jdGlvbnMKOzsK" + } + ] + }, + "network_manifest": { + "network": { + "name": "devnet", + "stacks_node_rpc_address": null, + "bitcoin_node_rpc_address": null, + "deployment_fee_rate": 10, + "sats_per_bytes": 10 + }, + "accounts": [ + { + "label": "deployer", + "mnemonic": "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "btc_address": "mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH", + "is_mainnet": false + }, + { + "label": "faucet", + "mnemonic": "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6", + "btc_address": "mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d", + "is_mainnet": false + }, + { + "label": "wallet_1", + "mnemonic": "crazy vibrant runway diagram beach language above aerobic maze coral this gas mirror output vehicle cover usage ecology unfold room feel file rocket expire", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "STB8E0SMACY4A6DCCH4WE48YGX3P877407QW176V", + "btc_address": "mha4u7F3e93P9Xy1WQgVvGtYtynnJtT22x", + "is_mainnet": false + }, + { + "label": "wallet_2", + "mnemonic": "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG", + "btc_address": "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG", + "is_mainnet": false + }, + { + "label": "wallet_3", + "mnemonic": "cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC", + "btc_address": "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7", + "is_mainnet": false + }, + { + "label": "wallet_4", + "mnemonic": "board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND", + "btc_address": "mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8", + "is_mainnet": false + }, + { + "label": "wallet_5", + "mnemonic": "hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB", + "btc_address": "mweN5WVqadScHdA81aATSdcVr4B6dNokqx", + "is_mainnet": false + }, + { + "label": "wallet_6", + "mnemonic": "area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0", + "btc_address": "mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt", + "is_mainnet": false + }, + { + "label": "wallet_7", + "mnemonic": "prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ", + "btc_address": "n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7", + "is_mainnet": false + }, + { + "label": "wallet_8", + "mnemonic": "female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP", + "btc_address": "n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw", + "is_mainnet": false + } + ], + "devnet_settings": { + "name": "devnet", + "network_id": null, + "orchestrator_ingestion_port": 20445, + "orchestrator_control_port": 20446, + "bitcoin_node_p2p_port": 18444, + "bitcoin_node_rpc_port": 18443, + "bitcoin_node_username": "devnet", + "bitcoin_node_password": "devnet", + "stacks_node_p2p_port": 20444, + "stacks_node_rpc_port": 20443, + "stacks_node_wait_time_for_microblocks": 50, + "stacks_node_first_attempt_time_ms": 500, + "stacks_node_subsequent_attempt_time_ms": 1000, + "stacks_node_events_observers": [], + "stacks_node_env_vars": [], + "stacks_api_port": 3999, + "stacks_api_events_port": 3700, + "stacks_api_env_vars": [], + "stacks_explorer_port": 8000, + "stacks_explorer_env_vars": [], + "bitcoin_explorer_port": 8001, + "bitcoin_controller_block_time": 60000, + "bitcoin_controller_automining_disabled": false, + "miner_stx_address": "ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ", + "miner_secret_key_hex": "3b68e410cc7f9b8bae76f2f2991b69ecd0627c95da22a904065dfb2a73d0585f01", + "miner_btc_address": "n3GRiDLKWuKLCw1DZmV75W1mE35qmW2tQm", + "miner_mnemonic": "fragile loan twenty basic net assault jazz absorb diet talk art shock innocent float punch travel gadget embrace caught blossom hockey surround initial reduce", + "miner_derivation_path": "m/44'/5757'/0'/0/0", + "miner_coinbase_recipient": "ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ", + "faucet_stx_address": "STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6", + "faucet_secret_key_hex": "de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801", + "faucet_btc_address": "mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d", + "faucet_mnemonic": "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform", + "faucet_derivation_path": "m/44'/5757'/0'/0/0", + "working_dir": "/tmp", + "postgres_port": 5432, + "postgres_username": "postgres", + "postgres_password": "postgres", + "stacks_api_postgres_database": "stacks_api", + "subnet_api_postgres_database": "subnet_api", + "pox_stacking_orders": [ + { + "start_at_cycle": 3, + "duration": 12, + "wallet": "wallet_1", + "slots": 2, + "btc_address": "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" + }, + { + "start_at_cycle": 3, + "duration": 12, + "wallet": "wallet_2", + "slots": 1, + "btc_address": "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG" + }, + { + "start_at_cycle": 3, + "duration": 12, + "wallet": "wallet_3", + "slots": 1, + "btc_address": "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" } - ] + ], + "execute_script": [], + "bitcoin_node_image_url": "quay.io/hirosystems/bitcoind:devnet-v3", + "stacks_node_image_url": "quay.io/hirosystems/stacks-node:devnet-2.4.0.0.0", + "stacks_api_image_url": "hirosystems/stacks-blockchain-api:latest", + "stacks_explorer_image_url": "hirosystems/explorer:latest", + "postgres_image_url": "postgres:14", + "bitcoin_explorer_image_url": "quay.io/hirosystems/bitcoin-explorer:devnet", + "disable_bitcoin_explorer": true, + "disable_stacks_explorer": true, + "disable_stacks_api": false, + "bind_containers_volumes": true, + "enable_subnet_node": false, + "subnet_node_image_url": "hirosystems/stacks-subnets:0.8.1", + "subnet_leader_stx_address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "subnet_leader_secret_key_hex": "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601", + "subnet_leader_btc_address": "mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH", + "subnet_leader_mnemonic": "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw", + "subnet_leader_derivation_path": "m/44'/5757'/0'/0/0", + "subnet_node_p2p_port": 30444, + "subnet_node_rpc_port": 30443, + "subnet_events_ingestion_port": 30445, + "subnet_node_events_observers": [], + "subnet_contract_id": "ST173JK7NZBA4BS05ZRATQH1K89YJMTGEH1Z5J52E.subnet-v3-0-1", + "remapped_subnet_contract_id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.subnet-v3-0-1", + "subnet_node_env_vars": [], + "subnet_api_image_url": "hirosystems/stacks-blockchain-api:latest", + "subnet_api_port": 13999, + "subnet_api_events_port": 13700, + "subnet_api_env_vars": [], + "disable_subnet_api": true, + "docker_host": "unix:///var/run/docker.sock", + "components_host": "127.0.0.1", + "epoch_2_0": 100, + "epoch_2_05": 100, + "epoch_2_1": 101, + "epoch_2_2": 103, + "epoch_2_3": 104, + "epoch_2_4": 105, + "pox_2_activation": 102, + "use_docker_gateway_routing": false, + "docker_platform": "linux/amd64" } }, - "contracts": [ - { + "project_manifest": { + "project": { "name": "px", - "source": "XG47OyB0aXRsZTogcHhcbjs7IHZlcnNpb246XG47OyBzdW1tYXJ5OlxuOzsgZGVzY3JpcHRpb246IEFsbG93cyB1c2VycyB0byBwYXkgdG8gdXBkYXRlIGRhdGEgaW4gYSBtYXRyaXguIFxuOzsgIEVhY2ggbWF0cml4IHZhbHVlIG11c3QgYmUgYSBoZXhhZGVjaW1hbCB2YWx1ZSBmcm9tIDB4MDAwMDAwIHRvIDB4ZmZmZmZmLCByZXByZXNlbnRpbmcgYSBjb2xvciB0byBiZSBkaXNwbGF5ZWQgb24gYSBncmlkIGluIGEgd2ViIHBhZ2UuIFxuOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuXG5cbjs7IHRyYWl0c1xuOztcblxuOzsgdG9rZW4gZGVmaW5pdGlvbnNcbjs7IFxuXG47OyBjb25zdGFudHNcbjs7XG4oZGVmaW5lLWNvbnN0YW50IE1BWF9MT0MgdTEwMClcbihkZWZpbmUtY29uc3RhbnQgTUFYX1ZBTCAweGZmZmZmZilcbihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMClcbihkZWZpbmUtY29uc3RhbnQgQUxMX0xPQ1MgKGxpc3QgdTAgdTEgdTIgdTMgdTQgdTUgdTYgdTcgdTggdTkgdTEwIHUxMSB1MTIgdTEzIHUxNCB1MTUgdTE2IHUxNyB1MTggdTE5IHUyMCB1MjEgdTIyIHUyMyB1MjQgdTI1IHUyNiB1MjcgdTI4IHUyOSB1MzAgdTMxIHUzMiB1MzMgdTM0IHUzNSB1MzYgdTM3IHUzOCB1MzkgdTQwIHU0MSB1NDIgdTQzIHU0NCB1NDUgdTQ2IHU0NyB1NDggdTQ5IHU1MCB1NTEgdTUyIHU1MyB1NTQgdTU1IHU1NiB1NTcgdTU4IHU1OSB1NjAgdTYxIHU2MiB1NjMgdTY0IHU2NSB1NjYgdTY3IHU2OCB1NjkgdTcwIHU3MSB1NzIgdTczIHU3NCB1NzUgdTc2IHU3NyB1NzggdTc5IHU4MCB1ODEgdTgyIHU4MyB1ODQgdTg1IHU4NiB1ODcgdTg4IHU4OSB1OTAgdTkxIHU5MiB1OTMgdTk0IHU5NSB1OTYgdTk3IHU5OCB1OTkpKVxuOzsgZGF0YSB2YXJzXG47O1xuXG47OyBkYXRhIG1hcHNcbjs7XG4oZGVmaW5lLW1hcCBwaXhlbHMgdWludCAoYnVmZiAzKSlcblxuOzsgcHVibGljIGZ1bmN0aW9uc1xuOztcbihkZWZpbmUtcHVibGljIChzZXQtdmFsdWUtYXQgKGxvYyB1aW50KSAodmFsdWUgKGJ1ZmYgMykpKSBcbiAgICAoYmVnaW4gXG4gICAgICAgIChpZiAoPj0gbG9jIE1BWF9MT0MpXG4gICAgICAgICAgICAoZXJyIFwiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy5cIilcbiAgICAgICAgICAgIChpZiAoPiB2YWx1ZSBNQVhfVkFMKVxuICAgICAgICAgICAgICAgIChlcnIgXCJWYWx1ZSBtdXN0IGJlIGxlc3MgdGhhbiAweGZmZmZmZi5cIilcbiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTClcbiAgICAgICAgICAgICAgICAgICAgKGVyciBcIlZhbHVlIG11c3QgYmUgZ3JlYXRlciB0aGFuIDB4MDAwMDAwLlwiKVxuICAgICAgICAgICAgICAgICAgICAob2sgKG1hcC1zZXQgcGl4ZWxzIGxvYyB2YWx1ZSkpXG4gICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgKVxuICAgICAgICApXG4gICAgKVxuKVxuOzsgcmVhZCBvbmx5IGZ1bmN0aW9uc1xuOztcblxuKGRlZmluZS1yZWFkLW9ubHkgKGdldC12YWx1ZS1hdCAobG9jIHVpbnQpKVxuICAgIChpZiAoPj0gbG9jIE1BWF9MT0MpXG4gICAgICAgIChlcnIgXCJPdXQgb2YgYm91bmRzLlwiKVxuICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSlcbiAgICApXG4pXG5cbihkZWZpbmUtcmVhZC1vbmx5IChnZXQtYWxsKSBcbiAgICAobWFwIGdldC12YWx1ZS1hdCBBTExfTE9DUylcbilcblxuKGRlZmluZS1yZWFkLW9ubHkgKGdlbmVzaXMtdGltZSAoaGVpZ2h0IHVpbnQpKVxuICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpXG4pXG47OyBwcml2YXRlIGZ1bmN0aW9uc1xuOztcbg==", - "clarity_version": 2, - "epoch": 2.1, - "deployer": null + "description": "my description", + "authors": ["test1", "test2"], + "telemetry": false, + "cache_dir": ".cache", + "requirements": [] + }, + "contracts": { + "my-contract": { + "path": "contracts/my-contract.clar", + "clarity_version": 2, + "epoch": 2.4 + }, + "px": { + "path": "contracts/px.clar", + "clarity_version": 2, + "epoch": 2.1 + } + }, + "repl": { + "analysis": { + "passes": [], + "check_checker": { + "strict": false, + "trusted_sender": false, + "trusted_caller": false, + "callee_filter": false + } + } } - ] + } } diff --git a/scripts/deploy-api.sh b/scripts/deploy-api.sh index 2fcf312..949c424 100755 --- a/scripts/deploy-api.sh +++ b/scripts/deploy-api.sh @@ -1,3 +1,3 @@ kubectl --context kind-kind create namespace devnet -kubectl --context kind-kind create configmap stacks-devnet-api-conf --from-file=./Config.toml --namespace devnet && \ +kubectl --context kind-kind create configmap stacks-devnet-api --from-file=./Config.toml --namespace devnet && \ kubectl --context kind-kind apply -f ./templates/stacks-devnet-api.template.yaml diff --git a/scripts/get-logs.sh b/scripts/get-logs.sh index 3f88f54..2405fe3 100755 --- a/scripts/get-logs.sh +++ b/scripts/get-logs.sh @@ -1,5 +1,5 @@ -kubectl --context kind-kind logs stacks-node --namespace $1 > ./logs/stacks-node.txt & \ -kubectl --context kind-kind logs stacks-api --namespace $1 -c stacks-api-container > ./logs/stacks-api.txt & \ -kubectl --context kind-kind logs stacks-api --namespace $1 -c stacks-api-postgres > ./logs/stacks-api-postgres.txt & \ -kubectl --context kind-kind logs bitcoind-chain-coordinator --namespace $1 -c bitcoind-container > ./logs/bitcoin-node.txt & \ -kubectl --context kind-kind logs bitcoind-chain-coordinator --namespace $1 -c chain-coordinator-container > ./logs/chain-coordinator.txt \ No newline at end of file +kubectl logs stacks-blockchain --namespace $1 > ./logs/stacks-blockchain.txt & \ +kubectl logs stacks-blockchain-api --namespace $1 -c stacks-blockchain-api > ./logs/stacks-blockchain-api.txt & \ +kubectl logs stacks-blockchain-api --namespace $1 -c postgres > ./logs/stacks-blockchain-api-pg.txt & \ +kubectl logs bitcoind-chain-coordinator --namespace $1 -c bitcoind > ./logs/bitcoin-node.txt & \ +kubectl logs bitcoind-chain-coordinator --namespace $1 -c chain-coordinator > ./logs/chain-coordinator.txt \ No newline at end of file diff --git a/scripts/redeploy-api.sh b/scripts/redeploy-api.sh index 4654338..d50855e 100755 --- a/scripts/redeploy-api.sh +++ b/scripts/redeploy-api.sh @@ -1,3 +1,3 @@ -kubectl --context kind-kind delete configmap stacks-devnet-api-conf --namespace devnet & \ +kubectl --context kind-kind delete configmap stacks-devnet-api --namespace devnet & \ kubectl --context kind-kind delete pod stacks-devnet-api --namespace devnet && \ ./scripts/deploy-api.sh diff --git a/src/api_config.rs b/src/api_config.rs new file mode 100644 index 0000000..f7f4bbf --- /dev/null +++ b/src/api_config.rs @@ -0,0 +1,50 @@ +use std::{ + fs::File, + io::{BufReader, Read}, +}; + +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, serde::Deserialize, Clone, Default)] +pub struct ApiConfig { + #[serde(rename = "http_response")] + pub http_response_config: ResponderConfig, + #[serde(rename = "auth")] + pub auth_config: AuthConfig, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct ResponderConfig { + pub allowed_origins: Option>, + pub allowed_methods: Option>, + pub allowed_headers: Option, +} + +#[derive(Serialize, Deserialize, Clone, Default)] +pub struct AuthConfig { + pub auth_header: Option, + /// When the auth header is retrieved from the request, + /// this value will be prepended to the string to create + /// the k8s namespace. + pub namespace_prefix: Option, +} + +impl ApiConfig { + pub fn from_path(config_path: &str) -> ApiConfig { + let file = File::open(config_path) + .unwrap_or_else(|e| panic!("unable to read file {}\n{:?}", config_path, e)); + let mut file_reader = BufReader::new(file); + let mut file_buffer = vec![]; + file_reader + .read_to_end(&mut file_buffer) + .unwrap_or_else(|e| panic!("unable to read file {}\n{:?}", config_path, e)); + + let config_file: ApiConfig = match toml::from_slice(&file_buffer) { + Ok(s) => s, + Err(e) => { + panic!("Config file malformatted {}", e.to_string()); + } + }; + config_file + } +} diff --git a/src/config.rs b/src/config.rs index 47a925b..efea780 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,296 +1,194 @@ -use base64::{engine::general_purpose, Engine as _}; -use clarinet_deployments::types::DeploymentSpecificationFile; -use clarinet_files::{ - DEFAULT_DERIVATION_PATH, - DEFAULT_EPOCH_2_0, - DEFAULT_EPOCH_2_05, - DEFAULT_EPOCH_2_1, - DEFAULT_FAUCET_MNEMONIC, - DEFAULT_STACKS_MINER_MNEMONIC, //DEFAULT_EPOCH_2_2 (TODO, add when clarinet_files is updated) -}; +use clarinet_deployments::types::{DeploymentSpecification, TransactionSpecification}; +use clarinet_files::{AccountConfig, DevnetConfig, FileLocation, NetworkManifest, ProjectManifest}; use hiro_system_kit::slog; use serde::{Deserialize, Serialize}; -use std::str::from_utf8; +use std::{collections::BTreeMap, path::PathBuf}; use crate::{ resources::service::{get_service_port, ServicePort, StacksDevnetService}, Context, DevNetError, }; +const PROJECT_ROOT: &str = "/etc/stacks-network/project"; +const CONTRACT_DIR: &str = "/etc/stacks-network/project/contracts"; #[derive(Serialize, Deserialize, Debug)] pub struct ValidatedStacksDevnetConfig { - pub user_config: StacksDevnetConfig, + pub namespace: String, + pub user_id: String, + pub devnet_config: DevnetConfig, + pub accounts: BTreeMap, pub project_manifest_yaml_string: String, pub network_manifest_yaml_string: String, pub deployment_plan_yaml_string: String, pub contract_configmap_data: Vec<(String, String)>, + pub disable_stacks_api: bool, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct StacksDevnetConfig { pub namespace: String, - pub stacks_node_wait_time_for_microblocks: Option, - pub stacks_node_first_attempt_time_ms: Option, - pub stacks_node_subsequent_attempt_time_ms: Option, - pub bitcoin_node_username: String, - pub bitcoin_node_password: String, - pub miner_mnemonic: Option, - pub miner_derivation_path: Option, - pub miner_coinbase_recipient: Option, - faucet_mnemonic: Option, - faucet_derivation_path: Option, - bitcoin_controller_block_time: Option, - bitcoin_controller_automining_disabled: Option, + pub disable_stacks_api: bool, disable_bitcoin_explorer: Option, // todo: currently unused disable_stacks_explorer: Option, // todo: currently unused - pub disable_stacks_api: bool, - pub epoch_2_0: Option, - pub epoch_2_05: Option, - pub epoch_2_1: Option, - pub epoch_2_2: Option, - pub pox_2_activation: Option, - pub pox_2_unlock_height: Option, // todo (not currently used) - project_manifest: ProjectManifestConfig, - pub accounts: Vec, - deployment_plan: DeploymentSpecificationFile, - contracts: Vec, + deployment_plan: DeploymentSpecification, + network_manifest: NetworkManifest, + project_manifest: ProjectManifest, } impl StacksDevnetConfig { pub fn to_validated_config( self, + user_id: &str, ctx: Context, ) -> Result { let context = format!( "failed to validate config for NAMESPACE: {}", self.namespace ); - let project_manifest_yaml_string = self.get_project_manifest_yaml_string(); - let network_manifest_yaml_string = self.get_network_manifest_yaml_string(); - let deployment_plan_yaml_string = match self.get_deployment_plan_yaml_string() { - Ok(s) => Ok(s), - Err(e) => { - let msg = format!("{context}, ERROR: {e}"); - ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); - Err(DevNetError { - message: msg.into(), - code: 400, - }) - } - }?; + + if user_id != self.namespace { + let msg = + format!("{context}, ERROR: devnet namespace must match authenticated user id"); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + return Err(DevNetError { + message: msg.into(), + code: 400, + }); + } + + let project_manifest_yaml_string = self + .get_project_manifest_yaml_string() + .map_err(|e| log_and_return_err(e, &context, &ctx))?; + + let (network_manifest_yaml_string, devnet_config) = self + .get_network_manifest_string_and_devnet_config() + .map_err(|e| log_and_return_err(e, &context, &ctx))?; + + let deployment_plan_yaml_string = self + .get_deployment_plan_yaml_string() + .map_err(|e| log_and_return_err(e, &context, &ctx))?; let mut contracts: Vec<(String, String)> = vec![]; - for contract in &self.contracts { - let data = match contract.to_configmap_data() { - Ok(d) => Ok(d), - Err(e) => { - let msg = format!("{context}, ERROR: {e}"); - ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); - Err(DevNetError { - message: msg.into(), - code: 400, - }) - } - }?; - contracts.push(data); + for (contract_identifier, (src, _)) in self.deployment_plan.contracts { + contracts.push((contract_identifier.name.to_string(), src)); } + Ok(ValidatedStacksDevnetConfig { - user_config: self, - project_manifest_yaml_string, + namespace: self.namespace, + user_id: user_id.to_owned(), + devnet_config: devnet_config.to_owned(), + accounts: self.network_manifest.accounts, + project_manifest_yaml_string: project_manifest_yaml_string.to_owned(), network_manifest_yaml_string, deployment_plan_yaml_string, contract_configmap_data: contracts, + disable_stacks_api: self.disable_stacks_api, }) } - fn get_project_manifest_yaml_string(&self) -> String { - self.project_manifest.to_yaml_string(&self) - } + fn get_network_manifest_string_and_devnet_config( + &self, + ) -> Result<(String, DevnetConfig), String> { + let network_config = &self.network_manifest; - fn get_network_manifest_yaml_string(&self) -> String { - let mut config = format!( - r#"[network] -name = 'devnet' -"#, - ); + let devnet_config = match &self.network_manifest.devnet { + Some(devnet_config) => Ok(devnet_config), + None => Err("network manifest is missing required devnet config"), + }?; + let mut devnet_config = devnet_config.clone(); + devnet_config.orchestrator_ingestion_port = + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::Ingestion) + .unwrap() + .parse::() + .unwrap(); + devnet_config.orchestrator_control_port = + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::Control) + .unwrap() + .parse::() + .unwrap(); + devnet_config.bitcoin_node_p2p_port = + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::P2P) + .unwrap() + .parse::() + .unwrap(); + devnet_config.bitcoin_node_rpc_port = + get_service_port(StacksDevnetService::BitcoindNode, ServicePort::RPC) + .unwrap() + .parse::() + .unwrap(); + devnet_config.stacks_node_p2p_port = + get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::P2P) + .unwrap() + .parse::() + .unwrap(); + devnet_config.stacks_node_rpc_port = + get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::RPC) + .unwrap() + .parse::() + .unwrap(); + devnet_config.stacks_api_port = + get_service_port(StacksDevnetService::StacksBlockchainApi, ServicePort::API) + .unwrap() + .parse::() + .unwrap(); + devnet_config.stacks_api_events_port = + get_service_port(StacksDevnetService::StacksBlockchainApi, ServicePort::Event) + .unwrap() + .parse::() + .unwrap(); + devnet_config.postgres_port = + get_service_port(StacksDevnetService::StacksBlockchainApi, ServicePort::DB) + .unwrap() + .parse::() + .unwrap(); - config.push_str( - &self - .accounts - .clone() - .iter() - .map(|a| a.to_yaml_string()) - .collect::>() - .join("\n"), - ); - config.push_str(&format!( - r#" -[devnet] -miner_mnemonic = "{}" -miner_derivation_path = "{}" -bitcoin_node_username = "{}" -bitcoin_node_password = "{}" -faucet_mnemonic = "{}" -faucet_derivation_path = "{}" -orchestrator_ingestion_port = {} -orchestrator_control_port = {} -bitcoin_node_rpc_port = {} -stacks_node_rpc_port = {} -stacks_api_port = {} -epoch_2_0 = {} -epoch_2_05 = {} -epoch_2_1 = {} -epoch_2_2 = {} -working_dir = "/devnet" -bitcoin_controller_block_time = {} -bitcoin_controller_automining_disabled = {}"#, - &self - .miner_mnemonic - .clone() - .unwrap_or(DEFAULT_STACKS_MINER_MNEMONIC.into()), - &self - .miner_derivation_path - .clone() - .unwrap_or(DEFAULT_DERIVATION_PATH.into()), - &self.bitcoin_node_username, - &self.bitcoin_node_password, - &self - .faucet_mnemonic - .clone() - .unwrap_or(DEFAULT_FAUCET_MNEMONIC.into()), - &self - .faucet_derivation_path - .clone() - .unwrap_or(DEFAULT_DERIVATION_PATH.into()), - get_service_port(StacksDevnetService::BitcoindNode, ServicePort::Ingestion).unwrap(), - get_service_port(StacksDevnetService::BitcoindNode, ServicePort::Control).unwrap(), - get_service_port(StacksDevnetService::BitcoindNode, ServicePort::RPC).unwrap(), - get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(), - get_service_port(StacksDevnetService::StacksApi, ServicePort::API).unwrap(), - &self.epoch_2_0.unwrap_or(DEFAULT_EPOCH_2_0), - &self.epoch_2_05.unwrap_or(DEFAULT_EPOCH_2_05), - &self.epoch_2_1.unwrap_or(DEFAULT_EPOCH_2_1), - &self.epoch_2_2.unwrap_or(122), // todo: should be DEFAULT_EPOCH_2_2 when clarinet_files is updated - &self.bitcoin_controller_block_time.unwrap_or(50), - &self.bitcoin_controller_automining_disabled.unwrap_or(false) - )); + let yaml_str = match serde_yaml::to_string(&network_config) { + Ok(s) => Ok(s), + Err(e) => Err(format!("failed to parse devnet config: {}", e)), + }?; - config + Ok((yaml_str, devnet_config)) } - pub fn get_deployment_plan_yaml_string(&self) -> Result { - serde_yaml::to_string(&self.deployment_plan) - .map_err(|e| format!("failed to parse deployment plan config: {}", e)) + fn get_project_manifest_yaml_string(&self) -> Result { + let mut project_manifest = self.project_manifest.clone(); + project_manifest.location = FileLocation::from_path(PathBuf::from(PROJECT_ROOT)); + project_manifest.project.cache_location = + FileLocation::from_path(PathBuf::from(CONTRACT_DIR)); + serde_yaml::to_string(&project_manifest) + .map_err(|e| format!("failed to parse project manifest: {}", e)) } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct ProjectManifestConfig { - name: String, - description: Option, - authors: Option>, - requirements: Option>, -} - -impl ProjectManifestConfig { - fn to_yaml_string(&self, config: &StacksDevnetConfig) -> String { - let description = match &self.description { - Some(d) => d.to_owned(), - None => String::new(), - }; - let authors = match &self.authors { - Some(a) => format!("['{}']", a.join("','")), - None => String::from("[]"), - }; - let requirements = match &self.requirements { - Some(r) => format!("['{}']", r.join("','")), - None => String::from("[]"), - }; - format!( - r#"[project] -name = "{}" -description = "{}" -authors = {} -requirements = {} - -{}"#, - &self.name, - description, - authors, - requirements, - &config - .contracts - .clone() - .into_iter() - .map(|c| c.to_project_manifest_yaml_string()) - .collect::>() - .join("\n") - ) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ContractConfig { - pub name: String, - pub source: String, - clarity_version: u32, - epoch: f64, - deployer: Option, -} -impl ContractConfig { - fn to_project_manifest_yaml_string(&self) -> String { - let mut config = format!( - r#"[contracts.{}] -path = "contracts/{}.clar" -clarity_version = {} -epoch = "{}""#, - &self.name, &self.name, self.clarity_version, self.epoch, - ); - if let Some(deployer) = &self.deployer { - config.push_str(&format!(r#"deployer = {}"#, deployer,)); + pub fn get_deployment_plan_yaml_string(&self) -> Result { + let deployment = self.deployment_plan.clone(); + let contracts_loc = FileLocation::from_path(PathBuf::from(CONTRACT_DIR)); + for b in deployment.plan.batches { + for t in b.transactions { + match t { + TransactionSpecification::ContractPublish(mut spec) => { + spec.location = contracts_loc.clone(); + } + TransactionSpecification::RequirementPublish(mut spec) => { + spec.location = contracts_loc.clone(); + }, + TransactionSpecification::EmulatedContractCall(_) | TransactionSpecification::EmulatedContractPublish(_) => { + return Err(format!("devnet deployment plans do not support emulated-contract-calls or emulated-contract-publish types")) + } + _ => {} + } + } } - config - } - - fn to_configmap_data(&self) -> Result<(String, String), String> { - let bytes = general_purpose::STANDARD - .decode(&self.source) - .map_err(|e| format!("unable to decode contract source: {}", e.to_string()))?; - - let decoded = from_utf8(&bytes).map_err(|e| { - format!( - "invalid UTF-8 sequence when decoding contract source: {}", - e.to_string() - ) - })?; - let filename = format!("{}.clar", &self.name); - Ok((filename, decoded.to_owned())) + serde_yaml::to_string(&self.deployment_plan) + .map_err(|e| format!("failed to parse deployment plan config: {}", e)) } } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct AccountConfig { - pub name: String, - pub mnemonic: String, - pub derivation: Option, - pub balance: u64, -} -impl AccountConfig { - pub fn to_yaml_string(&self) -> String { - let mut config = format!( - r#" -[accounts.{}] -mnemonic = "{}" -balance = "{}" -"#, - &self.name, &self.mnemonic, &self.balance - ); - if let Some(derivation) = &self.derivation { - config.push_str(&format!(r#"derivation = "{}""#, derivation)); - } - config +fn log_and_return_err(e: String, context: &str, ctx: &Context) -> DevNetError { + let msg = format!("{context}, ERROR: {e}"); + ctx.try_log(|logger: &hiro_system_kit::Logger| slog::warn!(logger, "{}", msg)); + DevNetError { + message: msg.into(), + code: 400, } } - #[cfg(test)] mod tests { use std::{ @@ -301,7 +199,7 @@ mod tests { use crate::Context; - use super::{ProjectManifestConfig, StacksDevnetConfig}; + use super::StacksDevnetConfig; fn read_file(file_path: &str) -> Vec { let file = File::open(file_path) @@ -325,27 +223,16 @@ mod tests { }; config_file } - #[test] - #[should_panic] - fn it_rejects_config_with_none_base64_source_code() { - let mut template = get_template_config("src/tests/fixtures/stacks-devnet-config.json"); - let logger = hiro_system_kit::log::setup_logger(); - let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); - let ctx = Context::empty(); - template.contracts[0].source = "invalid base64 string".to_string(); - template - .to_validated_config(ctx) - .unwrap_or_else(|e| panic!("config validation test failed: {}", e.message)); - } #[test] fn it_converts_config_to_yaml() { let template = get_template_config("src/tests/fixtures/stacks-devnet-config.json"); + let user_id = &template.namespace.clone(); let logger = hiro_system_kit::log::setup_logger(); let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); let ctx = Context::empty(); let validated_config = template - .to_validated_config(ctx) + .to_validated_config(user_id, ctx) .unwrap_or_else(|e| panic!("config validation test failed: {}", e.message)); let expected_project_manifest = read_file("src/tests/fixtures/project-manifest.yaml"); @@ -358,11 +245,7 @@ mod tests { let expected_deployment_plan = from_utf8(&expected_deployment_plan).unwrap(); let expected_contract_source = read_file("src/tests/fixtures/contract-source.clar"); - let escaped = expected_contract_source - .iter() - .flat_map(|b| std::ascii::escape_default(*b)) - .collect::>(); - let expected_contract_source = from_utf8(&escaped).unwrap(); + let expected_contract_source = from_utf8(&expected_contract_source).unwrap(); assert_eq!( expected_project_mainfest, @@ -383,23 +266,35 @@ mod tests { } #[test] - fn project_manifest_allows_omitted_values() { - let project_manifest = ProjectManifestConfig { - name: "Test".to_string(), - description: None, - authors: None, - requirements: None, - }; + #[should_panic] + fn it_requires_devnet_config() { let mut template = get_template_config("src/tests/fixtures/stacks-devnet-config.json"); - template.contracts = vec![]; - let yaml = project_manifest.to_yaml_string(&template); - let expected = r#"[project] -name = "Test" -description = "" -authors = [] -requirements = [] + let logger = hiro_system_kit::log::setup_logger(); + let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); + let ctx = Context { + logger: None, + tracer: false, + }; + template.network_manifest.devnet = None; + let user_id = template.clone().namespace; + template + .to_validated_config(&user_id, ctx) + .unwrap_or_else(|e| panic!("config validation test failed: {}", e.message)); + } -"#; - assert_eq!(expected.to_string(), yaml); + #[test] + fn it_rejects_config_with_namespace_user_id_mismatch() { + let template = get_template_config("src/tests/fixtures/stacks-devnet-config.json"); + let namespace = template.namespace.clone(); + let user_id = "wrong"; + match template.to_validated_config(user_id, Context::empty()) { + Ok(_) => { + panic!("config validation with non-matching user_id should have been rejected") + } + Err(e) => { + assert_eq!(e.code, 400); + assert_eq!(e.message, format!("failed to validate config for NAMESPACE: {}, ERROR: devnet namespace must match authenticated user id", namespace)); + } + } } } diff --git a/src/lib.rs b/src/lib.rs index 02fe87e..400de22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,36 +1,40 @@ use chainhook_types::StacksNetwork; -use clarinet_files::{ - compute_addresses, DEFAULT_DERIVATION_PATH, DEFAULT_EPOCH_2_0, DEFAULT_EPOCH_2_05, - DEFAULT_EPOCH_2_1, DEFAULT_POX2_ACTIVATION, DEFAULT_STACKS_MINER_MNEMONIC, -}; +use clarinet_files::compute_addresses; use futures::future::try_join4; use hiro_system_kit::{slog, Logger}; use hyper::{body::Bytes, Body, Client as HttpClient, Request, Response, Uri}; use k8s_openapi::{ - api::core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}, + api::{ + apps::v1::{Deployment, StatefulSet}, + core::v1::{ConfigMap, Namespace, PersistentVolumeClaim, Pod, Service}, + }, NamespaceResourceScope, }; use kube::{ - api::{Api, DeleteParams, PostParams}, - Client, + api::{Api, DeleteParams, ListParams, PostParams}, + config::KubeConfigOptions, + Client, Config, }; use resources::{ + deployment::StacksDevnetDeployment, pvc::StacksDevnetPvc, service::{get_service_port, ServicePort}, + stateful_set::StacksDevnetStatefulSet, StacksDevnetResource, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use std::thread::sleep; use std::{collections::BTreeMap, str::FromStr, time::Duration}; +use std::{env, thread::sleep}; use strum::IntoEnumIterator; use tower::BoxError; pub mod config; -use config::{StacksDevnetConfig, ValidatedStacksDevnetConfig}; +use config::ValidatedStacksDevnetConfig; mod template_parser; use template_parser::get_yaml_from_resource; +pub mod api_config; pub mod resources; pub mod responder; pub mod routes; @@ -38,6 +42,10 @@ use crate::resources::configmap::StacksDevnetConfigmap; use crate::resources::pod::StacksDevnetPod; use crate::resources::service::{get_service_url, StacksDevnetService}; +const COMPONENT_SELECTOR: &str = "app.kubernetes.io/component"; +const USER_SELECTOR: &str = "app.kubernetes.io/instance"; +const NAME_SELECTOR: &str = "app.kubernetes.io/name"; + #[derive(Clone, Debug)] pub struct DevNetError { pub message: String, @@ -101,17 +109,55 @@ pub struct StacksDevnetApiK8sManager { } impl StacksDevnetApiK8sManager { - pub async fn default(ctx: &Context) -> StacksDevnetApiK8sManager { - let client = Client::try_default() - .await - .expect("could not create kube client"); + pub async fn new(ctx: &Context) -> StacksDevnetApiK8sManager { + let context = match env::var("KUBE_CONTEXT") { + Ok(context) => Some(context), + Err(_) => { + if cfg!(test) { + let is_ci = match env::var("GITHUB_ACTIONS") { + Ok(is_ci) => is_ci == format!("true"), + Err(_) => false, + }; + if is_ci { + None + } else { + // ensures that if no context is supplied and we're running + // tests locally, we deploy to the local kind cluster + Some(format!("kind-kind")) + } + } else { + None + } + } + }; + let client = match context { + Some(context) => { + let kube_config = KubeConfigOptions { + context: Some(context.clone()), + cluster: Some(context), + user: None, + }; + let client_config = + Config::from_kubeconfig(&kube_config) + .await + .unwrap_or_else(|e| { + panic!("could not create kube client config: {}", e.to_string()) + }); + Client::try_from(client_config) + .unwrap_or_else(|e| panic!("could not create kube client: {}", e.to_string())) + } + None => Client::try_default() + .await + .expect("could not create kube client"), + }; + StacksDevnetApiK8sManager { client, ctx: ctx.to_owned(), } } - pub async fn new( + pub async fn from_service( service: S, default_namespace: T, ctx: &Context, @@ -135,8 +181,8 @@ impl StacksDevnetApiK8sManager { &self, config: ValidatedStacksDevnetConfig, ) -> Result<(), DevNetError> { - let user_config = config.user_config; - let namespace = &user_config.namespace; + let namespace = &config.namespace; + let user_id = &config.user_id; let context = format!("NAMESPACE: {}", &namespace); let namespace_exists = self.check_namespace_exists(&namespace).await?; @@ -156,7 +202,9 @@ impl StacksDevnetApiK8sManager { } } - let any_assets_exist = self.check_any_devnet_assets_exist(&namespace).await?; + let any_assets_exist = self + .check_any_devnet_assets_exist(&namespace, &user_id) + .await?; if any_assets_exist { let msg = format!( "cannot create devnet because assets already exist {}", @@ -169,32 +217,45 @@ impl StacksDevnetApiK8sManager { }); }; - self.deploy_bitcoin_node_pod( - &user_config, - config.project_manifest_yaml_string, - config.network_manifest_yaml_string, - config.deployment_plan_yaml_string, - config.contract_configmap_data, - ) - .await?; + self.deploy_bitcoin_node(&config).await?; sleep(Duration::from_secs(5)); - self.deploy_stacks_node_pod(&user_config).await?; + self.deploy_stacks_blockchain(&config).await?; - if !user_config.disable_stacks_api { - self.deploy_stacks_api_pod(&namespace).await?; + if !config.disable_stacks_api { + self.deploy_stacks_blockchain_api(&config).await?; } Ok(()) } - pub async fn delete_devnet(&self, namespace: &str) -> Result<(), DevNetError> { - match self.check_any_devnet_assets_exist(&namespace).await? { + pub async fn delete_devnet(&self, namespace: &str, user_id: &str) -> Result<(), DevNetError> { + match self + .check_any_devnet_assets_exist(namespace, user_id) + .await? + { true => { let mut errors = vec![]; - let pods: Vec = StacksDevnetPod::iter().map(|p| p.to_string()).collect(); - for pod in pods { - if let Err(e) = self.delete_resource::(namespace, &pod).await { + let deployments: Vec = StacksDevnetDeployment::iter() + .map(|p| p.to_string()) + .collect(); + for deployment in deployments { + if let Err(e) = self + .delete_resource::(namespace, &deployment) + .await + { + errors.push(e); + } + } + + let stateful_sets: Vec = StacksDevnetStatefulSet::iter() + .map(|p| p.to_string()) + .collect(); + for stateful_set in stateful_sets { + if let Err(e) = self + .delete_resource::(namespace, &stateful_set) + .await + { errors.push(e); } } @@ -219,15 +280,17 @@ impl StacksDevnetApiK8sManager { } } - let pvcs: Vec = StacksDevnetPvc::iter().map(|s| s.to_string()).collect(); + let pvcs: Vec = + StacksDevnetPvc::iter().map(|pvc| pvc.to_string()).collect(); for pvc in pvcs { if let Err(e) = self - .delete_resource::(namespace, &pvc) + .delete_resource_by_label::(namespace, &pvc, user_id) .await { errors.push(e); } } + if errors.is_empty() { Ok(()) } else if errors.len() == 1 { @@ -262,7 +325,7 @@ impl StacksDevnetApiK8sManager { pub async fn check_namespace_exists(&self, namespace_str: &str) -> Result { self.ctx.try_log(|logger| { - slog::warn!( + slog::info!( logger, "checking if namespace NAMESPACE: {}", &namespace_str @@ -304,17 +367,36 @@ impl StacksDevnetApiK8sManager { pub async fn check_any_devnet_assets_exist( &self, namespace: &str, + user_id: &str, ) -> Result { self.ctx.try_log(|logger| { - slog::warn!( + slog::info!( logger, "checking if any devnet assets exist for devnet NAMESPACE: {}", &namespace ) }); + for deployment in StacksDevnetDeployment::iter() { + if self + .check_resource_exists::(namespace, &deployment.to_string()) + .await? + { + return Ok(true); + } + } + + for stateful_set in StacksDevnetStatefulSet::iter() { + if self + .check_resource_exists::(namespace, &stateful_set.to_string()) + .await? + { + return Ok(true); + } + } + for pod in StacksDevnetPod::iter() { if self - .check_resource_exists::(namespace, &pod.to_string()) + .check_resource_exists_by_label::(namespace, &pod.to_string(), user_id) .await? { return Ok(true); @@ -341,13 +423,16 @@ impl StacksDevnetApiK8sManager { for pvc in StacksDevnetPvc::iter() { if self - .check_resource_exists::(namespace, &pvc.to_string()) + .check_resource_exists_by_label::( + namespace, + &pvc.to_string(), + user_id, + ) .await? { return Ok(true); } } - Ok(false) } @@ -356,42 +441,42 @@ impl StacksDevnetApiK8sManager { namespace: &str, ) -> Result { self.ctx.try_log(|logger| { - slog::warn!( + slog::info!( logger, "checking if all devnet assets exist for devnet NAMESPACE: {}", &namespace ) }); - for pod in StacksDevnetPod::iter() { + for deployment in StacksDevnetDeployment::iter() { if !self - .check_resource_exists::(namespace, &pod.to_string()) + .check_resource_exists::(namespace, &deployment.to_string()) .await? { return Ok(false); } } - for configmap in StacksDevnetConfigmap::iter() { + for stateful_set in StacksDevnetStatefulSet::iter() { if !self - .check_resource_exists::(namespace, &configmap.to_string()) + .check_resource_exists::(namespace, &stateful_set.to_string()) .await? { return Ok(false); } } - for service in StacksDevnetService::iter() { + for configmap in StacksDevnetConfigmap::iter() { if !self - .check_resource_exists::(namespace, &service.to_string()) + .check_resource_exists::(namespace, &configmap.to_string()) .await? { return Ok(false); } } - for pvc in StacksDevnetPvc::iter() { + for service in StacksDevnetService::iter() { if !self - .check_resource_exists::(namespace, &pvc.to_string()) + .check_resource_exists::(namespace, &service.to_string()) .await? { return Ok(false); @@ -404,6 +489,7 @@ impl StacksDevnetApiK8sManager { async fn get_pod_status_info( &self, namespace: &str, + user_id: &str, pod: StacksDevnetPod, ) -> Result { let context = format!("NAMESPACE: {}, POD: {}", namespace, pod); @@ -412,24 +498,38 @@ impl StacksDevnetApiK8sManager { slog::info!(logger, "getting pod status {}", context) }); let pod_api: Api = Api::namespaced(self.client.to_owned(), &namespace); - let pod_name = pod.to_string(); - match pod_api.get_status(&pod_name).await { - Ok(pod_with_status) => match pod_with_status.status { - Some(status) => { - self.ctx.try_log(|logger: &hiro_system_kit::Logger| { - slog::info!(logger, "successfully retrieved pod status {}", context) - }); - let start_time = match status.start_time { - Some(st) => Some(st.0.to_string()), - None => None, - }; - Ok(PodStatusResponse { - status: status.phase, - start_time, - }) + + let pod_label_selector = format!("{COMPONENT_SELECTOR}={pod}"); + let user_label_selector = format!("{USER_SELECTOR}={user_id}"); + let name_label_selector = format!("{NAME_SELECTOR}={pod}"); + let label_selector = + format!("{pod_label_selector},{user_label_selector},{name_label_selector}"); + + let lp = ListParams::default() + .match_any() + .labels(&label_selector) + .limit(1); + + match pod_api.list(&lp).await { + Ok(pods) => { + let pod_with_status = &pods.items[0]; + match &pod_with_status.status { + Some(status) => { + self.ctx.try_log(|logger: &hiro_system_kit::Logger| { + slog::info!(logger, "successfully retrieved pod status {}", context) + }); + let start_time = match &status.start_time { + Some(st) => Some(st.0.to_string()), + None => None, + }; + Ok(PodStatusResponse { + status: status.phase.to_owned(), + start_time, + }) + } + None => Ok(PodStatusResponse::default()), } - None => Ok(PodStatusResponse::default()), - }, + } Err(e) => { let (msg, code) = match e { kube::Error::Api(api_error) => (api_error.message, api_error.code), @@ -447,8 +547,9 @@ impl StacksDevnetApiK8sManager { namespace: &str, ) -> Result { let client = HttpClient::new(); - let url = get_service_url(namespace, StacksDevnetService::StacksNode); - let port = get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(); + let url = get_service_url(namespace, StacksDevnetService::StacksBlockchain); + let port = + get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::RPC).unwrap(); let url = format!("http://{}:{}/v2/info", url, port); let context = format!("NAMESPACE: {}", namespace); @@ -524,6 +625,7 @@ impl StacksDevnetApiK8sManager { pub async fn get_devnet_info( &self, namespace: &str, + user_id: &str, ) -> Result { let context = format!("NAMESPACE: {}", namespace); @@ -557,9 +659,17 @@ impl StacksDevnetApiK8sManager { }, chain_info, ) = try_join4( - self.get_pod_status_info(&namespace, StacksDevnetPod::BitcoindNode), - self.get_pod_status_info(&namespace, StacksDevnetPod::StacksNode), - self.get_pod_status_info(&namespace, StacksDevnetPod::StacksApi), + self.get_pod_status_info(&namespace, user_id, StacksDevnetPod::BitcoindNode), + self.get_pod_status_info( + &namespace, + user_id, + StacksDevnetPod::StacksBlockchain, + ), + self.get_pod_status_info( + &namespace, + user_id, + StacksDevnetPod::StacksBlockchainApi, + ), self.get_stacks_v2_info(&namespace), ) .await?; @@ -614,6 +724,69 @@ impl StacksDevnetApiK8sManager { } } + async fn get_resource_by_label>( + &self, + namespace: &str, + name: &str, + user_id: &str, + ) -> Result, DevNetError> + where + ::DynamicType: Default, + K: Clone, + K: DeserializeOwned, + K: std::fmt::Debug, + K: Serialize, + { + let resource_api: Api = Api::namespaced(self.client.to_owned(), &namespace); + + let pod_label_selector = format!("{COMPONENT_SELECTOR}={name}"); + let user_label_selector = format!("{USER_SELECTOR}={user_id}"); + let name_label_selector = format!("{NAME_SELECTOR}={name}"); + let label_selector = + format!("{pod_label_selector},{user_label_selector},{name_label_selector}"); + let lp = ListParams::default() + .match_any() + .labels(&label_selector) + .limit(1); + + let resource_details = format!( + "RESOURCE: {}, NAME: {}, NAMESPACE: {}", + std::any::type_name::(), + name, + namespace + ); + self.ctx + .try_log(|logger| slog::info!(logger, "fetching {}", resource_details)); + + match resource_api.list(&lp).await { + Ok(pods) => { + if pods.items.len() > 0 { + let pod = &pods.items[0]; + Ok(Some(pod.clone())) + } else { + Ok(None) + } + } + Err(e) => { + let (msg, code) = match e { + kube::Error::Api(api_error) => { + if api_error.code == 404 { + return Ok(None); + } + (api_error.message, api_error.code) + } + e => (e.to_string(), 500), + }; + let msg = format!("failed to fetch {}, ERROR: {}", resource_details, msg); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: code, + }) + } + } + } + async fn get_resource>( &self, namespace: &str, @@ -667,6 +840,28 @@ impl StacksDevnetApiK8sManager { } } + async fn check_resource_exists_by_label>( + &self, + namespace: &str, + name: &str, + user_id: &str, + ) -> Result + where + ::DynamicType: Default, + K: Clone, + K: DeserializeOwned, + K: std::fmt::Debug, + K: Serialize, + { + match self + .get_resource_by_label::(namespace, name, user_id) + .await? + { + Some(_) => Ok(true), + None => Ok(false), + } + } + async fn check_resource_exists>( &self, namespace: &str, @@ -742,21 +937,140 @@ impl StacksDevnetApiK8sManager { } } - async fn deploy_pod(&self, pod: StacksDevnetPod, namespace: &str) -> Result<(), DevNetError> { - let mut pod: Pod = self.get_resource_from_file(StacksDevnetResource::Pod(pod))?; + async fn deploy_deployment( + &self, + deployment: StacksDevnetDeployment, + namespace: &str, + user_id: &str, + ) -> Result<(), DevNetError> { + let mut deployment: Deployment = + self.get_resource_from_file(StacksDevnetResource::Deployment(deployment))?; + + let key = "app.kubernetes.io/instance".to_string(); + let user_id = user_id.to_owned(); + + if let Some(mut labels) = deployment.clone().metadata.labels { + if let Some(label) = labels.get_mut(&key) { + *label = user_id.clone(); + } else { + labels.insert(key.clone(), user_id.clone()); + } + deployment.metadata.labels = Some(labels); + } + + if let Some(mut spec) = deployment.clone().spec { + if let Some(mut match_labels) = spec.selector.match_labels { + if let Some(match_label) = match_labels.get_mut(&key) { + *match_label = user_id.clone(); + } else { + match_labels.insert(key.clone(), user_id.clone()); + } + spec.selector.match_labels = Some(match_labels); + } + + if let Some(mut metadata) = spec.template.metadata { + if let Some(mut labels) = metadata.labels { + if let Some(label) = labels.get_mut(&key) { + *label = user_id.clone(); + } else { + labels.insert(key.clone(), user_id.clone()); + } + metadata.labels = Some(labels); + } + spec.template.metadata = Some(metadata); + } + + deployment.spec = Some(spec); + } - pod.metadata.namespace = Some(namespace.to_owned()); - self.deploy_resource(namespace, pod, "pod").await + deployment.metadata.namespace = Some(namespace.to_owned()); + self.deploy_resource(namespace, deployment, "deployment") + .await + } + + async fn deploy_stateful_set( + &self, + stateful_set: StacksDevnetStatefulSet, + namespace: &str, + user_id: &str, + ) -> Result<(), DevNetError> { + let mut stateful_set: StatefulSet = + self.get_resource_from_file(StacksDevnetResource::StatefulSet(stateful_set))?; + let key = "app.kubernetes.io/instance".to_string(); + let user_id = user_id.to_owned(); + + if let Some(mut labels) = stateful_set.clone().metadata.labels { + if let Some(label) = labels.get_mut(&key) { + *label = user_id.clone(); + } else { + labels.insert(key.clone(), user_id.clone()); + } + stateful_set.metadata.labels = Some(labels); + } + + if let Some(mut spec) = stateful_set.clone().spec { + if let Some(mut match_labels) = spec.selector.match_labels { + if let Some(match_label) = match_labels.get_mut(&key) { + *match_label = user_id.clone(); + } else { + match_labels.insert(key.clone(), user_id.clone()); + } + spec.selector.match_labels = Some(match_labels); + } + + if let Some(mut metadata) = spec.template.metadata { + if let Some(mut labels) = metadata.labels { + if let Some(label) = labels.get_mut(&key) { + *label = user_id.clone(); + } else { + labels.insert(key.clone(), user_id.clone()); + } + metadata.labels = Some(labels); + } + spec.template.metadata = Some(metadata); + } + + stateful_set.spec = Some(spec); + } + + stateful_set.metadata.namespace = Some(namespace.to_owned()); + self.deploy_resource(namespace, stateful_set, "stateful_set") + .await } async fn deploy_service( &self, service: StacksDevnetService, namespace: &str, + user_id: &str, ) -> Result<(), DevNetError> { let mut service: Service = self.get_resource_from_file(StacksDevnetResource::Service(service))?; + let key = "app.kubernetes.io/instance".to_string(); + let user_id = user_id.to_owned(); + + if let Some(mut labels) = service.clone().metadata.labels { + if let Some(label) = labels.get_mut(&key) { + *label = user_id.clone(); + } else { + labels.insert(key.clone(), user_id.clone()); + } + service.metadata.labels = Some(labels); + } + + if let Some(mut spec) = service.clone().spec { + if let Some(mut selector_map) = spec.selector { + if let Some(selector_entry) = selector_map.get_mut(&key) { + *selector_entry = user_id.clone(); + } else { + selector_map.insert(key.clone(), user_id.clone()); + } + spec.selector = Some(selector_map); + } + + service.spec = Some(spec); + } service.metadata.namespace = Some(namespace.to_owned()); self.deploy_resource(namespace, service, "service").await } @@ -783,24 +1097,13 @@ impl StacksDevnetApiK8sManager { .await } - async fn deploy_pvc(&self, pvc: StacksDevnetPvc, namespace: &str) -> Result<(), DevNetError> { - let mut pvc: PersistentVolumeClaim = - self.get_resource_from_file(StacksDevnetResource::Pvc(pvc))?; - - pvc.metadata.namespace = Some(namespace.to_owned()); - - self.deploy_resource(namespace, pvc, "pvc").await - } - - async fn deploy_bitcoin_node_pod( + async fn deploy_bitcoin_node( &self, - config: &StacksDevnetConfig, - project_mainfest: String, - network_manifest: String, - deployment_plan: String, - contracts: Vec<(String, String)>, + config: &ValidatedStacksDevnetConfig, ) -> Result<(), DevNetError> { let namespace = &config.namespace; + let user_id = &config.user_id; + let devnet_config = &config.devnet_config; let bitcoin_rpc_port = get_service_port(StacksDevnetService::BitcoindNode, ServicePort::RPC).unwrap(); @@ -831,8 +1134,8 @@ impl StacksDevnetApiK8sManager { rpcbind=0.0.0.0:{} rpcport={} "#, - config.bitcoin_node_username, - config.bitcoin_node_password, + devnet_config.bitcoin_node_username, + devnet_config.bitcoin_node_password, bitcoin_p2p_port, bitcoin_rpc_port, bitcoin_rpc_port @@ -845,65 +1148,66 @@ impl StacksDevnetApiK8sManager { ) .await?; - self.deploy_configmap( - StacksDevnetConfigmap::Namespace, - &namespace, - Some(vec![("NAMESPACE".into(), namespace.clone())]), - ) - .await?; - self.deploy_configmap( StacksDevnetConfigmap::ProjectManifest, &namespace, - Some(vec![("Clarinet.toml".into(), project_mainfest)]), + Some(vec![( + "Clarinet.toml".into(), + config.project_manifest_yaml_string.to_owned(), + )]), ) .await?; self.deploy_configmap( StacksDevnetConfigmap::Devnet, &namespace, - Some(vec![("Devnet.toml".into(), network_manifest)]), + Some(vec![( + "Devnet.toml".into(), + config.network_manifest_yaml_string.to_owned(), + )]), ) .await?; self.deploy_configmap( StacksDevnetConfigmap::DeploymentPlan, &namespace, - Some(vec![("default.devnet-plan.yaml".into(), deployment_plan)]), + Some(vec![( + "default.devnet-plan.yaml".into(), + config.deployment_plan_yaml_string.to_owned(), + )]), ) .await?; self.deploy_configmap( StacksDevnetConfigmap::ProjectDir, &namespace, - Some(contracts), + Some(config.contract_configmap_data.to_owned()), ) .await?; - self.deploy_pod(StacksDevnetPod::BitcoindNode, &namespace) + self.deploy_deployment(StacksDevnetDeployment::BitcoindNode, &namespace, &user_id) .await?; - self.deploy_service(StacksDevnetService::BitcoindNode, namespace) + self.deploy_service(StacksDevnetService::BitcoindNode, namespace, &user_id) .await?; Ok(()) } - async fn deploy_stacks_node_pod(&self, config: &StacksDevnetConfig) -> Result<(), DevNetError> { + async fn deploy_stacks_blockchain( + &self, + config: &ValidatedStacksDevnetConfig, + ) -> Result<(), DevNetError> { let namespace = &config.namespace; + let user_id = &config.user_id; + let devnet_config = &config.devnet_config; let chain_coordinator_ingestion_port = get_service_port(StacksDevnetService::BitcoindNode, ServicePort::Ingestion).unwrap(); let (miner_coinbase_recipient, _, stacks_miner_secret_key_hex) = compute_addresses( - &config - .miner_mnemonic - .clone() - .unwrap_or(DEFAULT_STACKS_MINER_MNEMONIC.into()), - &config - .miner_derivation_path - .clone() - .unwrap_or(DEFAULT_DERIVATION_PATH.into()), + &devnet_config.miner_mnemonic, + &devnet_config.miner_derivation_path, &StacksNetwork::Devnet.get_networks(), ); @@ -937,35 +1241,24 @@ impl StacksDevnetApiK8sManager { block_reward_recipient = "{}" # microblock_attempt_time_ms = 15000 "#, - get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(), - get_service_port(StacksDevnetService::StacksNode, ServicePort::P2P).unwrap(), + get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::RPC).unwrap(), + get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::P2P).unwrap(), stacks_miner_secret_key_hex, stacks_miner_secret_key_hex, - config.stacks_node_wait_time_for_microblocks.unwrap_or(50), - config.stacks_node_first_attempt_time_ms.unwrap_or(500), - config - .stacks_node_subsequent_attempt_time_ms - .unwrap_or(1_000), + devnet_config.stacks_node_wait_time_for_microblocks, + devnet_config.stacks_node_first_attempt_time_ms, + devnet_config.stacks_node_subsequent_attempt_time_ms, miner_coinbase_recipient ); - for account in config.accounts.clone().iter() { - let derivation_path = account - .derivation - .clone() - .unwrap_or(DEFAULT_DERIVATION_PATH.into()); - let (stx_address, _, _) = compute_addresses( - &account.mnemonic, - &derivation_path, - &StacksNetwork::Devnet.get_networks(), - ); + for (_, account) in config.accounts.iter() { stacks_conf.push_str(&format!( r#" [[ustx_balance]] address = "{}" amount = {} "#, - stx_address, account.balance + account.stx_address, account.balance )); } @@ -996,15 +1289,16 @@ impl StacksDevnetApiK8sManager { stacks_conf.push_str(&format!( r#" - # Add stacks-api as an event observer + # Add stacks-blockchain-api as an event observer [[events_observer]] endpoint = "{}:{}" retry_count = 255 include_data_events = false events_keys = ["*"] "#, - get_service_url(&namespace, StacksDevnetService::StacksApi), - get_service_port(StacksDevnetService::StacksApi, ServicePort::Event).unwrap(), + get_service_url(&namespace, StacksDevnetService::StacksBlockchainApi), + get_service_port(StacksDevnetService::StacksBlockchainApi, ServicePort::Event) + .unwrap(), )); stacks_conf.push_str(&format!( @@ -1023,8 +1317,8 @@ impl StacksDevnetApiK8sManager { peer_port = {} "#, bitcoind_chain_coordinator_host, - config.bitcoin_node_username, - config.bitcoin_node_password, + devnet_config.bitcoin_node_username, + devnet_config.bitcoin_node_password, chain_coordinator_ingestion_port, get_service_port(StacksDevnetService::BitcoindNode, ServicePort::P2P).unwrap() )); @@ -1053,51 +1347,63 @@ impl StacksDevnetApiK8sManager { # epoch_name = "2.2" # start_height = {} "#, - config.pox_2_activation.unwrap_or(DEFAULT_POX2_ACTIVATION), - config.epoch_2_0.unwrap_or(DEFAULT_EPOCH_2_0), - config.epoch_2_05.unwrap_or(DEFAULT_EPOCH_2_05), - config.epoch_2_1.unwrap_or(DEFAULT_EPOCH_2_1), - config.epoch_2_2.unwrap_or(110) //todo - get default value and uncomment config once stacks image is updated + devnet_config.pox_2_activation, + devnet_config.epoch_2_0, + devnet_config.epoch_2_05, + devnet_config.epoch_2_1, + devnet_config.epoch_2_2, )); stacks_conf }; self.deploy_configmap( - StacksDevnetConfigmap::StacksNode, + StacksDevnetConfigmap::StacksBlockchain, &namespace, Some(vec![("Stacks.toml".into(), stacks_conf)]), ) .await?; - self.deploy_pod(StacksDevnetPod::StacksNode, &namespace) - .await?; + self.deploy_deployment( + StacksDevnetDeployment::StacksBlockchain, + &namespace, + &user_id, + ) + .await?; - self.deploy_service(StacksDevnetService::StacksNode, namespace) + self.deploy_service(StacksDevnetService::StacksBlockchain, namespace, &user_id) .await?; Ok(()) } - async fn deploy_stacks_api_pod(&self, namespace: &str) -> Result<(), DevNetError> { + async fn deploy_stacks_blockchain_api( + &self, + config: &ValidatedStacksDevnetConfig, + ) -> Result<(), DevNetError> { + let namespace = &config.namespace; + let user_id = &config.user_id; // configmap env vars for pg conatainer let stacks_api_pg_env = Vec::from([ ("POSTGRES_PASSWORD".into(), "postgres".into()), ("POSTGRES_DB".into(), "stacks_api".into()), ]); self.deploy_configmap( - StacksDevnetConfigmap::StacksApiPostgres, + StacksDevnetConfigmap::StacksBlockchainApiPg, &namespace, Some(stacks_api_pg_env), ) .await?; // configmap env vars for api conatainer - let stacks_node_host = get_service_url(&namespace, StacksDevnetService::StacksNode); - let rpc_port = get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(); - let api_port = get_service_port(StacksDevnetService::StacksApi, ServicePort::API).unwrap(); + let stacks_node_host = get_service_url(&namespace, StacksDevnetService::StacksBlockchain); + let rpc_port = + get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::RPC).unwrap(); + let api_port = + get_service_port(StacksDevnetService::StacksBlockchainApi, ServicePort::API).unwrap(); let event_port = - get_service_port(StacksDevnetService::StacksApi, ServicePort::Event).unwrap(); - let db_port = get_service_port(StacksDevnetService::StacksApi, ServicePort::DB).unwrap(); + get_service_port(StacksDevnetService::StacksBlockchainApi, ServicePort::Event).unwrap(); + let db_port = + get_service_port(StacksDevnetService::StacksBlockchainApi, ServicePort::DB).unwrap(); let stacks_api_env = Vec::from([ ("STACKS_CORE_RPC_HOST".into(), stacks_node_host), ("STACKS_BLOCKCHAIN_API_DB".into(), "pg".into()), @@ -1118,20 +1424,25 @@ impl StacksDevnetApiK8sManager { ("STACKS_API_LOG_LEVEL".into(), "debug".into()), ]); self.deploy_configmap( - StacksDevnetConfigmap::StacksApi, + StacksDevnetConfigmap::StacksBlockchainApi, &namespace, Some(stacks_api_env), ) .await?; - self.deploy_pvc(StacksDevnetPvc::StacksApi, &namespace) - .await?; - - self.deploy_pod(StacksDevnetPod::StacksApi, &namespace) - .await?; + self.deploy_stateful_set( + StacksDevnetStatefulSet::StacksBlockchainApi, + &namespace, + user_id, + ) + .await?; - self.deploy_service(StacksDevnetService::StacksApi, &namespace) - .await?; + self.deploy_service( + StacksDevnetService::StacksBlockchainApi, + &namespace, + &user_id, + ) + .await?; Ok(()) } @@ -1179,6 +1490,63 @@ impl StacksDevnetApiK8sManager { } } } + + async fn delete_resource_by_label>( + &self, + namespace: &str, + resource_name: &str, + user_id: &str, + ) -> Result<(), DevNetError> + where + ::DynamicType: Default, + K: Clone, + K: DeserializeOwned, + K: std::fmt::Debug, + { + let api: Api = Api::namespaced(self.client.to_owned(), &namespace); + let dp = DeleteParams::default(); + + let pod_label_selector = format!("{COMPONENT_SELECTOR}={resource_name}"); + let user_label_selector = format!("{USER_SELECTOR}={user_id}"); + let name_label_selector = format!("{NAME_SELECTOR}={resource_name}"); + let label_selector = + format!("{pod_label_selector},{user_label_selector},{name_label_selector}"); + + let lp = ListParams::default() + .match_any() + .labels(&label_selector) + .limit(1); + + let resource_details = format!( + "RESOURCE: {}, NAME: {}, NAMESPACE: {}", + std::any::type_name::(), + resource_name, + namespace + ); + self.ctx + .try_log(|logger| slog::info!(logger, "deleting {}", resource_details)); + match api.delete_collection(&dp, &lp).await { + Ok(_) => { + self.ctx.try_log(|logger| { + slog::info!(logger, "successfully deleted {}", resource_details) + }); + Ok(()) + } + Err(e) => { + let e = match e { + kube::Error::Api(api_error) => (api_error.message, api_error.code), + e => (e.to_string(), 500), + }; + let msg = format!("failed to delete {}, ERROR: {}", resource_details, e.0); + self.ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + Err(DevNetError { + message: msg, + code: e.1, + }) + } + } + } + pub async fn delete_namespace(&self, namespace_str: &str) -> Result<(), DevNetError> { if cfg!(debug_assertions) { use kube::ResourceExt; diff --git a/src/main.rs b/src/main.rs index 6adb119..491eebb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,14 @@ use hiro_system_kit::slog; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, Request, Response, Server}; -use stacks_devnet_api::responder::{Responder, ResponderConfig}; +use stacks_devnet_api::api_config::ApiConfig; +use stacks_devnet_api::responder::Responder; use stacks_devnet_api::routes::{ get_standardized_path_parts, handle_check_devnet, handle_delete_devnet, handle_get_devnet, - handle_new_devnet, handle_try_proxy_service, API_PATH, + handle_get_status, handle_new_devnet, handle_try_proxy_service, API_PATH, }; use stacks_devnet_api::{Context, StacksDevnetApiK8sManager}; +use std::env; use std::{convert::Infallible, net::SocketAddr}; #[tokio::main] @@ -22,13 +24,18 @@ async fn main() { logger: Some(logger), tracer: false, }; - let k8s_manager = StacksDevnetApiK8sManager::default(&ctx).await; - let config_path = if cfg!(debug_assertions) { - "./Config.toml" - } else { - "/etc/config/Config.toml" + let k8s_manager = StacksDevnetApiK8sManager::new(&ctx).await; + let config_path = match env::var("CONFIG_PATH") { + Ok(path) => path, + Err(_) => { + if cfg!(debug_assertions) { + "./Config.toml".into() + } else { + "/etc/config/Config.toml".into() + } + } }; - let config = ResponderConfig::from_path(config_path); + let config = ApiConfig::from_path(&config_path); let make_svc = make_service_fn(|_| { let k8s_manager = k8s_manager.clone(); @@ -53,7 +60,10 @@ async fn main() { async fn handle_request( request: Request, k8s_manager: StacksDevnetApiK8sManager, - config: ResponderConfig, + ApiConfig { + http_response_config, + auth_config, + }: ApiConfig, ctx: Context, ) -> Result, Infallible> { let uri = request.uri(); @@ -67,14 +77,43 @@ async fn handle_request( path ) }); - - let responder = Responder::new(config, request.headers().clone()).unwrap(); + let headers = request.headers().clone(); + let responder = Responder::new(http_response_config, headers.clone(), ctx.clone()).unwrap(); if method == &Method::OPTIONS { return responder.ok(); } + if method == &Method::GET && (path == "/" || path == &format!("{API_PATH}status")) { + return handle_get_status(responder, ctx).await; + } + let auth_header = auth_config + .auth_header + .unwrap_or("x-auth-request-user".to_string()); + let user_id = match headers.get(auth_header) { + Some(auth_header_value) => match auth_header_value.to_str() { + Ok(user_id) => { + let user_id = user_id.replace("|", "-"); + match auth_config.namespace_prefix { + Some(mut prefix) => { + prefix.push_str(&user_id); + prefix + } + None => user_id, + } + } + Err(e) => { + let msg = format!("unable to parse auth header: {}", &e); + ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + return responder.err_bad_request(msg); + } + }, + None => return responder.err_bad_request("missing required auth header".into()), + }; + if path == "/api/v1/networks" { return match method { - &Method::POST => handle_new_devnet(request, k8s_manager, responder, ctx).await, + &Method::POST => { + handle_new_devnet(request, &user_id, k8s_manager, responder, ctx).await + } _ => responder.err_method_not_allowed("network creation must be a POST request".into()), }; } else if path.starts_with(API_PATH) { @@ -88,6 +127,9 @@ async fn handle_request( return responder.err_bad_request("no network id provided".into()); } let network = path_parts.network.unwrap(); + if network != user_id { + return responder.err_bad_request("network id must match authenticated user id".into()); + } // verify that we have a valid namespace and the network actually exists let exists = match k8s_manager.check_namespace_exists(&network).await { @@ -98,7 +140,7 @@ async fn handle_request( }; if !exists { let msg = format!("network {} does not exist", &network); - ctx.try_log(|logger| slog::warn!(logger, "{}", msg)); + ctx.try_log(|logger| slog::info!(logger, "{}", msg)); return responder.err_not_found(msg); } @@ -106,9 +148,15 @@ async fn handle_request( // so it must be a request to DELETE a network or GET network info if path_parts.subroute.is_none() { return match method { - &Method::DELETE => handle_delete_devnet(k8s_manager, &network, responder).await, - &Method::GET => handle_get_devnet(k8s_manager, &network, responder, ctx).await, - &Method::HEAD => handle_check_devnet(k8s_manager, &network, responder).await, + &Method::DELETE => { + handle_delete_devnet(k8s_manager, &network, &user_id, responder).await + } + &Method::GET => { + handle_get_devnet(k8s_manager, &network, &user_id, responder, ctx).await + } + &Method::HEAD => { + handle_check_devnet(k8s_manager, &network, &user_id, responder).await + } _ => responder .err_method_not_allowed("can only GET/DELETE/HEAD at provided route".into()), }; diff --git a/src/resources/configmap.rs b/src/resources/configmap.rs index 4658d28..773c172 100644 --- a/src/resources/configmap.rs +++ b/src/resources/configmap.rs @@ -4,28 +4,26 @@ use strum_macros::EnumIter; #[derive(EnumIter, Debug)] pub enum StacksDevnetConfigmap { BitcoindNode, - StacksNode, - StacksApi, - StacksApiPostgres, + StacksBlockchain, + StacksBlockchainApi, + StacksBlockchainApiPg, DeploymentPlan, Devnet, ProjectDir, - Namespace, ProjectManifest, } impl fmt::Display for StacksDevnetConfigmap { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - StacksDevnetConfigmap::BitcoindNode => write!(f, "bitcoind-conf"), - StacksDevnetConfigmap::StacksNode => write!(f, "stacks-node-conf"), - StacksDevnetConfigmap::StacksApi => write!(f, "stacks-api-conf"), - StacksDevnetConfigmap::StacksApiPostgres => write!(f, "stacks-api-postgres-conf"), - StacksDevnetConfigmap::DeploymentPlan => write!(f, "deployment-plan-conf"), - StacksDevnetConfigmap::Devnet => write!(f, "devnet-conf"), - StacksDevnetConfigmap::ProjectDir => write!(f, "project-dir-conf"), - StacksDevnetConfigmap::Namespace => write!(f, "namespace-conf"), - StacksDevnetConfigmap::ProjectManifest => write!(f, "project-manifest-conf"), + StacksDevnetConfigmap::BitcoindNode => write!(f, "bitcoind"), + StacksDevnetConfigmap::StacksBlockchain => write!(f, "stacks-blockchain"), + StacksDevnetConfigmap::StacksBlockchainApi => write!(f, "stacks-blockchain-api"), + StacksDevnetConfigmap::StacksBlockchainApiPg => write!(f, "stacks-blockchain-api-pg"), + StacksDevnetConfigmap::DeploymentPlan => write!(f, "deployment-plan"), + StacksDevnetConfigmap::Devnet => write!(f, "devnet"), + StacksDevnetConfigmap::ProjectDir => write!(f, "project-dir"), + StacksDevnetConfigmap::ProjectManifest => write!(f, "project-manifest"), } } } diff --git a/src/resources/deployment.rs b/src/resources/deployment.rs new file mode 100644 index 0000000..8635cd6 --- /dev/null +++ b/src/resources/deployment.rs @@ -0,0 +1,17 @@ +use std::fmt; +use strum_macros::EnumIter; + +#[derive(EnumIter, Debug)] +pub enum StacksDevnetDeployment { + BitcoindNode, + StacksBlockchain, +} + +impl fmt::Display for StacksDevnetDeployment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StacksDevnetDeployment::BitcoindNode => write!(f, "bitcoind-chain-coordinator"), + StacksDevnetDeployment::StacksBlockchain => write!(f, "stacks-blockchain"), + } + } +} diff --git a/src/resources/mod.rs b/src/resources/mod.rs index 918143b..ff8041d 100644 --- a/src/resources/mod.rs +++ b/src/resources/mod.rs @@ -1,18 +1,22 @@ use self::{ - configmap::StacksDevnetConfigmap, pod::StacksDevnetPod, pvc::StacksDevnetPvc, - service::StacksDevnetService, + configmap::StacksDevnetConfigmap, deployment::StacksDevnetDeployment, pod::StacksDevnetPod, + pvc::StacksDevnetPvc, service::StacksDevnetService, stateful_set::StacksDevnetStatefulSet, }; pub mod configmap; +pub mod deployment; pub mod pod; pub mod pvc; pub mod service; +pub mod stateful_set; pub enum StacksDevnetResource { Configmap(StacksDevnetConfigmap), + Deployment(StacksDevnetDeployment), Pod(StacksDevnetPod), Pvc(StacksDevnetPvc), Service(StacksDevnetService), + StatefulSet(StacksDevnetStatefulSet), Namespace, } diff --git a/src/resources/pod.rs b/src/resources/pod.rs index e04cab8..4270a79 100644 --- a/src/resources/pod.rs +++ b/src/resources/pod.rs @@ -4,16 +4,16 @@ use strum_macros::EnumIter; #[derive(EnumIter, Debug)] pub enum StacksDevnetPod { BitcoindNode, - StacksNode, - StacksApi, + StacksBlockchain, + StacksBlockchainApi, } impl fmt::Display for StacksDevnetPod { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { StacksDevnetPod::BitcoindNode => write!(f, "bitcoind-chain-coordinator"), - StacksDevnetPod::StacksNode => write!(f, "stacks-node"), - StacksDevnetPod::StacksApi => write!(f, "stacks-api"), + StacksDevnetPod::StacksBlockchain => write!(f, "stacks-blockchain"), + StacksDevnetPod::StacksBlockchainApi => write!(f, "stacks-blockchain-api"), } } } diff --git a/src/resources/pvc.rs b/src/resources/pvc.rs index 21ad26c..58404b4 100644 --- a/src/resources/pvc.rs +++ b/src/resources/pvc.rs @@ -3,13 +3,13 @@ use strum_macros::EnumIter; #[derive(EnumIter, Debug)] pub enum StacksDevnetPvc { - StacksApi, + StacksBlockchainApiPg, } impl fmt::Display for StacksDevnetPvc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - StacksDevnetPvc::StacksApi => write!(f, "stacks-api-pvc"), + StacksDevnetPvc::StacksBlockchainApiPg => write!(f, "stacks-blockchain-api"), } } } diff --git a/src/resources/service.rs b/src/resources/service.rs index 8ecc83b..852e125 100644 --- a/src/resources/service.rs +++ b/src/resources/service.rs @@ -4,8 +4,8 @@ use strum_macros::EnumIter; #[derive(EnumIter, Debug, Clone, PartialEq)] pub enum StacksDevnetService { BitcoindNode, - StacksNode, - StacksApi, + StacksBlockchain, + StacksBlockchainApi, } pub enum ServicePort { @@ -21,9 +21,9 @@ pub enum ServicePort { impl fmt::Display for StacksDevnetService { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - StacksDevnetService::BitcoindNode => write!(f, "bitcoind-chain-coordinator-service"), - StacksDevnetService::StacksNode => write!(f, "stacks-node-service"), - StacksDevnetService::StacksApi => write!(f, "stacks-api-service"), + StacksDevnetService::BitcoindNode => write!(f, "bitcoind-chain-coordinator"), + StacksDevnetService::StacksBlockchain => write!(f, "stacks-blockchain"), + StacksDevnetService::StacksBlockchainApi => write!(f, "stacks-blockchain-api"), } } } @@ -34,21 +34,21 @@ pub fn get_service_port(service: StacksDevnetService, port_type: ServicePort) -> (StacksDevnetService::BitcoindNode, ServicePort::P2P) => Some("18444".into()), (StacksDevnetService::BitcoindNode, ServicePort::Ingestion) => Some("20445".into()), (StacksDevnetService::BitcoindNode, ServicePort::Control) => Some("20446".into()), - (StacksDevnetService::StacksNode, ServicePort::RPC) => Some("20443".into()), - (StacksDevnetService::StacksNode, ServicePort::P2P) => Some("20444".into()), - (StacksDevnetService::StacksApi, ServicePort::API) => Some("3999".into()), - (StacksDevnetService::StacksApi, ServicePort::Event) => Some("3700".into()), - (StacksDevnetService::StacksApi, ServicePort::DB) => Some("5432".into()), + (StacksDevnetService::StacksBlockchain, ServicePort::RPC) => Some("20443".into()), + (StacksDevnetService::StacksBlockchain, ServicePort::P2P) => Some("20444".into()), + (StacksDevnetService::StacksBlockchainApi, ServicePort::API) => Some("3999".into()), + (StacksDevnetService::StacksBlockchainApi, ServicePort::Event) => Some("3700".into()), + (StacksDevnetService::StacksBlockchainApi, ServicePort::DB) => Some("5432".into()), (_, _) => None, } } pub fn get_user_facing_port(service: StacksDevnetService) -> Option { match service { - StacksDevnetService::BitcoindNode | StacksDevnetService::StacksNode => { + StacksDevnetService::BitcoindNode | StacksDevnetService::StacksBlockchain => { get_service_port(service, ServicePort::RPC) } - StacksDevnetService::StacksApi => get_service_port(service, ServicePort::API), + StacksDevnetService::StacksBlockchainApi => get_service_port(service, ServicePort::API), } } @@ -59,8 +59,8 @@ pub fn get_service_url(namespace: &str, service: StacksDevnetService) -> String pub fn get_service_from_path_part(path_part: &str) -> Option { match path_part { "bitcoin-node" => Some(StacksDevnetService::BitcoindNode), - "stacks-node" => Some(StacksDevnetService::StacksNode), - "stacks-api" => Some(StacksDevnetService::StacksApi), + "stacks-blockchain" => Some(StacksDevnetService::StacksBlockchain), + "stacks-blockchain-api" => Some(StacksDevnetService::StacksBlockchainApi), _ => None, } } diff --git a/src/resources/stateful_set.rs b/src/resources/stateful_set.rs new file mode 100644 index 0000000..c843604 --- /dev/null +++ b/src/resources/stateful_set.rs @@ -0,0 +1,15 @@ +use std::fmt; +use strum_macros::EnumIter; + +#[derive(EnumIter, Debug)] +pub enum StacksDevnetStatefulSet { + StacksBlockchainApi, +} + +impl fmt::Display for StacksDevnetStatefulSet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StacksDevnetStatefulSet::StacksBlockchainApi => write!(f, "stacks-blockchain-api"), + } + } +} diff --git a/src/resources/tests.rs b/src/resources/tests.rs index 9e3a959..38a6b7d 100644 --- a/src/resources/tests.rs +++ b/src/resources/tests.rs @@ -1,38 +1,44 @@ use super::{ - pvc::StacksDevnetPvc, + deployment::StacksDevnetDeployment, service::{get_service_from_path_part, get_service_port, get_user_facing_port, ServicePort}, + stateful_set::StacksDevnetStatefulSet, StacksDevnetConfigmap, StacksDevnetPod, StacksDevnetService, }; use test_case::test_case; -#[test_case(StacksDevnetConfigmap::BitcoindNode => is equal_to "bitcoind-conf".to_string(); "for BitcoinNode")] -#[test_case(StacksDevnetConfigmap::StacksNode => is equal_to "stacks-node-conf".to_string(); "for StacksNode")] -#[test_case(StacksDevnetConfigmap::StacksApi => is equal_to "stacks-api-conf".to_string(); "for StacksApi")] -#[test_case(StacksDevnetConfigmap::StacksApiPostgres => is equal_to "stacks-api-postgres-conf".to_string(); "for StacksApiPostgres")] -#[test_case(StacksDevnetConfigmap::DeploymentPlan => is equal_to "deployment-plan-conf".to_string(); "for DeploymentPlan")] -#[test_case(StacksDevnetConfigmap::Devnet => is equal_to "devnet-conf".to_string(); "for Devnet")] -#[test_case(StacksDevnetConfigmap::ProjectDir => is equal_to "project-dir-conf".to_string(); "for ProjectDir")] -#[test_case(StacksDevnetConfigmap::Namespace => is equal_to "namespace-conf".to_string(); "for Namespace")] -#[test_case(StacksDevnetConfigmap::ProjectManifest => is equal_to "project-manifest-conf".to_string(); "for ProjectManifest")] +#[test_case(StacksDevnetConfigmap::BitcoindNode => is equal_to "bitcoind".to_string(); "for BitcoinNode")] +#[test_case(StacksDevnetConfigmap::StacksBlockchain => is equal_to "stacks-blockchain".to_string(); "for StacksBlockchain")] +#[test_case(StacksDevnetConfigmap::StacksBlockchainApi => is equal_to "stacks-blockchain-api".to_string(); "for StacksBlockchainApi")] +#[test_case(StacksDevnetConfigmap::StacksBlockchainApiPg => is equal_to "stacks-blockchain-api-pg".to_string(); "for StacksBlockchainApiPg")] +#[test_case(StacksDevnetConfigmap::DeploymentPlan => is equal_to "deployment-plan".to_string(); "for DeploymentPlan")] +#[test_case(StacksDevnetConfigmap::Devnet => is equal_to "devnet".to_string(); "for Devnet")] +#[test_case(StacksDevnetConfigmap::ProjectDir => is equal_to "project-dir".to_string(); "for ProjectDir")] +#[test_case(StacksDevnetConfigmap::ProjectManifest => is equal_to "project-manifest".to_string(); "for ProjectManifest")] fn it_prints_correct_name_for_configmap(configmap: StacksDevnetConfigmap) -> String { configmap.to_string() } #[test_case(StacksDevnetPod::BitcoindNode => is equal_to "bitcoind-chain-coordinator".to_string(); "for BitcoindNode")] -#[test_case(StacksDevnetPod::StacksNode => is equal_to "stacks-node".to_string(); "for StacksNode")] -#[test_case(StacksDevnetPod::StacksApi => is equal_to "stacks-api".to_string(); "for StacksApi")] +#[test_case(StacksDevnetPod::StacksBlockchain => is equal_to "stacks-blockchain".to_string(); "for StacksBlockchain")] +#[test_case(StacksDevnetPod::StacksBlockchainApi => is equal_to "stacks-blockchain-api".to_string(); "for StacksBlockchainApi")] fn it_prints_correct_name_for_pod(pod: StacksDevnetPod) -> String { pod.to_string() } -#[test_case(StacksDevnetPvc::StacksApi => is equal_to "stacks-api-pvc".to_string(); "for StacksApi")] -fn it_prints_correct_name_for_pvc(pvc: StacksDevnetPvc) -> String { - pvc.to_string() +#[test_case(StacksDevnetDeployment::BitcoindNode => is equal_to "bitcoind-chain-coordinator".to_string(); "for BitcoindNode")] +#[test_case(StacksDevnetDeployment::StacksBlockchain => is equal_to "stacks-blockchain".to_string(); "for StacksBlockchain")] +fn it_prints_correct_name_for_deployment(deployment: StacksDevnetDeployment) -> String { + deployment.to_string() } -#[test_case(StacksDevnetService::BitcoindNode => is equal_to "bitcoind-chain-coordinator-service".to_string(); "for BitcoindNode")] -#[test_case(StacksDevnetService::StacksNode => is equal_to "stacks-node-service".to_string(); "for StacksNode")] -#[test_case(StacksDevnetService::StacksApi => is equal_to "stacks-api-service".to_string(); "for StacksApi")] +#[test_case(StacksDevnetStatefulSet::StacksBlockchainApi => is equal_to "stacks-blockchain-api".to_string(); "for StacksBlockchainApi")] +fn it_prints_correct_name_for_stateful_set(pod: StacksDevnetStatefulSet) -> String { + pod.to_string() +} + +#[test_case(StacksDevnetService::BitcoindNode => is equal_to "bitcoind-chain-coordinator".to_string(); "for BitcoindNode")] +#[test_case(StacksDevnetService::StacksBlockchain => is equal_to "stacks-blockchain".to_string(); "for StacksBlockchain")] +#[test_case(StacksDevnetService::StacksBlockchainApi => is equal_to "stacks-blockchain-api".to_string(); "for StacksBlockchainApi")] fn it_prints_correct_name_for_service(service: StacksDevnetService) -> String { service.to_string() } @@ -41,12 +47,12 @@ fn it_prints_correct_name_for_service(service: StacksDevnetService) -> String { #[test_case(StacksDevnetService::BitcoindNode, ServicePort::P2P => is equal_to Some("18444".to_string()); "for BitcoindNode P2P port")] #[test_case(StacksDevnetService::BitcoindNode, ServicePort::Ingestion => is equal_to Some("20445".to_string()); "for BitcoindNode Ingestion port")] #[test_case(StacksDevnetService::BitcoindNode, ServicePort::Control => is equal_to Some("20446".to_string()); "for BitcoindNode Control port")] -#[test_case(StacksDevnetService::StacksNode, ServicePort::RPC => is equal_to Some("20443".to_string()); "for StacksNode RPC port")] -#[test_case(StacksDevnetService::StacksNode, ServicePort::P2P => is equal_to Some("20444".to_string()); "for StacksNode P2P port")] -#[test_case(StacksDevnetService::StacksApi, ServicePort::API => is equal_to Some("3999".to_string()); "for StacksApi API port")] -#[test_case(StacksDevnetService::StacksApi, ServicePort::Event => is equal_to Some("3700".to_string()); "for StacksApi Event port")] -#[test_case(StacksDevnetService::StacksApi, ServicePort::DB => is equal_to Some("5432".to_string()); "for StacksApi DB port")] -#[test_case(StacksDevnetService::StacksApi, ServicePort::RPC => is equal_to None; "invalid service port combination")] +#[test_case(StacksDevnetService::StacksBlockchain, ServicePort::RPC => is equal_to Some("20443".to_string()); "for StacksBlockchain RPC port")] +#[test_case(StacksDevnetService::StacksBlockchain, ServicePort::P2P => is equal_to Some("20444".to_string()); "for StacksBlockchain P2P port")] +#[test_case(StacksDevnetService::StacksBlockchainApi, ServicePort::API => is equal_to Some("3999".to_string()); "for StacksBlockchainApi API port")] +#[test_case(StacksDevnetService::StacksBlockchainApi, ServicePort::Event => is equal_to Some("3700".to_string()); "for StacksBlockchainApi Event port")] +#[test_case(StacksDevnetService::StacksBlockchainApi, ServicePort::DB => is equal_to Some("5432".to_string()); "for StacksBlockchainApi DB port")] +#[test_case(StacksDevnetService::StacksBlockchainApi, ServicePort::RPC => is equal_to None; "invalid service port combination")] fn it_gets_correct_port_for_service( service: StacksDevnetService, port_type: ServicePort, @@ -55,16 +61,16 @@ fn it_gets_correct_port_for_service( } #[test_case("bitcoin-node" => is equal_to Some(StacksDevnetService::BitcoindNode); "for bitcoin-node")] -#[test_case("stacks-node" => is equal_to Some(StacksDevnetService::StacksNode); "for stacks-node")] -#[test_case("stacks-api" => is equal_to Some(StacksDevnetService::StacksApi); "for stacks-api")] +#[test_case("stacks-blockchain" => is equal_to Some(StacksDevnetService::StacksBlockchain); "for stacks-blockchain")] +#[test_case("stacks-blockchain-api" => is equal_to Some(StacksDevnetService::StacksBlockchainApi); "for stacks-blockchain-api")] #[test_case("invalid" => is equal_to None; "returning None for invalid paths")] fn it_prints_service_from_path_part(path_part: &str) -> Option { get_service_from_path_part(path_part) } #[test_case(StacksDevnetService::BitcoindNode => is equal_to Some("18443".to_string()); "for BitcoindNode")] -#[test_case(StacksDevnetService::StacksNode => is equal_to Some("20443".to_string()); "for StacksNode")] -#[test_case(StacksDevnetService::StacksApi => is equal_to Some("3999".to_string()); "for StacksApi")] +#[test_case(StacksDevnetService::StacksBlockchain => is equal_to Some("20443".to_string()); "for StacksBlockchain")] +#[test_case(StacksDevnetService::StacksBlockchainApi => is equal_to Some("3999".to_string()); "for StacksBlockchainApi")] fn it_gets_user_facing_port_for_service(service: StacksDevnetService) -> Option { get_user_facing_port(service) } diff --git a/src/responder.rs b/src/responder.rs index d9ce884..5eb1957 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,66 +1,54 @@ -use std::{ - convert::Infallible, - fs::File, - io::{BufReader, Read}, -}; - +use hiro_system_kit::slog; use hyper::{ header::{ - ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_METHODS, - ACCESS_CONTROL_ALLOW_ORIGIN, ORIGIN, + ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, + ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ORIGIN, }, http::{response::Builder, HeaderValue}, Body, HeaderMap, Response, StatusCode, }; -use serde::{Deserialize, Serialize}; +use std::convert::Infallible; + +use crate::{api_config::ResponderConfig, Context}; -#[derive(Default)] pub struct Responder { allowed_origins: Vec, allowed_methods: Vec, + allowed_headers: String, headers: HeaderMap, + ctx: Context, } -#[derive(Serialize, Deserialize, Clone, Default)] -pub struct ResponderConfig { - pub allowed_origins: Option>, - pub allowed_methods: Option>, -} - -impl ResponderConfig { - pub fn from_path(config_path: &str) -> ResponderConfig { - let file = File::open(config_path) - .unwrap_or_else(|e| panic!("unable to read file {}\n{:?}", config_path, e)); - let mut file_reader = BufReader::new(file); - let mut file_buffer = vec![]; - file_reader - .read_to_end(&mut file_buffer) - .unwrap_or_else(|e| panic!("unable to read file {}\n{:?}", config_path, e)); - - let config_file: ResponderConfig = match toml::from_slice(&file_buffer) { - Ok(s) => s, - Err(e) => { - panic!("Config file malformatted {}", e.to_string()); - } - }; - config_file +impl Default for Responder { + fn default() -> Self { + Responder { + allowed_origins: Vec::default(), + allowed_methods: Vec::default(), + allowed_headers: String::default(), + headers: HeaderMap::default(), + ctx: Context::empty(), + } } } - impl Responder { pub fn new( config: ResponderConfig, headers: HeaderMap, + ctx: Context, ) -> Result { Ok(Responder { allowed_origins: config.allowed_origins.unwrap_or_default(), allowed_methods: config.allowed_methods.unwrap_or_default(), + allowed_headers: config.allowed_headers.unwrap_or("*".to_string()), headers, + ctx, }) } pub fn response_builder(&self) -> Builder { - let mut builder = Response::builder().header(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); + let mut builder = Response::builder() + .header(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true") + .header(ACCESS_CONTROL_ALLOW_HEADERS, &self.allowed_headers); for method in &self.allowed_methods { builder = builder.header(ACCESS_CONTROL_ALLOW_METHODS, method); @@ -86,20 +74,61 @@ impl Responder { fn _respond(&self, code: StatusCode, body: String) -> Result, Infallible> { let builder = self.response_builder(); - match builder.status(code).body(Body::try_from(body).unwrap()) { + let body = match Body::try_from(body) { + Ok(b) => b, + Err(e) => { + self.ctx.try_log(|logger| { + slog::error!( + logger, + "responder failed to create response body: {}", + e.to_string() + ) + }); + Body::empty() + } + }; + match builder.status(code).body(body) { Ok(r) => Ok(r), - Err(_) => unreachable!(), + Err(e) => { + self.ctx.try_log(|logger| { + slog::error!( + logger, + "responder failed to send response: {}", + e.to_string() + ) + }); + Ok(self + .response_builder() + .status(500) + .body(Body::empty()) + .unwrap()) + } } } pub fn respond(&self, code: u16, body: String) -> Result, Infallible> { - self._respond(StatusCode::from_u16(code).unwrap(), body) + self._respond( + StatusCode::from_u16(code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), + body, + ) } pub fn ok(&self) -> Result, Infallible> { self._respond(StatusCode::OK, "Ok".into()) } + pub fn ok_with_json(&self, body: Body) -> Result, Infallible> { + match self + .response_builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json") + .body(body) + { + Ok(r) => Ok(r), + Err(e) => self.err_internal(format!("failed to send response: {}", e.to_string())), + } + } + pub fn err_method_not_allowed(&self, body: String) -> Result, Infallible> { self._respond(StatusCode::METHOD_NOT_ALLOWED, body) } diff --git a/src/routes.rs b/src/routes.rs index e7486a4..a940e8a 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,5 +1,6 @@ use hiro_system_kit::slog; -use hyper::{Body, Client, Request, Response, StatusCode, Uri}; +use hyper::{Body, Client, Request, Response, Uri}; +use serde_json::json; use std::{convert::Infallible, str::FromStr}; use crate::{ @@ -9,8 +10,30 @@ use crate::{ Context, StacksDevnetApiK8sManager, }; +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const PRJ_NAME: &str = env!("CARGO_PKG_NAME"); + +pub async fn handle_get_status( + responder: Responder, + ctx: Context, +) -> Result, Infallible> { + let version_info = format!("{PRJ_NAME} v{VERSION}"); + let version_info = json!({ "version": version_info }); + let version_info = match serde_json::to_vec(&version_info) { + Ok(v) => v, + Err(e) => { + let msg = format!("failed to parse version info: {}", e.to_string()); + ctx.try_log(|logger| slog::error!(logger, "{}", msg)); + return responder.err_internal(msg); + } + }; + let body = Body::from(version_info); + responder.ok_with_json(body) +} + pub async fn handle_new_devnet( request: Request, + user_id: &str, k8s_manager: StacksDevnetApiK8sManager, responder: Responder, ctx: Context, @@ -24,7 +47,7 @@ pub async fn handle_new_devnet( let body = body.unwrap(); let config: Result = serde_json::from_slice(&body); match config { - Ok(config) => match config.to_validated_config(ctx) { + Ok(config) => match config.to_validated_config(user_id, ctx) { Ok(config) => match k8s_manager.deploy_devnet(config).await { Ok(_) => responder.ok(), Err(e) => responder.respond(e.code, e.message), @@ -40,9 +63,10 @@ pub async fn handle_new_devnet( pub async fn handle_delete_devnet( k8s_manager: StacksDevnetApiK8sManager, network: &str, + user_id: &str, responder: Responder, ) -> Result, Infallible> { - match k8s_manager.delete_devnet(network).await { + match k8s_manager.delete_devnet(network, user_id).await { Ok(_) => responder.ok(), Err(e) => { let msg = format!("error deleting network {}: {}", &network, e.message); @@ -54,17 +78,13 @@ pub async fn handle_delete_devnet( pub async fn handle_get_devnet( k8s_manager: StacksDevnetApiK8sManager, network: &str, + user_id: &str, responder: Responder, ctx: Context, ) -> Result, Infallible> { - match k8s_manager.get_devnet_info(&network).await { + match k8s_manager.get_devnet_info(&network, user_id).await { Ok(devnet_info) => match serde_json::to_vec(&devnet_info) { - Ok(body) => Ok(responder - .response_builder() - .status(StatusCode::OK) - .header("Content-Type", "application/json") - .body(Body::from(body)) - .unwrap()), + Ok(body) => responder.ok_with_json(Body::from(body)), Err(e) => { let msg = format!( "failed to form response body: NAMESPACE: {}, ERROR: {}", @@ -82,9 +102,13 @@ pub async fn handle_get_devnet( pub async fn handle_check_devnet( k8s_manager: StacksDevnetApiK8sManager, network: &str, + user_id: &str, responder: Responder, ) -> Result, Infallible> { - match k8s_manager.check_any_devnet_assets_exist(&network).await { + match k8s_manager + .check_any_devnet_assets_exist(network, user_id) + .await + { Ok(assets_exist) => match assets_exist { true => responder.ok(), false => responder.err_not_found("not found".to_string()), diff --git a/src/template_parser.rs b/src/template_parser.rs index 9e63384..afa97d0 100644 --- a/src/template_parser.rs +++ b/src/template_parser.rs @@ -1,58 +1,53 @@ use crate::resources::{ - configmap::StacksDevnetConfigmap, pod::StacksDevnetPod, pvc::StacksDevnetPvc, - service::StacksDevnetService, StacksDevnetResource, + configmap::StacksDevnetConfigmap, deployment::StacksDevnetDeployment, + service::StacksDevnetService, stateful_set::StacksDevnetStatefulSet, StacksDevnetResource, }; pub fn get_yaml_from_resource(resource: StacksDevnetResource) -> &'static str { match resource { - StacksDevnetResource::Pod(StacksDevnetPod::BitcoindNode) => { - include_str!("../templates/bitcoind-chain-coordinator-pod.template.yaml") + StacksDevnetResource::Deployment(StacksDevnetDeployment::BitcoindNode) => { + include_str!("../templates/deployments/bitcoind-chain-coordinator.template.yaml") } StacksDevnetResource::Service(StacksDevnetService::BitcoindNode) => { - include_str!("../templates/bitcoind-chain-coordinator-service.template.yaml") + include_str!("../templates/services/bitcoind-chain-coordinator.template.yaml") } StacksDevnetResource::Configmap(StacksDevnetConfigmap::BitcoindNode) => { - include_str!("../templates/bitcoind-configmap.template.yaml") + include_str!("../templates/configmaps/bitcoind.template.yaml") } StacksDevnetResource::Configmap(StacksDevnetConfigmap::DeploymentPlan) => { - include_str!("../templates/chain-coord-deployment-plan-configmap.template.yaml") + include_str!("../templates/configmaps/chain-coord-deployment-plan.template.yaml") } StacksDevnetResource::Configmap(StacksDevnetConfigmap::Devnet) => { - include_str!("../templates/chain-coord-devnet-configmap.template.yaml") - } - StacksDevnetResource::Configmap(StacksDevnetConfigmap::Namespace) => { - include_str!("../templates/chain-coord-namespace-configmap.template.yaml") + include_str!("../templates/configmaps/chain-coord-devnet.template.yaml") } StacksDevnetResource::Configmap(StacksDevnetConfigmap::ProjectDir) => { - include_str!("../templates/chain-coord-project-dir-configmap.template.yaml") + include_str!("../templates/configmaps/chain-coord-project-dir.template.yaml") } StacksDevnetResource::Configmap(StacksDevnetConfigmap::ProjectManifest) => { - include_str!("../templates/chain-coord-project-manifest-configmap.template.yaml") - } - StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksApi) => { - include_str!("../templates/stacks-api-configmap.template.yaml") + include_str!("../templates/configmaps/chain-coord-project-manifest.template.yaml") } - StacksDevnetResource::Pod(StacksDevnetPod::StacksApi) => { - include_str!("../templates/stacks-api-pod.template.yaml") + StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksBlockchainApi) => { + include_str!("../templates/configmaps/stacks-blockchain-api.template.yaml") } - StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksApiPostgres) => { - include_str!("../templates/stacks-api-postgres-configmap.template.yaml") + StacksDevnetResource::StatefulSet(StacksDevnetStatefulSet::StacksBlockchainApi) => { + include_str!("../templates/stateful-sets/stacks-blockchain-api.template.yaml") } - StacksDevnetResource::Pvc(StacksDevnetPvc::StacksApi) => { - include_str!("../templates/stacks-api-pvc.template.yaml") + StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksBlockchainApiPg) => { + include_str!("../templates/configmaps/stacks-blockchain-api-pg.template.yaml") } - StacksDevnetResource::Service(StacksDevnetService::StacksApi) => { - include_str!("../templates/stacks-api-service.template.yaml") + StacksDevnetResource::Service(StacksDevnetService::StacksBlockchainApi) => { + include_str!("../templates/services/stacks-blockchain-api.template.yaml") } - StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksNode) => { - include_str!("../templates/stacks-node-configmap.template.yaml") + StacksDevnetResource::Configmap(StacksDevnetConfigmap::StacksBlockchain) => { + include_str!("../templates/configmaps/stacks-blockchain.template.yaml") } - StacksDevnetResource::Pod(StacksDevnetPod::StacksNode) => { - include_str!("../templates/stacks-node-pod.template.yaml") + StacksDevnetResource::Deployment(StacksDevnetDeployment::StacksBlockchain) => { + include_str!("../templates/deployments/stacks-blockchain.template.yaml") } - StacksDevnetResource::Service(StacksDevnetService::StacksNode) => { - include_str!("../templates/stacks-node-service.template.yaml") + StacksDevnetResource::Service(StacksDevnetService::StacksBlockchain) => { + include_str!("../templates/services/stacks-blockchain.template.yaml") } StacksDevnetResource::Namespace => include_str!("../templates/namespace.template.yaml"), + StacksDevnetResource::Pod(_) | StacksDevnetResource::Pvc(_) => unreachable!(), } } diff --git a/src/tests/fixtures/deployment-plan.yaml b/src/tests/fixtures/deployment-plan.yaml index 267c4ad..21e25da 100644 --- a/src/tests/fixtures/deployment-plan.yaml +++ b/src/tests/fixtures/deployment-plan.yaml @@ -2,17 +2,88 @@ id: 0 name: Devnet deployment network: devnet -stacks-node: "http://localhost:20443" -bitcoin-node: "http://px-devnet:px-devnet@localhost:18443" -plan: - batches: - - id: 0 - transactions: - - contract-publish: - contract-name: px - expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - cost: 18060 - path: contracts/px.clar - anchor-block-only: true - clarity-version: 2 - epoch: "2.1" +stacks_node: "http://localhost:20443" +bitcoin_node: "http://px-devnet:px-devnet@localhost:18443" +genesis: ~ +batches: + - id: 0 + transactions: + - transaction_type: RequirementPublish + contract_id: ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait + remap_sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + remap_principals: + ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + source: KGRlZmluZS10cmFpdCBuZnQtdHJhaXQKICAoCiAgICA7OyBMYXN0IHRva2VuIElELCBsaW1pdGVkIHRvIHVpbnQgcmFuZ2UKICAgIChnZXQtbGFzdC10b2tlbi1pZCAoKSAocmVzcG9uc2UgdWludCB1aW50KSkKCiAgICA7OyBVUkkgZm9yIG1ldGFkYXRhIGFzc29jaWF0ZWQgd2l0aCB0aGUgdG9rZW4KICAgIChnZXQtdG9rZW4tdXJpICh1aW50KSAocmVzcG9uc2UgKG9wdGlvbmFsIChzdHJpbmctYXNjaWkgMjU2KSkgdWludCkpCgogICAgIDs7IE93bmVyIG9mIGEgZ2l2ZW4gdG9rZW4gaWRlbnRpZmllcgogICAgKGdldC1vd25lciAodWludCkgKHJlc3BvbnNlIChvcHRpb25hbCBwcmluY2lwYWwpIHVpbnQpKQoKICAgIDs7IFRyYW5zZmVyIGZyb20gdGhlIHNlbmRlciB0byBhIG5ldyBwcmluY2lwYWwKICAgICh0cmFuc2ZlciAodWludCBwcmluY2lwYWwgcHJpbmNpcGFsKSAocmVzcG9uc2UgYm9vbCB1aW50KSkKICApCik= + clarity_version: 1 + cost: 4670 + location: + path: "./.cache/requirements/ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait.clar" + epoch: "2.0" + - id: 1 + transactions: + - transaction_type: RequirementPublish + contract_id: ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard + remap_sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + remap_principals: + ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + source: KGRlZmluZS10cmFpdCBzaXAtMDEwLXRyYWl0CiAgKAogICAgOzsgVHJhbnNmZXIgZnJvbSB0aGUgY2FsbGVyIHRvIGEgbmV3IHByaW5jaXBhbAogICAgKHRyYW5zZmVyICh1aW50IHByaW5jaXBhbCBwcmluY2lwYWwgKG9wdGlvbmFsIChidWZmIDM0KSkpIChyZXNwb25zZSBib29sIHVpbnQpKQoKICAgIDs7IHRoZSBodW1hbiByZWFkYWJsZSBuYW1lIG9mIHRoZSB0b2tlbgogICAgKGdldC1uYW1lICgpIChyZXNwb25zZSAoc3RyaW5nLWFzY2lpIDMyKSB1aW50KSkKCiAgICA7OyB0aGUgdGlja2VyIHN5bWJvbCwgb3IgZW1wdHkgaWYgbm9uZQogICAgKGdldC1zeW1ib2wgKCkgKHJlc3BvbnNlIChzdHJpbmctYXNjaWkgMzIpIHVpbnQpKQoKICAgIDs7IHRoZSBudW1iZXIgb2YgZGVjaW1hbHMgdXNlZCwgZS5nLiA2IHdvdWxkIG1lYW4gMV8wMDBfMDAwIHJlcHJlc2VudHMgMSB0b2tlbgogICAgKGdldC1kZWNpbWFscyAoKSAocmVzcG9uc2UgdWludCB1aW50KSkKCiAgICA7OyB0aGUgYmFsYW5jZSBvZiB0aGUgcGFzc2VkIHByaW5jaXBhbAogICAgKGdldC1iYWxhbmNlIChwcmluY2lwYWwpIChyZXNwb25zZSB1aW50IHVpbnQpKQoKICAgIDs7IHRoZSBjdXJyZW50IHRvdGFsIHN1cHBseSAod2hpY2ggZG9lcyBub3QgbmVlZCB0byBiZSBhIGNvbnN0YW50KQogICAgKGdldC10b3RhbC1zdXBwbHkgKCkgKHJlc3BvbnNlIHVpbnQgdWludCkpCgogICAgOzsgYW4gb3B0aW9uYWwgVVJJIHRoYXQgcmVwcmVzZW50cyBtZXRhZGF0YSBvZiB0aGlzIHRva2VuCiAgICAoZ2V0LXRva2VuLXVyaSAoKSAocmVzcG9uc2UgKG9wdGlvbmFsIChzdHJpbmctdXRmOCAyNTYpKSB1aW50KSkKICApCik= + clarity_version: 1 + cost: 8390 + location: + path: "./.cache/requirements/ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard.clar" + epoch: "2.05" + - id: 2 + transactions: + - transaction_type: RequirementPublish + contract_id: ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-traits-v1 + remap_sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + remap_principals: + ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + source: OzsgSW4gb3JkZXIgdG8gc3VwcG9ydCB3aXRoZHJhd2luZyBhbiBhc3NldCB0aGF0IHdhcyBtaW50ZWQgb24gYSBzdWJuZXQsIHRoZQo7OyBMMSBjb250cmFjdCBtdXN0IGltcGxlbWVudCB0aGlzIHRyYWl0LgooZGVmaW5lLXRyYWl0IG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQKICAoCiAgICA7OyBQcm9jZXNzIGEgd2l0aGRyYXdhbCBmcm9tIHRoZSBzdWJuZXQgZm9yIGFuIGFzc2V0IHdoaWNoIGRvZXMgbm90IHlldAogICAgOzsgZXhpc3Qgb24gdGhpcyBuZXR3b3JrLCBhbmQgdGh1cyByZXF1aXJlcyBhIG1pbnQuCiAgICAobWludC1mcm9tLXN1Ym5ldAogICAgICAoCiAgICAgICAgdWludCAgICAgICA7OyBhc3NldC1pZCAoTkZUKSBvciBhbW91bnQgKEZUKQogICAgICAgIHByaW5jaXBhbCAgOzsgc2VuZGVyCiAgICAgICAgcHJpbmNpcGFsICA7OyByZWNpcGllbnQKICAgICAgKQogICAgICAocmVzcG9uc2UgYm9vbCB1aW50KQogICAgKQogICkKKQ== + clarity_version: 2 + cost: 4810 + location: + path: "./.cache/requirements/ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-traits-v1.clar" + - transaction_type: RequirementPublish + contract_id: ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1-2 + remap_sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + remap_principals: + ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + source: OzsgVGhlIC5zdWJuZXQgY29udHJhY3QKCihkZWZpbmUtY29uc3RhbnQgQ09OVFJBQ1RfQUREUkVTUyAoYXMtY29udHJhY3QgdHgtc2VuZGVyKSkKCjs7IEVycm9yIGNvZGVzCihkZWZpbmUtY29uc3RhbnQgRVJSX0JMT0NLX0FMUkVBRFlfQ09NTUlUVEVEIDEpCihkZWZpbmUtY29uc3RhbnQgRVJSX0lOVkFMSURfTUlORVIgMikKKGRlZmluZS1jb25zdGFudCBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQgMykKKGRlZmluZS1jb25zdGFudCBFUlJfVFJBTlNGRVJfRkFJTEVEIDQpCihkZWZpbmUtY29uc3RhbnQgRVJSX0RJU0FMTE9XRURfQVNTRVQgNSkKKGRlZmluZS1jb25zdGFudCBFUlJfQVNTRVRfQUxSRUFEWV9BTExPV0VEIDYpCihkZWZpbmUtY29uc3RhbnQgRVJSX01FUktMRV9ST09UX0RPRVNfTk9UX01BVENIIDcpCihkZWZpbmUtY29uc3RhbnQgRVJSX0lOVkFMSURfTUVSS0xFX1JPT1QgOCkKKGRlZmluZS1jb25zdGFudCBFUlJfV0lUSERSQVdBTF9BTFJFQURZX1BST0NFU1NFRCA5KQooZGVmaW5lLWNvbnN0YW50IEVSUl9WQUxJREFUSU9OX0ZBSUxFRCAxMCkKOzs7IFRoZSB2YWx1ZSBzdXBwbGllZCBmb3IgYHRhcmdldC1jaGFpbi10aXBgIGRvZXMgbm90IG1hdGNoIHRoZSBjdXJyZW50IGNoYWluIHRpcC4KKGRlZmluZS1jb25zdGFudCBFUlJfSU5WQUxJRF9DSEFJTl9USVAgMTEpCjs7OyBUaGUgY29udHJhY3Qgd2FzIGNhbGxlZCBiZWZvcmUgcmVhY2hpbmcgdGhpcy1jaGFpbiBoZWlnaHQgcmVhY2hlcyAxLgooZGVmaW5lLWNvbnN0YW50IEVSUl9DQUxMRURfVE9PX0VBUkxZIDEyKQooZGVmaW5lLWNvbnN0YW50IEVSUl9NSU5UX0ZBSUxFRCAxMykKKGRlZmluZS1jb25zdGFudCBFUlJfQVRURU1QVF9UT19UUkFOU0ZFUl9aRVJPX0FNT1VOVCAxNCkKKGRlZmluZS1jb25zdGFudCBFUlJfSU5fQ09NUFVUQVRJT04gMTUpCjs7IFRoZSBjb250cmFjdCBkb2VzIG5vdCBvd24gdGhpcyBORlQgdG8gd2l0aGRyYXcgaXQuCihkZWZpbmUtY29uc3RhbnQgRVJSX05GVF9OT1RfT1dORURfQllfQ09OVFJBQ1QgMTYpCihkZWZpbmUtY29uc3RhbnQgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQgMzApCgo7OyBNYXAgZnJvbSBTdGFja3MgYmxvY2sgaGVpZ2h0IHRvIGJsb2NrIGNvbW1pdAooZGVmaW5lLW1hcCBibG9jay1jb21taXRzIHVpbnQgKGJ1ZmYgMzIpKQo7OyBNYXAgcmVjb3JkaW5nIHdpdGhkcmF3YWwgcm9vdHMKKGRlZmluZS1tYXAgd2l0aGRyYXdhbC1yb290cy1tYXAgKGJ1ZmYgMzIpIGJvb2wpCjs7IE1hcCByZWNvcmRpbmcgcHJvY2Vzc2VkIHdpdGhkcmF3YWwgbGVhdmVzCihkZWZpbmUtbWFwIHByb2Nlc3NlZC13aXRoZHJhd2FsLWxlYXZlcy1tYXAgeyB3aXRoZHJhd2FsLWxlYWYtaGFzaDogKGJ1ZmYgMzIpLCB3aXRoZHJhd2FsLXJvb3QtaGFzaDogKGJ1ZmYgMzIpIH0gYm9vbCkKCjs7IHByaW5jaXBhbCB0aGF0IGNhbiBjb21taXQgYmxvY2tzCihkZWZpbmUtZGF0YS12YXIgbWluZXIgcHJpbmNpcGFsIHR4LXNlbmRlcikKOzsgcHJpbmNpcGFsIHRoYXQgY2FuIHJlZ2lzdGVyIGNvbnRyYWN0cwooZGVmaW5lLWRhdGEtdmFyIGFkbWluIHByaW5jaXBhbCB0eC1zZW5kZXIpCgo7OyBNYXAgb2YgYWxsb3dlZCBjb250cmFjdHMgZm9yIGFzc2V0IHRyYW5zZmVycyAtIG1hcHMgTDEgY29udHJhY3QgcHJpbmNpcGFsIHRvIEwyIGNvbnRyYWN0IHByaW5jaXBhbAooZGVmaW5lLW1hcCBhbGxvd2VkLWNvbnRyYWN0cyBwcmluY2lwYWwgcHJpbmNpcGFsKQoKOzsgVXNlIHRyYWl0IGRlY2xhcmF0aW9ucwoodXNlLXRyYWl0IG5mdC10cmFpdCAnU1QxTlhCSzNLNVlZTUQ2RkQ0MU1WTlAzSlMxR0FCWjhUUlZYMDIzUFQubmZ0LXRyYWl0Lm5mdC10cmFpdCkKKHVzZS10cmFpdCBmdC10cmFpdCAnU1QxTlhCSzNLNVlZTUQ2RkQ0MU1WTlAzSlMxR0FCWjhUUlZYMDIzUFQuc2lwLTAxMC10cmFpdC1mdC1zdGFuZGFyZC5zaXAtMDEwLXRyYWl0KQoodXNlLXRyYWl0IG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQgLnN1Ym5ldC10cmFpdHMtdjEubWludC1mcm9tLXN1Ym5ldC10cmFpdCkKCjs7IFVwZGF0ZSB0aGUgbWluZXIgZm9yIHRoaXMgY29udHJhY3QuCihkZWZpbmUtcHVibGljICh1cGRhdGUtbWluZXIgKG5ldy1taW5lciBwcmluY2lwYWwpKQogICAgKGJlZ2luCiAgICAgICAgKGFzc2VydHMhIChpcy1lcSB0eC1zZW5kZXIgKHZhci1nZXQgbWluZXIpKSAoZXJyIEVSUl9JTlZBTElEX01JTkVSKSkKICAgICAgICAob2sgKHZhci1zZXQgbWluZXIgbmV3LW1pbmVyKSkKICAgICkKKQoKOzsgUmVnaXN0ZXIgYSBuZXcgRlQgY29udHJhY3QgdG8gYmUgc3VwcG9ydGVkIGJ5IHRoaXMgc3VibmV0LgooZGVmaW5lLXB1YmxpYyAocmVnaXN0ZXItbmV3LWZ0LWNvbnRyYWN0IChmdC1jb250cmFjdCA8ZnQtdHJhaXQ+KSAobDItY29udHJhY3QgcHJpbmNpcGFsKSkKICAgIChiZWdpbgogICAgICAgIDs7IFZlcmlmeSB0aGF0IHR4LXNlbmRlciBpcyBhbiBhdXRob3JpemVkIGFkbWluCiAgICAgICAgKGFzc2VydHMhIChpcy1hZG1pbiB0eC1zZW5kZXIpIChlcnIgRVJSX0lOVkFMSURfTUlORVIpKQoKICAgICAgICA7OyBTZXQgdXAgdGhlIGFzc2V0cyB0aGF0IHRoZSBjb250cmFjdCBpcyBhbGxvd2VkIHRvIHRyYW5zZmVyCiAgICAgICAgKGFzc2VydHMhIChtYXAtaW5zZXJ0IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkgbDItY29udHJhY3QpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX0FTU0VUX0FMUkVBRFlfQUxMT1dFRCkpCgogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAicmVnaXN0ZXItY29udHJhY3QiLAogICAgICAgICAgICBhc3NldC10eXBlOiAiZnQiLAogICAgICAgICAgICBsMS1jb250cmFjdDogKGNvbnRyYWN0LW9mIGZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgbDItY29udHJhY3Q6IGwyLWNvbnRyYWN0CiAgICAgICAgfSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IFJlZ2lzdGVyIGEgbmV3IE5GVCBjb250cmFjdCB0byBiZSBzdXBwb3J0ZWQgYnkgdGhpcyBzdWJuZXQuCihkZWZpbmUtcHVibGljIChyZWdpc3Rlci1uZXctbmZ0LWNvbnRyYWN0IChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pIChsMi1jb250cmFjdCBwcmluY2lwYWwpKQogICAgKGJlZ2luCiAgICAgICAgOzsgVmVyaWZ5IHRoYXQgdHgtc2VuZGVyIGlzIGFuIGF1dGhvcml6ZWQgYWRtaW4KICAgICAgICAoYXNzZXJ0cyEgKGlzLWFkbWluIHR4LXNlbmRlcikgKGVyciBFUlJfSU5WQUxJRF9NSU5FUikpCgogICAgICAgIDs7IFNldCB1cCB0aGUgYXNzZXRzIHRoYXQgdGhlIGNvbnRyYWN0IGlzIGFsbG93ZWQgdG8gdHJhbnNmZXIKICAgICAgICAoYXNzZXJ0cyEgKG1hcC1pbnNlcnQgYWxsb3dlZC1jb250cmFjdHMgKGNvbnRyYWN0LW9mIG5mdC1jb250cmFjdCkgbDItY29udHJhY3QpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX0FTU0VUX0FMUkVBRFlfQUxMT1dFRCkpCgogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAicmVnaXN0ZXItY29udHJhY3QiLAogICAgICAgICAgICBhc3NldC10eXBlOiAibmZ0IiwKICAgICAgICAgICAgbDEtY29udHJhY3Q6IChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpLAogICAgICAgICAgICBsMi1jb250cmFjdDogbDItY29udHJhY3QKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKOzsgSGVscGVyIGZ1bmN0aW9uOiByZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGdpdmVuIHByaW5jaXBhbCBpcyBhIG1pbmVyCjs7IFJldHVybnMgYm9vbAooZGVmaW5lLXByaXZhdGUgKGlzLW1pbmVyIChtaW5lci10by1jaGVjayBwcmluY2lwYWwpKQogICAgKGlzLWVxIG1pbmVyLXRvLWNoZWNrICh2YXItZ2V0IG1pbmVyKSkKKQoKOzsgSGVscGVyIGZ1bmN0aW9uOiByZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGdpdmVuIHByaW5jaXBhbCBpcyBhbiBhZG1pbgo7OyBSZXR1cm5zIGJvb2wKKGRlZmluZS1wcml2YXRlIChpcy1hZG1pbiAoYWRkci10by1jaGVjayBwcmluY2lwYWwpKQogICAgKGlzLWVxIGFkZHItdG8tY2hlY2sgKHZhci1nZXQgYWRtaW4pKQopCgo7OyBIZWxwZXIgZnVuY3Rpb246IGRldGVybWluZXMgd2hldGhlciB0aGUgY29tbWl0LWJsb2NrIG9wZXJhdGlvbiBzYXRpc2ZpZXMgcHJlLWNvbmRpdGlvbnMKOzsgbGlzdGVkIGluIGBjb21taXQtYmxvY2tgLgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wcml2YXRlIChjYW4tY29tbWl0LWJsb2NrPyAoY29tbWl0LWJsb2NrLWhlaWdodCB1aW50KSAgKHRhcmdldC1jaGFpbi10aXAgKGJ1ZmYgMzIpKSkKICAgIChiZWdpbgogICAgICAgIDs7IGNoZWNrIG5vIGJsb2NrIGhhcyBiZWVuIGNvbW1pdHRlZCBhdCB0aGlzIGhlaWdodAogICAgICAgIChhc3NlcnRzISAoaXMtbm9uZSAobWFwLWdldD8gYmxvY2stY29tbWl0cyBjb21taXQtYmxvY2staGVpZ2h0KSkgKGVyciBFUlJfQkxPQ0tfQUxSRUFEWV9DT01NSVRURUQpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IGB0YXJnZXQtY2hhaW4tdGlwYCBtYXRjaGVzIHRoZSBidXJuIGNoYWluIHRpcAogICAgICAgIChhc3NlcnRzISAoaXMtZXEKICAgICAgICAgICAgdGFyZ2V0LWNoYWluLXRpcAogICAgICAgICAgICAodW53cmFwISAoZ2V0LWJsb2NrLWluZm8/IGlkLWhlYWRlci1oYXNoICgtIGJsb2NrLWhlaWdodCB1MSkpIChlcnIgRVJSX0NBTExFRF9UT09fRUFSTFkpKSApCiAgICAgICAgICAgIChlcnIgRVJSX0lOVkFMSURfQ0hBSU5fVElQKSkKCiAgICAgICAgOzsgY2hlY2sgdGhhdCB0aGUgdHggc2VuZGVyIGlzIG9uZSBvZiB0aGUgbWluZXJzCiAgICAgICAgKGFzc2VydHMhIChpcy1taW5lciB0eC1zZW5kZXIpIChlcnIgRVJSX0lOVkFMSURfTUlORVIpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSBtaW5lciBjYWxsZWQgdGhpcyBjb250cmFjdCBkaXJlY3RseQogICAgICAgIChhc3NlcnRzISAoaXMtbWluZXIgY29udHJhY3QtY2FsbGVyKSAoZXJyIEVSUl9JTlZBTElEX01JTkVSKSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEhlbHBlciBmdW5jdGlvbjogbW9kaWZpZXMgdGhlIGJsb2NrLWNvbW1pdHMgbWFwIHdpdGggYSBuZXcgY29tbWl0IGFuZCBwcmludHMgcmVsYXRlZCBpbmZvCjs7IFJldHVybnMgcmVzcG9uc2U8KGJ1ZmYgMzIpLCA/PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLWNvbW1pdC1ibG9jawogICAgICAgIChibG9jayAoYnVmZiAzMikpCiAgICAgICAgKGNvbW1pdC1ibG9jay1oZWlnaHQgdWludCkKICAgICAgICAodGFyZ2V0LWJ1cm4tYmxvY2staGVpZ2h0IHVpbnQpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICApCiAgICAoYmVnaW4KICAgICAgICAobWFwLXNldCBibG9jay1jb21taXRzIGNvbW1pdC1ibG9jay1oZWlnaHQgYmxvY2spCiAgICAgICAgKG1hcC1zZXQgd2l0aGRyYXdhbC1yb290cy1tYXAgd2l0aGRyYXdhbC1yb290IHRydWUpCiAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgZXZlbnQ6ICJibG9jay1jb21taXQiLAogICAgICAgICAgICBibG9jay1jb21taXQ6IGJsb2NrLAogICAgICAgICAgICBibG9jay1oZWlnaHQ6IGNvbW1pdC1ibG9jay1oZWlnaHQsCiAgICAgICAgICAgIHdpdGhkcmF3YWwtcm9vdDogd2l0aGRyYXdhbC1yb290LAogICAgICAgICAgICB0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQ6IHRhcmdldC1idXJuLWJsb2NrLWhlaWdodAogICAgICAgIH0pCiAgICAgICAgKG9rIGJsb2NrKQogICAgKQopCgo7OyBUaGUgc3VibmV0IG1pbmVyIGNhbGxzIHRoaXMgZnVuY3Rpb24gdG8gY29tbWl0IGEgYmxvY2sgYXQgYSBwYXJ0aWN1bGFyIGhlaWdodC4KOzsgYGJsb2NrYCBpcyB0aGUgaGFzaCBvZiB0aGUgYmxvY2sgYmVpbmcgc3VibWl0dGVkLgo7OyBgdGFyZ2V0LWNoYWluLXRpcGAgaXMgdGhlIGBpZC1oZWFkZXItaGFzaGAgb2YgdGhlIGJ1cm4gYmxvY2sgKGkuZS4sIGJsb2NrIG9uCjs7ICAgIHRoaXMgY2hhaW4pIHRoYXQgdGhlIG1pbmVyIGludGVuZHMgdG8gYnVpbGQgb2ZmLgo7Owo7OyBGYWlscyBpZjoKOzsgIDEpIHdlIGhhdmUgYWxyZWFkeSBjb21taXR0ZWQgYXQgdGhpcyBibG9jayBoZWlnaHQKOzsgIDIpIGB0YXJnZXQtY2hhaW4tdGlwYCBpcyBub3QgdGhlIGJ1cm4gY2hhaW4gdGlwIChpLmUuLCBvbiB0aGlzIGNoYWluKQo7OyAgMykgdGhlIHNlbmRlciBpcyBub3QgYSBtaW5lcgooZGVmaW5lLXB1YmxpYyAoY29tbWl0LWJsb2NrCiAgICAgICAgKGJsb2NrIChidWZmIDMyKSkKICAgICAgICAoY29tbWl0LWJsb2NrLWhlaWdodCB1aW50KQogICAgICAgICh0YXJnZXQtY2hhaW4tdGlwIChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICkKICAgIChsZXQgKCh0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQgYmxvY2staGVpZ2h0KSkKICAgICAgICAodHJ5ISAoY2FuLWNvbW1pdC1ibG9jaz8gdGFyZ2V0LWJ1cm4tYmxvY2staGVpZ2h0IHRhcmdldC1jaGFpbi10aXApKQogICAgICAgIChpbm5lci1jb21taXQtYmxvY2sgYmxvY2sgY29tbWl0LWJsb2NrLWhlaWdodCB0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQgd2l0aGRyYXdhbC1yb290KQogICAgKQopCgo7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Cjs7IEZPUiBORlQgQVNTRVQgVFJBTlNGRVJTCgo7OyBIZWxwZXIgZnVuY3Rpb24gdGhhdCB0cmFuc2ZlcnMgdGhlIHNwZWNpZmllZCBORlQgZnJvbSB0aGUgZ2l2ZW4gc2VuZGVyIHRvIHRoZSBnaXZlbiByZWNpcGllbnQuCjs7IFJldHVybnMgcmVzcG9uc2U8Ym9vbCwgaW50PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChjb250cmFjdC1jYWxsPyBuZnQtY29udHJhY3QgdHJhbnNmZXIgaWQgc2VuZGVyIHJlY2lwaWVudCkpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICApCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdHJhbnNmZXIgc3VjY2VlZGVkCiAgICAgICAgKGFzc2VydHMhIHRyYW5zZmVyLXJlc3VsdCAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtbWludC1jb250cmFjdCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChhcy1jb250cmFjdCAoY29udHJhY3QtY2FsbD8gbmZ0LW1pbnQtY29udHJhY3QgbWludC1mcm9tLXN1Ym5ldCBpZCBzZW5kZXIgcmVjaXBpZW50KSkpCiAgICAgICAgICAgIChtaW50LXJlc3VsdCAodW53cmFwISBjYWxsLXJlc3VsdCAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICkKICAgICAgICA7OyBDaGVjayB0aGF0IHRoZSB0cmFuc2ZlciBzdWNjZWVkZWQKICAgICAgICAoYXNzZXJ0cyEgbWludC1yZXN1bHQgKGVyciBFUlJfTUlOVF9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci10cmFuc2Zlci1vci1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKG5mdC1taW50LWNvbnRyYWN0IDxtaW50LWZyb20tc3VibmV0LXRyYWl0PikKICAgICAgICAoaWQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY2FsbC1yZXN1bHQgKGNvbnRyYWN0LWNhbGw/IG5mdC1jb250cmFjdCBnZXQtb3duZXIgaWQpKQogICAgICAgICAgICAobmZ0LW93bmVyICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgICAgIChjb250cmFjdC1vd25zLW5mdCAoaXMtZXEgbmZ0LW93bmVyIChzb21lIENPTlRSQUNUX0FERFJFU1MpKSkKICAgICAgICAgICAgKG5vLW93bmVyIChpcy1lcSBuZnQtb3duZXIgbm9uZSkpCiAgICAgICAgKQoKICAgICAgICAoaWYgY29udHJhY3Qtb3ducy1uZnQKICAgICAgICAgICAgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICAgICAgICAgIChpZiBuby1vd25lcgogICAgICAgICAgICAgICAgOzsgVHJ5IG1pbnRpbmcgdGhlIGFzc2V0IGlmIHRoZXJlIGlzIG5vIGV4aXN0aW5nIG93bmVyIG9mIHRoaXMgTkZUCiAgICAgICAgICAgICAgICAoaW5uZXItbWludC1uZnQtYXNzZXQgbmZ0LW1pbnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICAgICAgICAgICAgICA7OyBJbiB0aGlzIGNhc2UsIGEgcHJpbmNpcGFsIG90aGVyIHRoYW4gdGhpcyBjb250cmFjdCBvd25zIHRoaXMgTkZULCBzbyBtaW50aW5nIGlzIG5vdCBwb3NzaWJsZQogICAgICAgICAgICAgICAgKGVyciBFUlJfTUlOVF9GQUlMRUQpCiAgICAgICAgICAgICkKICAgICAgICApCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIGRlcG9zaXQgYW4gTkZUIGludG8gdGhlIGNvbnRyYWN0Lgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChkZXBvc2l0LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgICAgIChzdWJuZXQtY29udHJhY3QtaWQgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpKSAoZXJyIEVSUl9ESVNBTExPV0VEX0FTU0VUKSkpCiAgICAgICAgKQoKICAgICAgICA7OyBUcnkgdG8gdHJhbnNmZXIgdGhlIE5GVCB0byB0aGlzIGNvbnRyYWN0CiAgICAgICAgKGFzc2VydHMhICh0cnkhIChpbm5lci10cmFuc2Zlci1uZnQtYXNzZXQgbmZ0LWNvbnRyYWN0IGlkIHNlbmRlciBDT05UUkFDVF9BRERSRVNTKSkgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkKCiAgICAgICAgOzsgRW1pdCBhIHByaW50IGV2ZW50IC0gdGhlIG5vZGUgY29uc3VtZXMgdGhpcwogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAiZGVwb3NpdC1uZnQiLAogICAgICAgICAgICBsMS1jb250cmFjdC1pZDogKGFzLWNvbnRyYWN0IG5mdC1jb250cmFjdCksCiAgICAgICAgICAgIG5mdC1pZDogaWQsCiAgICAgICAgICAgIHNlbmRlcjogc2VuZGVyLAogICAgICAgICAgICBzdWJuZXQtY29udHJhY3QtaWQ6IHN1Ym5ldC1jb250cmFjdC1pZCwKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7IEhlbHBlciBmdW5jdGlvbiBmb3IgYHdpdGhkcmF3LW5mdC1hc3NldGAKOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChpbm5lci13aXRoZHJhdy1uZnQtYXNzZXQKICAgICAgICAobmZ0LWNvbnRyYWN0IDxuZnQtdHJhaXQ+KQogICAgICAgIChsMi1jb250cmFjdCBwcmluY2lwYWwpCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKG5mdC1taW50LWNvbnRyYWN0IChvcHRpb25hbCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pKQogICAgICAgICh3aXRoZHJhd2FsLXJvb3QgKGJ1ZmYgMzIpKQogICAgICAgICh3aXRoZHJhd2FsLWxlYWYtaGFzaCAoYnVmZiAzMikpCiAgICAgICAgKHNpYmxpbmctaGFzaGVzIChsaXN0IDUwIHsKICAgICAgICAgICAgaGFzaDogKGJ1ZmYgMzIpLAogICAgICAgICAgICBpcy1sZWZ0LXNpZGU6IGJvb2wsCiAgICAgICAgfSkpCiAgICApCiAgICAobGV0ICgoaGFzaGVzLWFyZS12YWxpZCAoY2hlY2std2l0aGRyYXdhbC1oYXNoZXMgd2l0aGRyYXdhbC1yb290IHdpdGhkcmF3YWwtbGVhZi1oYXNoIHNpYmxpbmctaGFzaGVzKSkpCgogICAgICAgIChhc3NlcnRzISAodHJ5ISBoYXNoZXMtYXJlLXZhbGlkKSAoZXJyIEVSUl9WQUxJREFUSU9OX0ZBSUxFRCkpCgogICAgICAgIDs7IGNoZWNrIHRoYXQgdGhlIHdpdGhkcmF3YWwgcmVxdWVzdCBkYXRhIG1hdGNoZXMgdGhlIHN1cHBsaWVkIGxlYWYgaGFzaAogICAgICAgIChhc3NlcnRzISAoaXMtZXEgd2l0aGRyYXdhbC1sZWFmLWhhc2gKICAgICAgICAgICAgICAgICAgICAgICAgIChsZWFmLWhhc2gtd2l0aGRyYXctbmZ0IGwyLWNvbnRyYWN0IGlkIHJlY2lwaWVudCB3aXRoZHJhd2FsLWlkIGhlaWdodCkpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEKICAgICAgICAgICAgICAgIChtYXRjaCBuZnQtbWludC1jb250cmFjdAogICAgICAgICAgICAgICAgICAgIG1pbnQtY29udHJhY3QgKGFzLWNvbnRyYWN0IChpbm5lci10cmFuc2Zlci1vci1taW50LW5mdC1hc3NldCBuZnQtY29udHJhY3QgbWludC1jb250cmFjdCBpZCByZWNpcGllbnQpKQogICAgICAgICAgICAgICAgICAgIChhcy1jb250cmFjdCAoaW5uZXItdHJhbnNmZXItd2l0aG91dC1taW50LW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgcmVjaXBpZW50KSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKGZpbmlzaC13aXRoZHJhdyB7IHdpdGhkcmF3YWwtbGVhZi1oYXNoOiB3aXRoZHJhd2FsLWxlYWYtaGFzaCwgd2l0aGRyYXdhbC1yb290LWhhc2g6IHdpdGhkcmF3YWwtcm9vdCB9KQogICAgICAgICAgICAoZXJyIEVSUl9XSVRIRFJBV0FMX0FMUkVBRFlfUFJPQ0VTU0VEKQogICAgICAgICkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIHdpdGhkcmF3IHRoZSBzcGVjaWZpZWQgTkZUIGZyb20gdGhpcyBjb250cmFjdC4KOzsgSW4gb3JkZXIgZm9yIHRoaXMgd2l0aGRyYXdhbCB0byBnbyB0aHJvdWdoLCB0aGUgZ2l2ZW4gd2l0aGRyYXdhbCBtdXN0IGhhdmUgYmVlbiBpbmNsdWRlZAo7OyBpbiBhIHdpdGhkcmF3YWwgTWVya2xlIHRyZWUgYSBzdWJuZXQgbWluZXIgc3VibWl0dGVkLiBUaGUgdXNlciBtdXN0IHByb3ZpZGUgdGhlIGxlYWYKOzsgaGFzaCBvZiB0aGVpciB3aXRoZHJhd2FsIGFuZCB0aGUgcm9vdCBoYXNoIG9mIHRoZSBzcGVjaWZpYyBNZXJrbGUgdHJlZSB0aGVpciB3aXRoZHJhd2FsCjs7IGlzIGluY2x1ZGVkIGluLiBUaGV5IG11c3QgYWxzbyBwcm92aWRlIGEgbGlzdCBvZiBzaWJsaW5nIGhhc2hlcy4gVGhlIHdpdGhkcmF3IGZ1bmN0aW9uCjs7IHVzZXMgdGhlIHByb3ZpZGVkIGhhc2hlcyB0byBlbnN1cmUgdGhlIHJlcXVlc3RlZCB3aXRoZHJhd2FsIGlzIHZhbGlkLgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljICh3aXRoZHJhdy1uZnQtYXNzZXQKICAgICAgICAobmZ0LWNvbnRyYWN0IDxuZnQtdHJhaXQ+KQogICAgICAgIChpZCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgICh3aXRoZHJhd2FsLWlkIHVpbnQpCiAgICAgICAgKGhlaWdodCB1aW50KQogICAgICAgIChuZnQtbWludC1jb250cmFjdCAob3B0aW9uYWwgPG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQ+KSkKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1sZWFmLWhhc2ggKGJ1ZmYgMzIpKQogICAgICAgIChzaWJsaW5nLWhhc2hlcyAobGlzdCA1MCB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIGFzc2V0IGJlbG9uZ3MgdG8gdGhlIGFsbG93ZWQtY29udHJhY3RzIG1hcAogICAgICAgICAgICAobDItY29udHJhY3QgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpKSAoZXJyIEVSUl9ESVNBTExPV0VEX0FTU0VUKSkpCiAgICAgICAgKQogICAgICAgIChhc3NlcnRzIQogICAgICAgICAgICAodHJ5ISAoaW5uZXItd2l0aGRyYXctbmZ0LWFzc2V0CiAgICAgICAgICAgICAgICBuZnQtY29udHJhY3QKICAgICAgICAgICAgICAgIGwyLWNvbnRyYWN0CiAgICAgICAgICAgICAgICBpZAogICAgICAgICAgICAgICAgcmVjaXBpZW50CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLWlkCiAgICAgICAgICAgICAgICBoZWlnaHQKICAgICAgICAgICAgICAgIG5mdC1taW50LWNvbnRyYWN0CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLXJvb3QKICAgICAgICAgICAgICAgIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICBzaWJsaW5nLWhhc2hlcwogICAgICAgICAgICApKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAocHJpbnQgewogICAgICAgICAgICBldmVudDogIndpdGhkcmF3LW5mdCIsCiAgICAgICAgICAgIGwxLWNvbnRyYWN0LWlkOiAoYXMtY29udHJhY3QgbmZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgbmZ0LWlkOiBpZCwKICAgICAgICAgICAgcmVjaXBpZW50OiByZWNpcGllbnQKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7IExpa2UgYGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtbmZ0LWFzc2V0IGJ1dCB3aXRob3V0IGFsbG93aW5nIG9yIHJlcXVpcmluZyBhIG1pbnQgZnVuY3Rpb24uIEluIG9yZGVyIHRvIHdpdGhkcmF3LCB0aGUgdXNlciBtdXN0Cjs7IGhhdmUgdGhlIGFwcHJvcHJpYXRlIGJhbGFuY2UuCihkZWZpbmUtcHJpdmF0ZSAoaW5uZXItdHJhbnNmZXItd2l0aG91dC1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChjb250cmFjdC1jYWxsPyBuZnQtY29udHJhY3QgZ2V0LW93bmVyIGlkKSkKICAgICAgICAgICAgKG5mdC1vd25lciAodW53cmFwISBjYWxsLXJlc3VsdCAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICAgICAoY29udHJhY3Qtb3ducy1uZnQgKGlzLWVxIG5mdC1vd25lciAoc29tZSBDT05UUkFDVF9BRERSRVNTKSkpCiAgICAgICAgKQoKICAgICAgICAoYXNzZXJ0cyEgY29udHJhY3Qtb3ducy1uZnQgKGVyciBFUlJfTkZUX05PVF9PV05FRF9CWV9DT05UUkFDVCkpCiAgICAgICAgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICApCikKCjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OzsKOzsgRk9SIEZVTkdJQkxFIFRPS0VOIEFTU0VUIFRSQU5TRkVSUwoKOzsgSGVscGVyIGZ1bmN0aW9uIHRoYXQgdHJhbnNmZXJzIGEgc3BlY2lmaWVkIGFtb3VudCBvZiB0aGUgZnVuZ2libGUgdG9rZW4gZnJvbSB0aGUgZ2l2ZW4gc2VuZGVyIHRvIHRoZSBnaXZlbiByZWNpcGllbnQuCjs7IFJldHVybnMgcmVzcG9uc2U8Ym9vbCwgaW50PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLWZ0LWFzc2V0CiAgICAgICAgKGZ0LWNvbnRyYWN0IDxmdC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChzZW5kZXIgcHJpbmNpcGFsKQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoY29udHJhY3QtY2FsbD8gZnQtY29udHJhY3QgdHJhbnNmZXIgYW1vdW50IHNlbmRlciByZWNpcGllbnQgbWVtbykpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICApCiAgICAgICAgOzsgRklYTUU6IFNJUC0wMTAgZG9lc24ndCByZXF1aXJlIHRoYXQgdHJhbnNmZXIgcmV0dXJucyAob2sgdHJ1ZSkgb24gc3VjY2Vzcywgc28gaXMgdGhpcyBjaGVjayBuZWNlc3Nhcnk/CiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdHJhbnNmZXIgc3VjY2VlZGVkCiAgICAgICAgKGFzc2VydHMhIHRyYW5zZmVyLXJlc3VsdCAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci1taW50LWZ0LWFzc2V0CiAgICAgICAgKGZ0LW1pbnQtY29udHJhY3QgPG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQ+KQogICAgICAgIChhbW91bnQgdWludCkKICAgICAgICAoc2VuZGVyIHByaW5jaXBhbCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY2FsbC1yZXN1bHQgKGFzLWNvbnRyYWN0IChjb250cmFjdC1jYWxsPyBmdC1taW50LWNvbnRyYWN0IG1pbnQtZnJvbS1zdWJuZXQgYW1vdW50IHNlbmRlciByZWNpcGllbnQpKSkKICAgICAgICAgICAgKG1pbnQtcmVzdWx0ICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgKQogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRyYW5zZmVyIHN1Y2NlZWRlZAogICAgICAgIChhc3NlcnRzISBtaW50LXJlc3VsdCAoZXJyIEVSUl9NSU5UX0ZBSUxFRCkpCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoZnQtbWludC1jb250cmFjdCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoY29udHJhY3QtY2FsbD8gZnQtY29udHJhY3QgZ2V0LWJhbGFuY2UgQ09OVFJBQ1RfQUREUkVTUykpCiAgICAgICAgICAgIChjb250cmFjdC1mdC1iYWxhbmNlICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgICAgIChjb250cmFjdC1vd25zLWVub3VnaCAoPj0gY29udHJhY3QtZnQtYmFsYW5jZSBhbW91bnQpKQogICAgICAgICAgICAoYW1vdW50LXRvLXRyYW5zZmVyIChpZiBjb250cmFjdC1vd25zLWVub3VnaCBhbW91bnQgY29udHJhY3QtZnQtYmFsYW5jZSkpCiAgICAgICAgICAgIChhbW91bnQtdG8tbWludCAoLSBhbW91bnQgYW1vdW50LXRvLXRyYW5zZmVyKSkKICAgICAgICApCgogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRvdGFsIGJhbGFuY2UgYmV0d2VlbiB0aGUgdHJhbnNmZXIgYW5kIG1pbnQgaXMgZXF1YWwgdG8gdGhlIG9yaWdpbmFsIGJhbGFuY2UKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIGFtb3VudCAoKyBhbW91bnQtdG8tdHJhbnNmZXIgYW1vdW50LXRvLW1pbnQpKSAoZXJyIEVSUl9JTl9DT01QVVRBVElPTikpCgogICAgICAgIChhbmQKICAgICAgICAgICAgKD4gYW1vdW50LXRvLXRyYW5zZmVyIHUwKQogICAgICAgICAgICAodHJ5ISAoaW5uZXItdHJhbnNmZXItZnQtYXNzZXQgZnQtY29udHJhY3QgYW1vdW50LXRvLXRyYW5zZmVyIENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICkKICAgICAgICAoYW5kCiAgICAgICAgICAgICg+IGFtb3VudC10by1taW50IHUwKQogICAgICAgICAgICAodHJ5ISAoaW5uZXItbWludC1mdC1hc3NldCBmdC1taW50LWNvbnRyYWN0IGFtb3VudC10by1taW50IENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50KSkKICAgICAgICApCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgo7OyBBIHVzZXIgY2FsbHMgdGhpcyBmdW5jdGlvbiB0byBkZXBvc2l0IGEgZnVuZ2libGUgdG9rZW4gaW50byB0aGUgY29udHJhY3QuCjs7IFRoZSBmdW5jdGlvbiBlbWl0cyBhIHByaW50IHdpdGggZGV0YWlscyBvZiB0aGlzIGV2ZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wdWJsaWMgKGRlcG9zaXQtZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKG1lbW8gKG9wdGlvbmFsIChidWZmIDM0KSkpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgICAgIChzdWJuZXQtY29udHJhY3QtaWQgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkpIChlcnIgRVJSX0RJU0FMTE9XRURfQVNTRVQpKSkKICAgICAgICApCiAgICAgICAgOzsgVHJ5IHRvIHRyYW5zZmVyIHRoZSBGVCB0byB0aGlzIGNvbnRyYWN0CiAgICAgICAgKGFzc2VydHMhICh0cnkhIChpbm5lci10cmFuc2Zlci1mdC1hc3NldCBmdC1jb250cmFjdCBhbW91bnQgc2VuZGVyIENPTlRSQUNUX0FERFJFU1MgbWVtbykpIChlcnIgRVJSX1RSQU5TRkVSX0ZBSUxFRCkpCgogICAgICAgIChsZXQgKAogICAgICAgICAgICAgICAgKGZ0LW5hbWUgKHVud3JhcCEgKGNvbnRyYWN0LWNhbGw/IGZ0LWNvbnRyYWN0IGdldC1uYW1lKSAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICAgICApCiAgICAgICAgICAgIDs7IEVtaXQgYSBwcmludCBldmVudCAtIHRoZSBub2RlIGNvbnN1bWVzIHRoaXMKICAgICAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgICAgIGV2ZW50OiAiZGVwb3NpdC1mdCIsCiAgICAgICAgICAgICAgICBsMS1jb250cmFjdC1pZDogKGFzLWNvbnRyYWN0IGZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgICAgIGZ0LW5hbWU6IGZ0LW5hbWUsCiAgICAgICAgICAgICAgICBmdC1hbW91bnQ6IGFtb3VudCwKICAgICAgICAgICAgICAgIHNlbmRlcjogc2VuZGVyLAogICAgICAgICAgICAgICAgc3VibmV0LWNvbnRyYWN0LWlkOiBzdWJuZXQtY29udHJhY3QtaWQsCiAgICAgICAgICAgIH0pCiAgICAgICAgKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKOzsgVGhpcyBmdW5jdGlvbiBwZXJmb3JtcyB2YWxpZGl0eSBjaGVja3MgcmVsYXRlZCB0byB0aGUgd2l0aGRyYXdhbCBhbmQgcGVyZm9ybXMgdGhlIHdpdGhkcmF3YWwgYXMgd2VsbC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHJpdmF0ZSAoaW5uZXItd2l0aGRyYXctZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKG1lbW8gKG9wdGlvbmFsIChidWZmIDM0KSkpCiAgICAgICAgKGZ0LW1pbnQtY29udHJhY3QgKG9wdGlvbmFsIDxtaW50LWZyb20tc3VibmV0LXRyYWl0PikpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICAgICAgKHdpdGhkcmF3YWwtbGVhZi1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoZXMgKGxpc3QgNTAgewogICAgICAgICAgICBoYXNoOiAoYnVmZiAzMiksCiAgICAgICAgICAgIGlzLWxlZnQtc2lkZTogYm9vbCwKICAgICAgICB9KSkKICAgICkKICAgIChsZXQgKChoYXNoZXMtYXJlLXZhbGlkIChjaGVjay13aXRoZHJhd2FsLWhhc2hlcyB3aXRoZHJhd2FsLXJvb3Qgd2l0aGRyYXdhbC1sZWFmLWhhc2ggc2libGluZy1oYXNoZXMpKSkKICAgICAgICAoYXNzZXJ0cyEgKHRyeSEgaGFzaGVzLWFyZS12YWxpZCkgKGVyciBFUlJfVkFMSURBVElPTl9GQUlMRUQpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSB3aXRoZHJhd2FsIHJlcXVlc3QgZGF0YSBtYXRjaGVzIHRoZSBzdXBwbGllZCBsZWFmIGhhc2gKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICAgICAgICAgICAobGVhZi1oYXNoLXdpdGhkcmF3LWZ0IChjb250cmFjdC1vZiBmdC1jb250cmFjdCkgYW1vdW50IHJlY2lwaWVudCB3aXRoZHJhd2FsLWlkIGhlaWdodCkpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEKICAgICAgICAgICAgICAgIChtYXRjaCBmdC1taW50LWNvbnRyYWN0CiAgICAgICAgICAgICAgICAgICAgbWludC1jb250cmFjdCAoYXMtY29udHJhY3QgKGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtZnQtYXNzZXQgZnQtY29udHJhY3QgbWludC1jb250cmFjdCBhbW91bnQgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICAgICAgICAgICAgIChhcy1jb250cmFjdCAoaW5uZXItdHJhbnNmZXItZnQtYXNzZXQgZnQtY29udHJhY3QgYW1vdW50IENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICApCiAgICAgICAgICAgIChlcnIgRVJSX1RSQU5TRkVSX0ZBSUxFRCkKICAgICAgICApCgogICAgICAgIChhc3NlcnRzIQogICAgICAgICAgKGZpbmlzaC13aXRoZHJhdyB7IHdpdGhkcmF3YWwtbGVhZi1oYXNoOiB3aXRoZHJhd2FsLWxlYWYtaGFzaCwgd2l0aGRyYXdhbC1yb290LWhhc2g6IHdpdGhkcmF3YWwtcm9vdCB9KQogICAgICAgICAgKGVyciBFUlJfV0lUSERSQVdBTF9BTFJFQURZX1BST0NFU1NFRCkpCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgo7OyBBIHVzZXIgY2FuIGNhbGwgdGhpcyBmdW5jdGlvbiB0byB3aXRoZHJhdyBzb21lIGFtb3VudCBvZiBhIGZ1bmdpYmxlIHRva2VuIGFzc2V0IGZyb20gdGhlCjs7IGNvbnRyYWN0IGFuZCBzZW5kIGl0IHRvIGEgcmVjaXBpZW50Lgo7OyBJbiBvcmRlciBmb3IgdGhpcyB3aXRoZHJhd2FsIHRvIGdvIHRocm91Z2gsIHRoZSBnaXZlbiB3aXRoZHJhd2FsIG11c3QgaGF2ZSBiZWVuIGluY2x1ZGVkCjs7IGluIGEgd2l0aGRyYXdhbCBNZXJrbGUgdHJlZSBhIHN1Ym5ldCBtaW5lciBzdWJtaXR0ZWQuIFRoZSB1c2VyIG11c3QgcHJvdmlkZSB0aGUgbGVhZgo7OyBoYXNoIG9mIHRoZWlyIHdpdGhkcmF3YWwgYW5kIHRoZSByb290IGhhc2ggb2YgdGhlIHNwZWNpZmljIE1lcmtsZSB0cmVlIHRoZWlyIHdpdGhkcmF3YWwKOzsgaXMgaW5jbHVkZWQgaW4uIFRoZXkgbXVzdCBhbHNvIHByb3ZpZGUgYSBsaXN0IG9mIHNpYmxpbmcgaGFzaGVzLiBUaGUgd2l0aGRyYXcgZnVuY3Rpb24KOzsgdXNlcyB0aGUgcHJvdmlkZWQgaGFzaGVzIHRvIGVuc3VyZSB0aGUgcmVxdWVzdGVkIHdpdGhkcmF3YWwgaXMgdmFsaWQuCjs7IFRoZSBmdW5jdGlvbiBlbWl0cyBhIHByaW50IHdpdGggZGV0YWlscyBvZiB0aGlzIGV2ZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wdWJsaWMgKHdpdGhkcmF3LWZ0LWFzc2V0CiAgICAgICAgKGZ0LWNvbnRyYWN0IDxmdC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgICh3aXRoZHJhd2FsLWlkIHVpbnQpCiAgICAgICAgKGhlaWdodCB1aW50KQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgICAgIChmdC1taW50LWNvbnRyYWN0IChvcHRpb25hbCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pKQogICAgICAgICh3aXRoZHJhd2FsLXJvb3QgKGJ1ZmYgMzIpKQogICAgICAgICh3aXRoZHJhd2FsLWxlYWYtaGFzaCAoYnVmZiAzMikpCiAgICAgICAgKHNpYmxpbmctaGFzaGVzIChsaXN0IDUwIHsKICAgICAgICAgICAgaGFzaDogKGJ1ZmYgMzIpLAogICAgICAgICAgICBpcy1sZWZ0LXNpZGU6IGJvb2wsCiAgICAgICAgfSkpCiAgICApCiAgICAoYmVnaW4KICAgICAgICA7OyBDaGVjayB0aGF0IHRoZSB3aXRoZHJhdyBhbW91bnQgaXMgcG9zaXRpdmUKICAgICAgICAoYXNzZXJ0cyEgKD4gYW1vdW50IHUwKSAoZXJyIEVSUl9BVFRFTVBUX1RPX1RSQU5TRkVSX1pFUk9fQU1PVU5UKSkKCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkpIChlcnIgRVJSX0RJU0FMTE9XRURfQVNTRVQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEgKGlubmVyLXdpdGhkcmF3LWZ0LWFzc2V0CiAgICAgICAgICAgICAgICBmdC1jb250cmFjdAogICAgICAgICAgICAgICAgYW1vdW50CiAgICAgICAgICAgICAgICByZWNpcGllbnQKICAgICAgICAgICAgICAgIHdpdGhkcmF3YWwtaWQKICAgICAgICAgICAgICAgIGhlaWdodAogICAgICAgICAgICAgICAgbWVtbwogICAgICAgICAgICAgICAgZnQtbWludC1jb250cmFjdAogICAgICAgICAgICAgICAgd2l0aGRyYXdhbC1yb290CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLWxlYWYtaGFzaAogICAgICAgICAgICAgICAgc2libGluZy1oYXNoZXMpKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICAobGV0ICgKICAgICAgICAgICAgICAgIChmdC1uYW1lICh1bndyYXAhIChjb250cmFjdC1jYWxsPyBmdC1jb250cmFjdCBnZXQtbmFtZSkgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICAgICAgKQogICAgICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgICAgIGV2ZW50OiAid2l0aGRyYXctZnQiLAogICAgICAgICAgICAgICAgbDEtY29udHJhY3QtaWQ6IChhcy1jb250cmFjdCBmdC1jb250cmFjdCksCiAgICAgICAgICAgICAgICBmdC1uYW1lOiBmdC1uYW1lLAogICAgICAgICAgICAgICAgZnQtYW1vdW50OiBhbW91bnQsCiAgICAgICAgICAgICAgICByZWNpcGllbnQ6IHJlY2lwaWVudCwKICAgICAgICAgICAgfSkKICAgICAgICApCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgoKOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Cjs7IEZPUiBTVFggVFJBTlNGRVJTCgoKOzsgSGVscGVyIGZ1bmN0aW9uIHRoYXQgdHJhbnNmZXJzIHRoZSBnaXZlbiBhbW91bnQgZnJvbSB0aGUgc3BlY2lmaWVkIGZ1bmdpYmxlIHRva2VuIGZyb20gdGhlIGdpdmVuIHNlbmRlciB0byB0aGUgZ2l2ZW4gcmVjaXBpZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wcml2YXRlIChpbm5lci10cmFuc2Zlci1zdHggKGFtb3VudCB1aW50KSAoc2VuZGVyIHByaW5jaXBhbCkgKHJlY2lwaWVudCBwcmluY2lwYWwpKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoc3R4LXRyYW5zZmVyPyBhbW91bnQgc2VuZGVyIHJlY2lwaWVudCkpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkpCiAgICAgICAgKQogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRyYW5zZmVyIHN1Y2NlZWRlZAogICAgICAgIChhc3NlcnRzISB0cmFuc2Zlci1yZXN1bHQgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIGRlcG9zaXQgU1RYIGludG8gdGhlIGNvbnRyYWN0Lgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChkZXBvc2l0LXN0eCAoYW1vdW50IHVpbnQpIChzZW5kZXIgcHJpbmNpcGFsKSkKICAgIChiZWdpbgogICAgICAgIDs7IFRyeSB0byB0cmFuc2ZlciB0aGUgU1RYIHRvIHRoaXMgY29udHJhY3QKICAgICAgICAoYXNzZXJ0cyEgKHRyeSEgKGlubmVyLXRyYW5zZmVyLXN0eCBhbW91bnQgc2VuZGVyIENPTlRSQUNUX0FERFJFU1MpKSAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQgLSB0aGUgbm9kZSBjb25zdW1lcyB0aGlzCiAgICAgICAgKHByaW50IHsgZXZlbnQ6ICJkZXBvc2l0LXN0eCIsIHNlbmRlcjogc2VuZGVyLCBhbW91bnQ6IGFtb3VudCB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGxlYWYtaGFzaC13aXRoZHJhdy1zdHgKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICApCiAgICAoc2hhNTEyLzI1NiAoY29uY2F0IDB4MDAgKHVud3JhcC1wYW5pYyAodG8tY29uc2Vuc3VzLWJ1ZmY/CiAgICAgICAgewogICAgICAgICAgICB0eXBlOiAic3R4IiwKICAgICAgICAgICAgYW1vdW50OiBhbW91bnQsCiAgICAgICAgICAgIHJlY2lwaWVudDogcmVjaXBpZW50LAogICAgICAgICAgICB3aXRoZHJhd2FsLWlkOiB3aXRoZHJhd2FsLWlkLAogICAgICAgICAgICBoZWlnaHQ6IGhlaWdodAogICAgICAgIH0pKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGxlYWYtaGFzaC13aXRoZHJhdy1uZnQKICAgICAgICAoYXNzZXQtY29udHJhY3QgcHJpbmNpcGFsKQogICAgICAgIChuZnQtaWQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICAgICAod2l0aGRyYXdhbC1pZCB1aW50KQogICAgICAgIChoZWlnaHQgdWludCkKICAgICkKICAgIChzaGE1MTIvMjU2IChjb25jYXQgMHgwMCAodW53cmFwLXBhbmljICh0by1jb25zZW5zdXMtYnVmZj8KICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJuZnQiLAogICAgICAgICAgICBuZnQtaWQ6IG5mdC1pZCwKICAgICAgICAgICAgYXNzZXQtY29udHJhY3Q6IGFzc2V0LWNvbnRyYWN0LAogICAgICAgICAgICByZWNpcGllbnQ6IHJlY2lwaWVudCwKICAgICAgICAgICAgd2l0aGRyYXdhbC1pZDogd2l0aGRyYXdhbC1pZCwKICAgICAgICAgICAgaGVpZ2h0OiBoZWlnaHQKICAgICAgICB9KSkpCiAgICApCikKCihkZWZpbmUtcmVhZC1vbmx5IChsZWFmLWhhc2gtd2l0aGRyYXctZnQKICAgICAgICAoYXNzZXQtY29udHJhY3QgcHJpbmNpcGFsKQogICAgICAgIChhbW91bnQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICAgICAod2l0aGRyYXdhbC1pZCB1aW50KQogICAgICAgIChoZWlnaHQgdWludCkKICAgICkKICAgIChzaGE1MTIvMjU2IChjb25jYXQgMHgwMCAodW53cmFwLXBhbmljICh0by1jb25zZW5zdXMtYnVmZj8KICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJmdCIsCiAgICAgICAgICAgIGFtb3VudDogYW1vdW50LAogICAgICAgICAgICBhc3NldC1jb250cmFjdDogYXNzZXQtY29udHJhY3QsCiAgICAgICAgICAgIHJlY2lwaWVudDogcmVjaXBpZW50LAogICAgICAgICAgICB3aXRoZHJhd2FsLWlkOiB3aXRoZHJhd2FsLWlkLAogICAgICAgICAgICBoZWlnaHQ6IGhlaWdodAogICAgICAgIH0pKSkKICAgICkKKQoKOzsgQSB1c2VyIGNhbGxzIHRoaXMgZnVuY3Rpb24gdG8gd2l0aGRyYXcgU1RYIGZyb20gdGhpcyBjb250cmFjdC4KOzsgSW4gb3JkZXIgZm9yIHRoaXMgd2l0aGRyYXdhbCB0byBnbyB0aHJvdWdoLCB0aGUgZ2l2ZW4gd2l0aGRyYXdhbCBtdXN0IGhhdmUgYmVlbiBpbmNsdWRlZAo7OyBpbiBhIHdpdGhkcmF3YWwgTWVya2xlIHRyZWUgYSBzdWJuZXQgbWluZXIgc3VibWl0dGVkLiBUaGUgdXNlciBtdXN0IHByb3ZpZGUgdGhlIGxlYWYKOzsgaGFzaCBvZiB0aGVpciB3aXRoZHJhd2FsIGFuZCB0aGUgcm9vdCBoYXNoIG9mIHRoZSBzcGVjaWZpYyBNZXJrbGUgdHJlZSB0aGVpciB3aXRoZHJhd2FsCjs7IGlzIGluY2x1ZGVkIGluLiBUaGV5IG11c3QgYWxzbyBwcm92aWRlIGEgbGlzdCBvZiBzaWJsaW5nIGhhc2hlcy4gVGhlIHdpdGhkcmF3IGZ1bmN0aW9uCjs7IHVzZXMgdGhlIHByb3ZpZGVkIGhhc2hlcyB0byBlbnN1cmUgdGhlIHJlcXVlc3RlZCB3aXRoZHJhd2FsIGlzIHZhbGlkLgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljICh3aXRoZHJhdy1zdHgKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICAgICAgKHdpdGhkcmF3YWwtbGVhZi1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoZXMgKGxpc3QgNTAgewogICAgICAgICAgICBoYXNoOiAoYnVmZiAzMiksCiAgICAgICAgICAgIGlzLWxlZnQtc2lkZTogYm9vbCwKICAgICAgICB9KSkKICAgICkKICAgIChsZXQgKChoYXNoZXMtYXJlLXZhbGlkIChjaGVjay13aXRoZHJhd2FsLWhhc2hlcyB3aXRoZHJhd2FsLXJvb3Qgd2l0aGRyYXdhbC1sZWFmLWhhc2ggc2libGluZy1oYXNoZXMpKSkKCiAgICAgICAgKGFzc2VydHMhICh0cnkhIGhhc2hlcy1hcmUtdmFsaWQpIChlcnIgRVJSX1ZBTElEQVRJT05fRkFJTEVEKSkKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSB3aXRoZHJhd2FsIHJlcXVlc3QgZGF0YSBtYXRjaGVzIHRoZSBzdXBwbGllZCBsZWFmIGhhc2gKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICAgICAgICAgICAobGVhZi1oYXNoLXdpdGhkcmF3LXN0eCBhbW91bnQgcmVjaXBpZW50IHdpdGhkcmF3YWwtaWQgaGVpZ2h0KSkKICAgICAgICAgICAgICAgICAgKGVyciBFUlJfVkFMSURBVElPTl9MRUFGX0ZBSUxFRCkpCgogICAgICAgIChhc3NlcnRzISAodHJ5ISAoYXMtY29udHJhY3QgKGlubmVyLXRyYW5zZmVyLXN0eCBhbW91bnQgdHgtc2VuZGVyIHJlY2lwaWVudCkpKSAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgIChmaW5pc2gtd2l0aGRyYXcgeyB3aXRoZHJhd2FsLWxlYWYtaGFzaDogd2l0aGRyYXdhbC1sZWFmLWhhc2gsIHdpdGhkcmF3YWwtcm9vdC1oYXNoOiB3aXRoZHJhd2FsLXJvb3QgfSkKICAgICAgICAgIChlcnIgRVJSX1dJVEhEUkFXQUxfQUxSRUFEWV9QUk9DRVNTRUQpKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAocHJpbnQgeyBldmVudDogIndpdGhkcmF3LXN0eCIsIHJlY2lwaWVudDogcmVjaXBpZW50LCBhbW91bnQ6IGFtb3VudCB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Owo7OyBHRU5FUkFMIFdJVEhEUkFXQUwgRlVOQ1RJT05TCgo7OyBUaGlzIGZ1bmN0aW9uIGNvbmNhdHMgdGhlIHR3byBnaXZlbiBoYXNoZXMgaW4gdGhlIGNvcnJlY3Qgb3JkZXIuIEl0IGFsc28gcHJlcGVuZHMgdGhlIGJ1ZmYgYDB4MDFgLCB3aGljaCBpcwo7OyBhIHRhZyBkZW5vdGluZyBhIG5vZGUgKHZlcnN1cyBhIGxlYWYpLgo7OyBSZXR1cm5zIGEgYnVmZgooZGVmaW5lLXByaXZhdGUgKGNyZWF0ZS1ub2RlLWhhc2gKICAgICAgICAoY3Vyci1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoIChidWZmIDMyKSkKICAgICAgICAoaXMtc2libGluZy1sZWZ0LXNpZGUgYm9vbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY29uY2F0dGVkLWhhc2ggKGlmIGlzLXNpYmxpbmctbGVmdC1zaWRlCiAgICAgICAgICAgICAgICAgICAgKGNvbmNhdCBzaWJsaW5nLWhhc2ggY3Vyci1oYXNoKQogICAgICAgICAgICAgICAgICAgIChjb25jYXQgY3Vyci1oYXNoIHNpYmxpbmctaGFzaCkKICAgICAgICAgICAgICAgICkpCiAgICAgICAgICApCgogICAgICAgICAgKGNvbmNhdCAweDAxIGNvbmNhdHRlZC1oYXNoKQogICAgKQopCgo7OyBUaGlzIGZ1bmN0aW9uIGhhc2hlcyB0aGUgY3VyciBoYXNoIHdpdGggaXRzIHNpYmxpbmcgaGFzaC4KOzsgUmV0dXJucyAoYnVmZiAzMikKKGRlZmluZS1wcml2YXRlIChoYXNoLWhlbHAKICAgICAgICAoc2libGluZyB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pCiAgICAgICAgKGN1cnItbm9kZS1oYXNoIChidWZmIDMyKSkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoc2libGluZy1oYXNoIChnZXQgaGFzaCBzaWJsaW5nKSkKICAgICAgICAgICAgKGlzLXNpYmxpbmctbGVmdC1zaWRlIChnZXQgaXMtbGVmdC1zaWRlIHNpYmxpbmcpKQogICAgICAgICAgICAobmV3LWJ1ZmYgKGNyZWF0ZS1ub2RlLWhhc2ggY3Vyci1ub2RlLWhhc2ggc2libGluZy1oYXNoIGlzLXNpYmxpbmctbGVmdC1zaWRlKSkKICAgICAgICApCiAgICAgICAoc2hhNTEyLzI1NiBuZXctYnVmZikKICAgICkKKQoKOzsgVGhpcyBmdW5jdGlvbiBjaGVja3M6Cjs7ICAtIFRoYXQgdGhlIHByb3ZpZGVkIHdpdGhkcmF3YWwgcm9vdCBtYXRjaGVzIGEgcHJldmlvdXNseSBzdWJtaXR0ZWQgb25lIChwYXNzZWQgdG8gdGhlIGZ1bmN0aW9uIGBjb21taXQtYmxvY2tgKQo7OyAgLSBUaGF0IHRoZSBjb21wdXRlZCB3aXRoZHJhd2FsIHJvb3QgbWF0Y2hlcyBhIHByZXZpb3VzIHZhbGlkIHdpdGhkcmF3YWwgcm9vdAo7OyAgLSBUaGF0IHRoZSBnaXZlbiB3aXRoZHJhd2FsIGxlYWYgaGFzaCBoYXMgbm90IGJlZW4gcHJldmlvdXNseSBwcm9jZXNzZWQKOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHJpdmF0ZSAoY2hlY2std2l0aGRyYXdhbC1oYXNoZXMKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1sZWFmLWhhc2ggKGJ1ZmYgMzIpKQogICAgICAgIChzaWJsaW5nLWhhc2hlcyAobGlzdCA1MCB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pKQogICAgKQogICAgKGJlZ2luCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdXNlciBzdWJtaXR0ZWQgYSB2YWxpZCB3aXRoZHJhd2FsIHJvb3QKICAgICAgICAoYXNzZXJ0cyEgKGlzLXNvbWUgKG1hcC1nZXQ/IHdpdGhkcmF3YWwtcm9vdHMtbWFwIHdpdGhkcmF3YWwtcm9vdCkpIChlcnIgRVJSX0lOVkFMSURfTUVSS0xFX1JPT1QpKQoKICAgICAgICA7OyBDaGVjayB0aGF0IHRoaXMgd2l0aGRyYXdhbCBsZWFmIGhhcyBub3QgYmVlbiBwcm9jZXNzZWQgYmVmb3JlCiAgICAgICAgKGFzc2VydHMhCiAgICAgICAgICAgIChpcy1ub25lCiAgICAgICAgICAgICAobWFwLWdldD8gcHJvY2Vzc2VkLXdpdGhkcmF3YWwtbGVhdmVzLW1hcAogICAgICAgICAgICAgICAgICAgICAgIHsgd2l0aGRyYXdhbC1sZWFmLWhhc2g6IHdpdGhkcmF3YWwtbGVhZi1oYXNoLCB3aXRoZHJhd2FsLXJvb3QtaGFzaDogd2l0aGRyYXdhbC1yb290IH0pKQogICAgICAgICAgICAoZXJyIEVSUl9XSVRIRFJBV0FMX0FMUkVBRFlfUFJPQ0VTU0VEKSkKCiAgICAgICAgKGxldCAoKGNhbGN1bGF0ZWQtd2l0aGRyYXdhbC1yb290IChmb2xkIGhhc2gtaGVscCBzaWJsaW5nLWhhc2hlcyB3aXRoZHJhd2FsLWxlYWYtaGFzaCkpCiAgICAgICAgICAgICAgKHJvb3RzLW1hdGNoIChpcy1lcSBjYWxjdWxhdGVkLXdpdGhkcmF3YWwtcm9vdCB3aXRoZHJhd2FsLXJvb3QpKSkKICAgICAgICAgICAgIChpZiByb290cy1tYXRjaAogICAgICAgICAgICAgICAgKG9rIHRydWUpCiAgICAgICAgICAgICAgICAoZXJyIEVSUl9NRVJLTEVfUk9PVF9ET0VTX05PVF9NQVRDSCkpCiAgICAgICAgKQogICAgKQopCgo7OyBUaGlzIGZ1bmN0aW9uIHNob3VsZCBiZSBjYWxsZWQgYWZ0ZXIgdGhlIGFzc2V0IGluIHF1ZXN0aW9uIGhhcyBiZWVuIHRyYW5zZmVycmVkLgo7OyBJdCBhZGRzIHRoZSB3aXRoZHJhd2FsIGxlYWYgaGFzaCB0byBhIG1hcCBvZiBwcm9jZXNzZWQgbGVhdmVzLiBUaGlzIGVuc3VyZXMgdGhhdAo7OyB0aGlzIHdpdGhkcmF3YWwgbGVhZiBjYW4ndCBiZSB1c2VkIGFnYWluIHRvIHdpdGhkcmF3IGFkZGl0aW9uYWwgZnVuZHMuCjs7IFJldHVybnMgYm9vbAooZGVmaW5lLXByaXZhdGUgKGZpbmlzaC13aXRoZHJhdwogICAgICAgICh3aXRoZHJhdy1pbmZvIHsKICAgICAgICAgICAgd2l0aGRyYXdhbC1sZWFmLWhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgd2l0aGRyYXdhbC1yb290LWhhc2g6IChidWZmIDMyKQogICAgICAgIH0pCiAgICApCiAgICAobWFwLWluc2VydCBwcm9jZXNzZWQtd2l0aGRyYXdhbC1sZWF2ZXMtbWFwIHdpdGhkcmF3LWluZm8gdHJ1ZSkKKQo= + clarity_version: 2 + cost: 290960 + location: + path: "./.cache/requirements/ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1-2.clar" + - transaction_type: ContractPublish + contract_name: px + expected_sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + location: + path: contracts/px.clar + source: Cjs7IHRpdGxlOiBweAo7OyB2ZXJzaW9uOgo7OyBzdW1tYXJ5Ogo7OyBkZXNjcmlwdGlvbjogQWxsb3dzIHVzZXJzIHRvIHBheSB0byB1cGRhdGUgZGF0YSBpbiBhIG1hdHJpeC4gCjs7ICBFYWNoIG1hdHJpeCB2YWx1ZSBtdXN0IGJlIGEgaGV4YWRlY2ltYWwgdmFsdWUgZnJvbSAweDAwMDAwMCB0byAweGZmZmZmZiwgcmVwcmVzZW50aW5nIGEgY29sb3IgdG8gYmUgZGlzcGxheWVkIG9uIGEgZ3JpZCBpbiBhIHdlYiBwYWdlLiAKOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuCgo7OyB0cmFpdHMKOzsKCjs7IHRva2VuIGRlZmluaXRpb25zCjs7IAoKOzsgY29uc3RhbnRzCjs7CihkZWZpbmUtY29uc3RhbnQgTUFYX0xPQyB1MTAwKQooZGVmaW5lLWNvbnN0YW50IE1BWF9WQUwgMHhmZmZmZmYpCihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMCkKKGRlZmluZS1jb25zdGFudCBBTExfTE9DUyAobGlzdCB1MCB1MSB1MiB1MyB1NCB1NSB1NiB1NyB1OCB1OSB1MTAgdTExIHUxMiB1MTMgdTE0IHUxNSB1MTYgdTE3IHUxOCB1MTkgdTIwIHUyMSB1MjIgdTIzIHUyNCB1MjUgdTI2IHUyNyB1MjggdTI5IHUzMCB1MzEgdTMyIHUzMyB1MzQgdTM1IHUzNiB1MzcgdTM4IHUzOSB1NDAgdTQxIHU0MiB1NDMgdTQ0IHU0NSB1NDYgdTQ3IHU0OCB1NDkgdTUwIHU1MSB1NTIgdTUzIHU1NCB1NTUgdTU2IHU1NyB1NTggdTU5IHU2MCB1NjEgdTYyIHU2MyB1NjQgdTY1IHU2NiB1NjcgdTY4IHU2OSB1NzAgdTcxIHU3MiB1NzMgdTc0IHU3NSB1NzYgdTc3IHU3OCB1NzkgdTgwIHU4MSB1ODIgdTgzIHU4NCB1ODUgdTg2IHU4NyB1ODggdTg5IHU5MCB1OTEgdTkyIHU5MyB1OTQgdTk1IHU5NiB1OTcgdTk4IHU5OSkpCjs7IGRhdGEgdmFycwo7OwoKOzsgZGF0YSBtYXBzCjs7CihkZWZpbmUtbWFwIHBpeGVscyB1aW50IChidWZmIDMpKQoKOzsgcHVibGljIGZ1bmN0aW9ucwo7OwooZGVmaW5lLXB1YmxpYyAoc2V0LXZhbHVlLWF0IChsb2MgdWludCkgKHZhbHVlIChidWZmIDMpKSkgCiAgICAoYmVnaW4gCiAgICAgICAgKGlmICg+PSBsb2MgTUFYX0xPQykKICAgICAgICAgICAgKGVyciAiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy4iKQogICAgICAgICAgICAoaWYgKD4gdmFsdWUgTUFYX1ZBTCkKICAgICAgICAgICAgICAgIChlcnIgIlZhbHVlIG11c3QgYmUgbGVzcyB0aGFuIDB4ZmZmZmZmLiIpCiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTCkKICAgICAgICAgICAgICAgICAgICAoZXJyICJWYWx1ZSBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAweDAwMDAwMC4iKQogICAgICAgICAgICAgICAgICAgIChvayAobWFwLXNldCBwaXhlbHMgbG9jIHZhbHVlKSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICkKICAgICkKKQo7OyByZWFkIG9ubHkgZnVuY3Rpb25zCjs7CgooZGVmaW5lLXJlYWQtb25seSAoZ2V0LXZhbHVlLWF0IChsb2MgdWludCkpCiAgICAoaWYgKD49IGxvYyBNQVhfTE9DKQogICAgICAgIChlcnIgIk91dCBvZiBib3VuZHMuIikKICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGdldC1hbGwpIAogICAgKG1hcCBnZXQtdmFsdWUtYXQgQUxMX0xPQ1MpCikKCihkZWZpbmUtcmVhZC1vbmx5IChnZW5lc2lzLXRpbWUgKGhlaWdodCB1aW50KSkKICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpCikKOzsgcHJpdmF0ZSBmdW5jdGlvbnMKOzsK + clarity_version: 2 + cost: 18060 + anchor_block_only: true + epoch: "2.1" + - id: 3 + transactions: + - transaction_type: ContractCall + contract_id: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.px + expected_sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + method: set-value-at + parameters: + - u0 + - "0xfffffa" + cost: 2240 + anchor_block_only: false + - transaction_type: StxTransfer + expected_sender: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB + recipient: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 + mstx_amount: 200 + memo: "0xabcabcabcaba00000000000000000000000000000000000000000000000000000000" + cost: 2240 + anchor_block_only: true + epoch: "2.1" +contracts: + - contract_id: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.px + path: /Users/micaiahreid/work/stx-px/contracts/px.clar + source: Cjs7IHRpdGxlOiBweAo7OyB2ZXJzaW9uOgo7OyBzdW1tYXJ5Ogo7OyBkZXNjcmlwdGlvbjogQWxsb3dzIHVzZXJzIHRvIHBheSB0byB1cGRhdGUgZGF0YSBpbiBhIG1hdHJpeC4gCjs7ICBFYWNoIG1hdHJpeCB2YWx1ZSBtdXN0IGJlIGEgaGV4YWRlY2ltYWwgdmFsdWUgZnJvbSAweDAwMDAwMCB0byAweGZmZmZmZiwgcmVwcmVzZW50aW5nIGEgY29sb3IgdG8gYmUgZGlzcGxheWVkIG9uIGEgZ3JpZCBpbiBhIHdlYiBwYWdlLiAKOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuCgo7OyB0cmFpdHMKOzsKCjs7IHRva2VuIGRlZmluaXRpb25zCjs7IAoKOzsgY29uc3RhbnRzCjs7CihkZWZpbmUtY29uc3RhbnQgTUFYX0xPQyB1MTAwKQooZGVmaW5lLWNvbnN0YW50IE1BWF9WQUwgMHhmZmZmZmYpCihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMCkKKGRlZmluZS1jb25zdGFudCBBTExfTE9DUyAobGlzdCB1MCB1MSB1MiB1MyB1NCB1NSB1NiB1NyB1OCB1OSB1MTAgdTExIHUxMiB1MTMgdTE0IHUxNSB1MTYgdTE3IHUxOCB1MTkgdTIwIHUyMSB1MjIgdTIzIHUyNCB1MjUgdTI2IHUyNyB1MjggdTI5IHUzMCB1MzEgdTMyIHUzMyB1MzQgdTM1IHUzNiB1MzcgdTM4IHUzOSB1NDAgdTQxIHU0MiB1NDMgdTQ0IHU0NSB1NDYgdTQ3IHU0OCB1NDkgdTUwIHU1MSB1NTIgdTUzIHU1NCB1NTUgdTU2IHU1NyB1NTggdTU5IHU2MCB1NjEgdTYyIHU2MyB1NjQgdTY1IHU2NiB1NjcgdTY4IHU2OSB1NzAgdTcxIHU3MiB1NzMgdTc0IHU3NSB1NzYgdTc3IHU3OCB1NzkgdTgwIHU4MSB1ODIgdTgzIHU4NCB1ODUgdTg2IHU4NyB1ODggdTg5IHU5MCB1OTEgdTkyIHU5MyB1OTQgdTk1IHU5NiB1OTcgdTk4IHU5OSkpCjs7IGRhdGEgdmFycwo7OwoKOzsgZGF0YSBtYXBzCjs7CihkZWZpbmUtbWFwIHBpeGVscyB1aW50IChidWZmIDMpKQoKOzsgcHVibGljIGZ1bmN0aW9ucwo7OwooZGVmaW5lLXB1YmxpYyAoc2V0LXZhbHVlLWF0IChsb2MgdWludCkgKHZhbHVlIChidWZmIDMpKSkgCiAgICAoYmVnaW4gCiAgICAgICAgKGlmICg+PSBsb2MgTUFYX0xPQykKICAgICAgICAgICAgKGVyciAiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy4iKQogICAgICAgICAgICAoaWYgKD4gdmFsdWUgTUFYX1ZBTCkKICAgICAgICAgICAgICAgIChlcnIgIlZhbHVlIG11c3QgYmUgbGVzcyB0aGFuIDB4ZmZmZmZmLiIpCiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTCkKICAgICAgICAgICAgICAgICAgICAoZXJyICJWYWx1ZSBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAweDAwMDAwMC4iKQogICAgICAgICAgICAgICAgICAgIChvayAobWFwLXNldCBwaXhlbHMgbG9jIHZhbHVlKSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICkKICAgICkKKQo7OyByZWFkIG9ubHkgZnVuY3Rpb25zCjs7CgooZGVmaW5lLXJlYWQtb25seSAoZ2V0LXZhbHVlLWF0IChsb2MgdWludCkpCiAgICAoaWYgKD49IGxvYyBNQVhfTE9DKQogICAgICAgIChlcnIgIk91dCBvZiBib3VuZHMuIikKICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGdldC1hbGwpIAogICAgKG1hcCBnZXQtdmFsdWUtYXQgQUxMX0xPQ1MpCikKCihkZWZpbmUtcmVhZC1vbmx5IChnZW5lc2lzLXRpbWUgKGhlaWdodCB1aW50KSkKICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpCikKOzsgcHJpdmF0ZSBmdW5jdGlvbnMKOzsK diff --git a/src/tests/fixtures/network-manifest.yaml b/src/tests/fixtures/network-manifest.yaml index b68c31c..e4d84b7 100644 --- a/src/tests/fixtures/network-manifest.yaml +++ b/src/tests/fixtures/network-manifest.yaml @@ -1,31 +1,177 @@ -[network] -name = 'devnet' - -[accounts.deployer] -mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw" -balance = "100000000000000" - - -[accounts.wallet_1] -mnemonic = "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild" -balance = "100000000000000" - -[devnet] -miner_mnemonic = "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild" -miner_derivation_path = "m/44'/5757'/0'/0/0" -bitcoin_node_username = "test-username" -bitcoin_node_password = "test-password" -faucet_mnemonic = "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform" -faucet_derivation_path = "m/44'/5757'/0'/0/0" -orchestrator_ingestion_port = 20445 -orchestrator_control_port = 20446 -bitcoin_node_rpc_port = 18443 -stacks_node_rpc_port = 20443 -stacks_api_port = 3999 -epoch_2_0 = 100 -epoch_2_05 = 102 -epoch_2_1 = 106 -epoch_2_2 = 120 -working_dir = "/devnet" -bitcoin_controller_block_time = 0 -bitcoin_controller_automining_disabled = true \ No newline at end of file +--- +network: + name: devnet + stacks_node_rpc_address: ~ + bitcoin_node_rpc_address: ~ + deployment_fee_rate: 10 + sats_per_bytes: 10 +accounts: + - label: deployer + mnemonic: twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH + is_mainnet: false + - label: faucet + mnemonic: shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 + btc_address: mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d + is_mainnet: false + - label: wallet_1 + mnemonic: crazy vibrant runway diagram beach language above aerobic maze coral this gas mirror output vehicle cover usage ecology unfold room feel file rocket expire + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: STB8E0SMACY4A6DCCH4WE48YGX3P877407QW176V + btc_address: mha4u7F3e93P9Xy1WQgVvGtYtynnJtT22x + is_mainnet: false + - label: wallet_2 + mnemonic: hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG + btc_address: muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG + is_mainnet: false + - label: wallet_3 + mnemonic: cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC + btc_address: mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7 + is_mainnet: false + - label: wallet_4 + mnemonic: board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND + btc_address: mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8 + is_mainnet: false + - label: wallet_5 + mnemonic: hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB + btc_address: mweN5WVqadScHdA81aATSdcVr4B6dNokqx + is_mainnet: false + - label: wallet_6 + mnemonic: area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 + btc_address: mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt + is_mainnet: false + - label: wallet_7 + mnemonic: prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ + btc_address: n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7 + is_mainnet: false + - label: wallet_8 + mnemonic: female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune + derivation: "m/44'/5757'/0'/0/0" + balance: 100000000000000 + stx_address: ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP + btc_address: n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw + is_mainnet: false +devnet_settings: + name: devnet + network_id: ~ + orchestrator_ingestion_port: 20445 + orchestrator_control_port: 20446 + bitcoin_node_p2p_port: 18444 + bitcoin_node_rpc_port: 18443 + bitcoin_node_username: devnet + bitcoin_node_password: devnet + stacks_node_p2p_port: 20444 + stacks_node_rpc_port: 20443 + stacks_node_wait_time_for_microblocks: 50 + stacks_node_first_attempt_time_ms: 500 + stacks_node_subsequent_attempt_time_ms: 1000 + stacks_node_events_observers: + - "host.docker.internal:20455" + stacks_node_env_vars: [] + stacks_api_port: 3999 + stacks_api_events_port: 3700 + stacks_api_env_vars: [] + stacks_explorer_port: 8000 + stacks_explorer_env_vars: [] + bitcoin_explorer_port: 8001 + bitcoin_controller_block_time: 60000 + bitcoin_controller_automining_disabled: false + miner_stx_address: ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ + miner_secret_key_hex: 3b68e410cc7f9b8bae76f2f2991b69ecd0627c95da22a904065dfb2a73d0585f01 + miner_btc_address: n3GRiDLKWuKLCw1DZmV75W1mE35qmW2tQm + miner_mnemonic: fragile loan twenty basic net assault jazz absorb diet talk art shock innocent float punch travel gadget embrace caught blossom hockey surround initial reduce + miner_derivation_path: "m/44'/5757'/0'/0/0" + miner_coinbase_recipient: ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ + faucet_stx_address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 + faucet_secret_key_hex: de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801 + faucet_btc_address: mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d + faucet_mnemonic: shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform + faucet_derivation_path: "m/44'/5757'/0'/0/0" + working_dir: /Users/micaiahreid/work/stx-px/tmp + postgres_port: 5432 + postgres_username: postgres + postgres_password: postgres + stacks_api_postgres_database: stacks_api + subnet_api_postgres_database: subnet_api + pox_stacking_orders: + - start_at_cycle: 3 + duration: 12 + wallet: wallet_1 + slots: 2 + btc_address: mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC + - start_at_cycle: 3 + duration: 12 + wallet: wallet_2 + slots: 1 + btc_address: muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG + - start_at_cycle: 3 + duration: 12 + wallet: wallet_3 + slots: 1 + btc_address: mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7 + execute_script: [] + bitcoin_node_image_url: "quay.io/hirosystems/bitcoind:devnet-v3" + stacks_node_image_url: "quay.io/hirosystems/stacks-node:devnet-2.4.0.0.0" + stacks_api_image_url: "hirosystems/stacks-blockchain-api:latest" + stacks_explorer_image_url: "hirosystems/explorer:latest" + postgres_image_url: "postgres:14" + bitcoin_explorer_image_url: "quay.io/hirosystems/bitcoin-explorer:devnet" + disable_bitcoin_explorer: true + disable_stacks_explorer: true + disable_stacks_api: false + bind_containers_volumes: false + enable_subnet_node: false + subnet_node_image_url: "hirosystems/stacks-subnets:0.8.1" + subnet_leader_stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + subnet_leader_secret_key_hex: 753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601 + subnet_leader_btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH + subnet_leader_mnemonic: twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw + subnet_leader_derivation_path: "m/44'/5757'/0'/0/0" + subnet_node_p2p_port: 30444 + subnet_node_rpc_port: 30443 + subnet_events_ingestion_port: 30445 + subnet_node_events_observers: [] + subnet_contract_id: ST173JK7NZBA4BS05ZRATQH1K89YJMTGEH1Z5J52E.subnet-v3-0-1 + remapped_subnet_contract_id: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.subnet-v3-0-1 + subnet_node_env_vars: [] + subnet_api_image_url: "hirosystems/stacks-blockchain-api:latest" + subnet_api_port: 13999 + subnet_api_events_port: 13700 + subnet_api_env_vars: [] + disable_subnet_api: true + docker_host: "unix:///var/run/docker.sock" + components_host: 127.0.0.1 + epoch_2_0: 100 + epoch_2_05: 100 + epoch_2_1: 101 + epoch_2_2: 103 + epoch_2_3: 104 + epoch_2_4: 105 + pox_2_activation: 102 + use_docker_gateway_routing: false + docker_platform: linux/amd64 diff --git a/src/tests/fixtures/project-manifest.yaml b/src/tests/fixtures/project-manifest.yaml index 5acdeab..2f08b5c 100644 --- a/src/tests/fixtures/project-manifest.yaml +++ b/src/tests/fixtures/project-manifest.yaml @@ -1,10 +1,23 @@ -[project] -name = "px" -description = "my description" -authors = ['test1','test2'] -requirements = ['test1','test2'] - -[contracts.px] -path = "contracts/px.clar" -clarity_version = 2 -epoch = "2.1" \ No newline at end of file +--- +project: + name: px + description: my description + authors: + - test1 + - test2 + telemetry: false + cache_dir: /etc/stacks-network/project/contracts + requirements: [] +contracts: + px: + path: contracts/px.clar + clarity_version: 2 + epoch: 2.1 +repl: + analysis: + passes: [] + check_checker: + strict: false + trusted_sender: false + trusted_caller: false + callee_filter: false diff --git a/src/tests/fixtures/stacks-devnet-config.json b/src/tests/fixtures/stacks-devnet-config.json index 08de413..fd57f0c 100644 --- a/src/tests/fixtures/stacks-devnet-config.json +++ b/src/tests/fixtures/stacks-devnet-config.json @@ -1,80 +1,366 @@ { - "namespace": "test-namespace1", - "stacks_node_wait_time_for_microblocks": 50, - "stacks_node_first_attempt_time_ms": 500, - "stacks_node_subsequent_attempt_time_ms": 1000, - "bitcoin_node_username": "test-username", - "bitcoin_node_password": "test-password", - "miner_mnemonic": "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild", - "miner_derivation_path": "m/44'/5757'/0'/0/0", - "miner_coinbase_recipient": "ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ", - "faucet_mnemonic": "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform", - "faucet_derivation_path": "m/44'/5757'/0'/0/0", - "bitcoin_controller_block_time": 0, - "bitcoin_controller_automining_disabled": true, - "disable_bitcoin_explorer": true, - "disable_stacks_explorer": true, + "namespace": "test-namespace", "disable_stacks_api": false, - "epoch_2_0": 100, - "epoch_2_05": 102, - "epoch_2_1": 106, - "epoch_2_2": 120, - "pox_2_activation": 112, - "pox_2_unlock_height": 112, - "project_manifest": { - "name": "px", - "description": "my description", - "authors": ["test1", "test2"], - "requirements": ["test1", "test2"] - }, - "accounts": [ - { - "name": "deployer", - "mnemonic": "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw", - "balance": 100000000000000, - "stx_address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" - }, - { - "name": "wallet_1", - "mnemonic": "sell invite acquire kitten bamboo drastic jelly vivid peace spawn twice guilt pave pen trash pretty park cube fragile unaware remain midnight betray rebuild", - "balance": 100000000000000, - "stx_address": "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5" - } - ], "deployment_plan": { "id": 0, "name": "Devnet deployment", "network": "devnet", - "stacks-node": "http://localhost:20443", - "bitcoin-node": "http://px-devnet:px-devnet@localhost:18443", - "plan": { - "batches": [ - { - "id": 0, - "transactions": [ - { - "contract-publish": { - "contract-name": "px", - "expected-sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - "cost": 18060, - "path": "contracts/px.clar", - "anchor-block-only": true, - "clarity-version": 2 - } + "stacks_node": "http://localhost:20443", + "bitcoin_node": "http://px-devnet:px-devnet@localhost:18443", + "genesis": null, + "batches": [ + { + "id": 0, + "transactions": [ + { + "transaction_type": "RequirementPublish", + "contract_id": "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "KGRlZmluZS10cmFpdCBuZnQtdHJhaXQKICAoCiAgICA7OyBMYXN0IHRva2VuIElELCBsaW1pdGVkIHRvIHVpbnQgcmFuZ2UKICAgIChnZXQtbGFzdC10b2tlbi1pZCAoKSAocmVzcG9uc2UgdWludCB1aW50KSkKCiAgICA7OyBVUkkgZm9yIG1ldGFkYXRhIGFzc29jaWF0ZWQgd2l0aCB0aGUgdG9rZW4KICAgIChnZXQtdG9rZW4tdXJpICh1aW50KSAocmVzcG9uc2UgKG9wdGlvbmFsIChzdHJpbmctYXNjaWkgMjU2KSkgdWludCkpCgogICAgIDs7IE93bmVyIG9mIGEgZ2l2ZW4gdG9rZW4gaWRlbnRpZmllcgogICAgKGdldC1vd25lciAodWludCkgKHJlc3BvbnNlIChvcHRpb25hbCBwcmluY2lwYWwpIHVpbnQpKQoKICAgIDs7IFRyYW5zZmVyIGZyb20gdGhlIHNlbmRlciB0byBhIG5ldyBwcmluY2lwYWwKICAgICh0cmFuc2ZlciAodWludCBwcmluY2lwYWwgcHJpbmNpcGFsKSAocmVzcG9uc2UgYm9vbCB1aW50KSkKICApCik=", + "clarity_version": 1, + "cost": 4670, + "location": { + "path": "./.cache/requirements/ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.nft-trait.clar" } - ], - "epoch": "2.1" + } + ], + "epoch": "2.0" + }, + { + "id": 1, + "transactions": [ + { + "transaction_type": "RequirementPublish", + "contract_id": "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "KGRlZmluZS10cmFpdCBzaXAtMDEwLXRyYWl0CiAgKAogICAgOzsgVHJhbnNmZXIgZnJvbSB0aGUgY2FsbGVyIHRvIGEgbmV3IHByaW5jaXBhbAogICAgKHRyYW5zZmVyICh1aW50IHByaW5jaXBhbCBwcmluY2lwYWwgKG9wdGlvbmFsIChidWZmIDM0KSkpIChyZXNwb25zZSBib29sIHVpbnQpKQoKICAgIDs7IHRoZSBodW1hbiByZWFkYWJsZSBuYW1lIG9mIHRoZSB0b2tlbgogICAgKGdldC1uYW1lICgpIChyZXNwb25zZSAoc3RyaW5nLWFzY2lpIDMyKSB1aW50KSkKCiAgICA7OyB0aGUgdGlja2VyIHN5bWJvbCwgb3IgZW1wdHkgaWYgbm9uZQogICAgKGdldC1zeW1ib2wgKCkgKHJlc3BvbnNlIChzdHJpbmctYXNjaWkgMzIpIHVpbnQpKQoKICAgIDs7IHRoZSBudW1iZXIgb2YgZGVjaW1hbHMgdXNlZCwgZS5nLiA2IHdvdWxkIG1lYW4gMV8wMDBfMDAwIHJlcHJlc2VudHMgMSB0b2tlbgogICAgKGdldC1kZWNpbWFscyAoKSAocmVzcG9uc2UgdWludCB1aW50KSkKCiAgICA7OyB0aGUgYmFsYW5jZSBvZiB0aGUgcGFzc2VkIHByaW5jaXBhbAogICAgKGdldC1iYWxhbmNlIChwcmluY2lwYWwpIChyZXNwb25zZSB1aW50IHVpbnQpKQoKICAgIDs7IHRoZSBjdXJyZW50IHRvdGFsIHN1cHBseSAod2hpY2ggZG9lcyBub3QgbmVlZCB0byBiZSBhIGNvbnN0YW50KQogICAgKGdldC10b3RhbC1zdXBwbHkgKCkgKHJlc3BvbnNlIHVpbnQgdWludCkpCgogICAgOzsgYW4gb3B0aW9uYWwgVVJJIHRoYXQgcmVwcmVzZW50cyBtZXRhZGF0YSBvZiB0aGlzIHRva2VuCiAgICAoZ2V0LXRva2VuLXVyaSAoKSAocmVzcG9uc2UgKG9wdGlvbmFsIChzdHJpbmctdXRmOCAyNTYpKSB1aW50KSkKICApCik=", + "clarity_version": 1, + "cost": 8390, + "location": { + "path": "./.cache/requirements/ST1NXBK3K5YYMD6FD41MVNP3JS1GABZ8TRVX023PT.sip-010-trait-ft-standard.clar" + } + } + ], + "epoch": "2.05" + }, + { + "id": 2, + "transactions": [ + { + "transaction_type": "RequirementPublish", + "contract_id": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-traits-v1", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "OzsgSW4gb3JkZXIgdG8gc3VwcG9ydCB3aXRoZHJhd2luZyBhbiBhc3NldCB0aGF0IHdhcyBtaW50ZWQgb24gYSBzdWJuZXQsIHRoZQo7OyBMMSBjb250cmFjdCBtdXN0IGltcGxlbWVudCB0aGlzIHRyYWl0LgooZGVmaW5lLXRyYWl0IG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQKICAoCiAgICA7OyBQcm9jZXNzIGEgd2l0aGRyYXdhbCBmcm9tIHRoZSBzdWJuZXQgZm9yIGFuIGFzc2V0IHdoaWNoIGRvZXMgbm90IHlldAogICAgOzsgZXhpc3Qgb24gdGhpcyBuZXR3b3JrLCBhbmQgdGh1cyByZXF1aXJlcyBhIG1pbnQuCiAgICAobWludC1mcm9tLXN1Ym5ldAogICAgICAoCiAgICAgICAgdWludCAgICAgICA7OyBhc3NldC1pZCAoTkZUKSBvciBhbW91bnQgKEZUKQogICAgICAgIHByaW5jaXBhbCAgOzsgc2VuZGVyCiAgICAgICAgcHJpbmNpcGFsICA7OyByZWNpcGllbnQKICAgICAgKQogICAgICAocmVzcG9uc2UgYm9vbCB1aW50KQogICAgKQogICkKKQ==", + "clarity_version": 2, + "cost": 4810, + "location": { + "path": "./.cache/requirements/ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-traits-v1.clar" + } + }, + { + "transaction_type": "RequirementPublish", + "contract_id": "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1-2", + "remap_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "remap_principals": { + "ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM" + }, + "source": "OzsgVGhlIC5zdWJuZXQgY29udHJhY3QKCihkZWZpbmUtY29uc3RhbnQgQ09OVFJBQ1RfQUREUkVTUyAoYXMtY29udHJhY3QgdHgtc2VuZGVyKSkKCjs7IEVycm9yIGNvZGVzCihkZWZpbmUtY29uc3RhbnQgRVJSX0JMT0NLX0FMUkVBRFlfQ09NTUlUVEVEIDEpCihkZWZpbmUtY29uc3RhbnQgRVJSX0lOVkFMSURfTUlORVIgMikKKGRlZmluZS1jb25zdGFudCBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQgMykKKGRlZmluZS1jb25zdGFudCBFUlJfVFJBTlNGRVJfRkFJTEVEIDQpCihkZWZpbmUtY29uc3RhbnQgRVJSX0RJU0FMTE9XRURfQVNTRVQgNSkKKGRlZmluZS1jb25zdGFudCBFUlJfQVNTRVRfQUxSRUFEWV9BTExPV0VEIDYpCihkZWZpbmUtY29uc3RhbnQgRVJSX01FUktMRV9ST09UX0RPRVNfTk9UX01BVENIIDcpCihkZWZpbmUtY29uc3RhbnQgRVJSX0lOVkFMSURfTUVSS0xFX1JPT1QgOCkKKGRlZmluZS1jb25zdGFudCBFUlJfV0lUSERSQVdBTF9BTFJFQURZX1BST0NFU1NFRCA5KQooZGVmaW5lLWNvbnN0YW50IEVSUl9WQUxJREFUSU9OX0ZBSUxFRCAxMCkKOzs7IFRoZSB2YWx1ZSBzdXBwbGllZCBmb3IgYHRhcmdldC1jaGFpbi10aXBgIGRvZXMgbm90IG1hdGNoIHRoZSBjdXJyZW50IGNoYWluIHRpcC4KKGRlZmluZS1jb25zdGFudCBFUlJfSU5WQUxJRF9DSEFJTl9USVAgMTEpCjs7OyBUaGUgY29udHJhY3Qgd2FzIGNhbGxlZCBiZWZvcmUgcmVhY2hpbmcgdGhpcy1jaGFpbiBoZWlnaHQgcmVhY2hlcyAxLgooZGVmaW5lLWNvbnN0YW50IEVSUl9DQUxMRURfVE9PX0VBUkxZIDEyKQooZGVmaW5lLWNvbnN0YW50IEVSUl9NSU5UX0ZBSUxFRCAxMykKKGRlZmluZS1jb25zdGFudCBFUlJfQVRURU1QVF9UT19UUkFOU0ZFUl9aRVJPX0FNT1VOVCAxNCkKKGRlZmluZS1jb25zdGFudCBFUlJfSU5fQ09NUFVUQVRJT04gMTUpCjs7IFRoZSBjb250cmFjdCBkb2VzIG5vdCBvd24gdGhpcyBORlQgdG8gd2l0aGRyYXcgaXQuCihkZWZpbmUtY29uc3RhbnQgRVJSX05GVF9OT1RfT1dORURfQllfQ09OVFJBQ1QgMTYpCihkZWZpbmUtY29uc3RhbnQgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQgMzApCgo7OyBNYXAgZnJvbSBTdGFja3MgYmxvY2sgaGVpZ2h0IHRvIGJsb2NrIGNvbW1pdAooZGVmaW5lLW1hcCBibG9jay1jb21taXRzIHVpbnQgKGJ1ZmYgMzIpKQo7OyBNYXAgcmVjb3JkaW5nIHdpdGhkcmF3YWwgcm9vdHMKKGRlZmluZS1tYXAgd2l0aGRyYXdhbC1yb290cy1tYXAgKGJ1ZmYgMzIpIGJvb2wpCjs7IE1hcCByZWNvcmRpbmcgcHJvY2Vzc2VkIHdpdGhkcmF3YWwgbGVhdmVzCihkZWZpbmUtbWFwIHByb2Nlc3NlZC13aXRoZHJhd2FsLWxlYXZlcy1tYXAgeyB3aXRoZHJhd2FsLWxlYWYtaGFzaDogKGJ1ZmYgMzIpLCB3aXRoZHJhd2FsLXJvb3QtaGFzaDogKGJ1ZmYgMzIpIH0gYm9vbCkKCjs7IHByaW5jaXBhbCB0aGF0IGNhbiBjb21taXQgYmxvY2tzCihkZWZpbmUtZGF0YS12YXIgbWluZXIgcHJpbmNpcGFsIHR4LXNlbmRlcikKOzsgcHJpbmNpcGFsIHRoYXQgY2FuIHJlZ2lzdGVyIGNvbnRyYWN0cwooZGVmaW5lLWRhdGEtdmFyIGFkbWluIHByaW5jaXBhbCB0eC1zZW5kZXIpCgo7OyBNYXAgb2YgYWxsb3dlZCBjb250cmFjdHMgZm9yIGFzc2V0IHRyYW5zZmVycyAtIG1hcHMgTDEgY29udHJhY3QgcHJpbmNpcGFsIHRvIEwyIGNvbnRyYWN0IHByaW5jaXBhbAooZGVmaW5lLW1hcCBhbGxvd2VkLWNvbnRyYWN0cyBwcmluY2lwYWwgcHJpbmNpcGFsKQoKOzsgVXNlIHRyYWl0IGRlY2xhcmF0aW9ucwoodXNlLXRyYWl0IG5mdC10cmFpdCAnU1QxTlhCSzNLNVlZTUQ2RkQ0MU1WTlAzSlMxR0FCWjhUUlZYMDIzUFQubmZ0LXRyYWl0Lm5mdC10cmFpdCkKKHVzZS10cmFpdCBmdC10cmFpdCAnU1QxTlhCSzNLNVlZTUQ2RkQ0MU1WTlAzSlMxR0FCWjhUUlZYMDIzUFQuc2lwLTAxMC10cmFpdC1mdC1zdGFuZGFyZC5zaXAtMDEwLXRyYWl0KQoodXNlLXRyYWl0IG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQgLnN1Ym5ldC10cmFpdHMtdjEubWludC1mcm9tLXN1Ym5ldC10cmFpdCkKCjs7IFVwZGF0ZSB0aGUgbWluZXIgZm9yIHRoaXMgY29udHJhY3QuCihkZWZpbmUtcHVibGljICh1cGRhdGUtbWluZXIgKG5ldy1taW5lciBwcmluY2lwYWwpKQogICAgKGJlZ2luCiAgICAgICAgKGFzc2VydHMhIChpcy1lcSB0eC1zZW5kZXIgKHZhci1nZXQgbWluZXIpKSAoZXJyIEVSUl9JTlZBTElEX01JTkVSKSkKICAgICAgICAob2sgKHZhci1zZXQgbWluZXIgbmV3LW1pbmVyKSkKICAgICkKKQoKOzsgUmVnaXN0ZXIgYSBuZXcgRlQgY29udHJhY3QgdG8gYmUgc3VwcG9ydGVkIGJ5IHRoaXMgc3VibmV0LgooZGVmaW5lLXB1YmxpYyAocmVnaXN0ZXItbmV3LWZ0LWNvbnRyYWN0IChmdC1jb250cmFjdCA8ZnQtdHJhaXQ+KSAobDItY29udHJhY3QgcHJpbmNpcGFsKSkKICAgIChiZWdpbgogICAgICAgIDs7IFZlcmlmeSB0aGF0IHR4LXNlbmRlciBpcyBhbiBhdXRob3JpemVkIGFkbWluCiAgICAgICAgKGFzc2VydHMhIChpcy1hZG1pbiB0eC1zZW5kZXIpIChlcnIgRVJSX0lOVkFMSURfTUlORVIpKQoKICAgICAgICA7OyBTZXQgdXAgdGhlIGFzc2V0cyB0aGF0IHRoZSBjb250cmFjdCBpcyBhbGxvd2VkIHRvIHRyYW5zZmVyCiAgICAgICAgKGFzc2VydHMhIChtYXAtaW5zZXJ0IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkgbDItY29udHJhY3QpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX0FTU0VUX0FMUkVBRFlfQUxMT1dFRCkpCgogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAicmVnaXN0ZXItY29udHJhY3QiLAogICAgICAgICAgICBhc3NldC10eXBlOiAiZnQiLAogICAgICAgICAgICBsMS1jb250cmFjdDogKGNvbnRyYWN0LW9mIGZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgbDItY29udHJhY3Q6IGwyLWNvbnRyYWN0CiAgICAgICAgfSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IFJlZ2lzdGVyIGEgbmV3IE5GVCBjb250cmFjdCB0byBiZSBzdXBwb3J0ZWQgYnkgdGhpcyBzdWJuZXQuCihkZWZpbmUtcHVibGljIChyZWdpc3Rlci1uZXctbmZ0LWNvbnRyYWN0IChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pIChsMi1jb250cmFjdCBwcmluY2lwYWwpKQogICAgKGJlZ2luCiAgICAgICAgOzsgVmVyaWZ5IHRoYXQgdHgtc2VuZGVyIGlzIGFuIGF1dGhvcml6ZWQgYWRtaW4KICAgICAgICAoYXNzZXJ0cyEgKGlzLWFkbWluIHR4LXNlbmRlcikgKGVyciBFUlJfSU5WQUxJRF9NSU5FUikpCgogICAgICAgIDs7IFNldCB1cCB0aGUgYXNzZXRzIHRoYXQgdGhlIGNvbnRyYWN0IGlzIGFsbG93ZWQgdG8gdHJhbnNmZXIKICAgICAgICAoYXNzZXJ0cyEgKG1hcC1pbnNlcnQgYWxsb3dlZC1jb250cmFjdHMgKGNvbnRyYWN0LW9mIG5mdC1jb250cmFjdCkgbDItY29udHJhY3QpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX0FTU0VUX0FMUkVBRFlfQUxMT1dFRCkpCgogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAicmVnaXN0ZXItY29udHJhY3QiLAogICAgICAgICAgICBhc3NldC10eXBlOiAibmZ0IiwKICAgICAgICAgICAgbDEtY29udHJhY3Q6IChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpLAogICAgICAgICAgICBsMi1jb250cmFjdDogbDItY29udHJhY3QKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKOzsgSGVscGVyIGZ1bmN0aW9uOiByZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGdpdmVuIHByaW5jaXBhbCBpcyBhIG1pbmVyCjs7IFJldHVybnMgYm9vbAooZGVmaW5lLXByaXZhdGUgKGlzLW1pbmVyIChtaW5lci10by1jaGVjayBwcmluY2lwYWwpKQogICAgKGlzLWVxIG1pbmVyLXRvLWNoZWNrICh2YXItZ2V0IG1pbmVyKSkKKQoKOzsgSGVscGVyIGZ1bmN0aW9uOiByZXR1cm5zIGEgYm9vbGVhbiBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIGdpdmVuIHByaW5jaXBhbCBpcyBhbiBhZG1pbgo7OyBSZXR1cm5zIGJvb2wKKGRlZmluZS1wcml2YXRlIChpcy1hZG1pbiAoYWRkci10by1jaGVjayBwcmluY2lwYWwpKQogICAgKGlzLWVxIGFkZHItdG8tY2hlY2sgKHZhci1nZXQgYWRtaW4pKQopCgo7OyBIZWxwZXIgZnVuY3Rpb246IGRldGVybWluZXMgd2hldGhlciB0aGUgY29tbWl0LWJsb2NrIG9wZXJhdGlvbiBzYXRpc2ZpZXMgcHJlLWNvbmRpdGlvbnMKOzsgbGlzdGVkIGluIGBjb21taXQtYmxvY2tgLgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wcml2YXRlIChjYW4tY29tbWl0LWJsb2NrPyAoY29tbWl0LWJsb2NrLWhlaWdodCB1aW50KSAgKHRhcmdldC1jaGFpbi10aXAgKGJ1ZmYgMzIpKSkKICAgIChiZWdpbgogICAgICAgIDs7IGNoZWNrIG5vIGJsb2NrIGhhcyBiZWVuIGNvbW1pdHRlZCBhdCB0aGlzIGhlaWdodAogICAgICAgIChhc3NlcnRzISAoaXMtbm9uZSAobWFwLWdldD8gYmxvY2stY29tbWl0cyBjb21taXQtYmxvY2staGVpZ2h0KSkgKGVyciBFUlJfQkxPQ0tfQUxSRUFEWV9DT01NSVRURUQpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IGB0YXJnZXQtY2hhaW4tdGlwYCBtYXRjaGVzIHRoZSBidXJuIGNoYWluIHRpcAogICAgICAgIChhc3NlcnRzISAoaXMtZXEKICAgICAgICAgICAgdGFyZ2V0LWNoYWluLXRpcAogICAgICAgICAgICAodW53cmFwISAoZ2V0LWJsb2NrLWluZm8/IGlkLWhlYWRlci1oYXNoICgtIGJsb2NrLWhlaWdodCB1MSkpIChlcnIgRVJSX0NBTExFRF9UT09fRUFSTFkpKSApCiAgICAgICAgICAgIChlcnIgRVJSX0lOVkFMSURfQ0hBSU5fVElQKSkKCiAgICAgICAgOzsgY2hlY2sgdGhhdCB0aGUgdHggc2VuZGVyIGlzIG9uZSBvZiB0aGUgbWluZXJzCiAgICAgICAgKGFzc2VydHMhIChpcy1taW5lciB0eC1zZW5kZXIpIChlcnIgRVJSX0lOVkFMSURfTUlORVIpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSBtaW5lciBjYWxsZWQgdGhpcyBjb250cmFjdCBkaXJlY3RseQogICAgICAgIChhc3NlcnRzISAoaXMtbWluZXIgY29udHJhY3QtY2FsbGVyKSAoZXJyIEVSUl9JTlZBTElEX01JTkVSKSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEhlbHBlciBmdW5jdGlvbjogbW9kaWZpZXMgdGhlIGJsb2NrLWNvbW1pdHMgbWFwIHdpdGggYSBuZXcgY29tbWl0IGFuZCBwcmludHMgcmVsYXRlZCBpbmZvCjs7IFJldHVybnMgcmVzcG9uc2U8KGJ1ZmYgMzIpLCA/PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLWNvbW1pdC1ibG9jawogICAgICAgIChibG9jayAoYnVmZiAzMikpCiAgICAgICAgKGNvbW1pdC1ibG9jay1oZWlnaHQgdWludCkKICAgICAgICAodGFyZ2V0LWJ1cm4tYmxvY2staGVpZ2h0IHVpbnQpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICApCiAgICAoYmVnaW4KICAgICAgICAobWFwLXNldCBibG9jay1jb21taXRzIGNvbW1pdC1ibG9jay1oZWlnaHQgYmxvY2spCiAgICAgICAgKG1hcC1zZXQgd2l0aGRyYXdhbC1yb290cy1tYXAgd2l0aGRyYXdhbC1yb290IHRydWUpCiAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgZXZlbnQ6ICJibG9jay1jb21taXQiLAogICAgICAgICAgICBibG9jay1jb21taXQ6IGJsb2NrLAogICAgICAgICAgICBibG9jay1oZWlnaHQ6IGNvbW1pdC1ibG9jay1oZWlnaHQsCiAgICAgICAgICAgIHdpdGhkcmF3YWwtcm9vdDogd2l0aGRyYXdhbC1yb290LAogICAgICAgICAgICB0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQ6IHRhcmdldC1idXJuLWJsb2NrLWhlaWdodAogICAgICAgIH0pCiAgICAgICAgKG9rIGJsb2NrKQogICAgKQopCgo7OyBUaGUgc3VibmV0IG1pbmVyIGNhbGxzIHRoaXMgZnVuY3Rpb24gdG8gY29tbWl0IGEgYmxvY2sgYXQgYSBwYXJ0aWN1bGFyIGhlaWdodC4KOzsgYGJsb2NrYCBpcyB0aGUgaGFzaCBvZiB0aGUgYmxvY2sgYmVpbmcgc3VibWl0dGVkLgo7OyBgdGFyZ2V0LWNoYWluLXRpcGAgaXMgdGhlIGBpZC1oZWFkZXItaGFzaGAgb2YgdGhlIGJ1cm4gYmxvY2sgKGkuZS4sIGJsb2NrIG9uCjs7ICAgIHRoaXMgY2hhaW4pIHRoYXQgdGhlIG1pbmVyIGludGVuZHMgdG8gYnVpbGQgb2ZmLgo7Owo7OyBGYWlscyBpZjoKOzsgIDEpIHdlIGhhdmUgYWxyZWFkeSBjb21taXR0ZWQgYXQgdGhpcyBibG9jayBoZWlnaHQKOzsgIDIpIGB0YXJnZXQtY2hhaW4tdGlwYCBpcyBub3QgdGhlIGJ1cm4gY2hhaW4gdGlwIChpLmUuLCBvbiB0aGlzIGNoYWluKQo7OyAgMykgdGhlIHNlbmRlciBpcyBub3QgYSBtaW5lcgooZGVmaW5lLXB1YmxpYyAoY29tbWl0LWJsb2NrCiAgICAgICAgKGJsb2NrIChidWZmIDMyKSkKICAgICAgICAoY29tbWl0LWJsb2NrLWhlaWdodCB1aW50KQogICAgICAgICh0YXJnZXQtY2hhaW4tdGlwIChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICkKICAgIChsZXQgKCh0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQgYmxvY2staGVpZ2h0KSkKICAgICAgICAodHJ5ISAoY2FuLWNvbW1pdC1ibG9jaz8gdGFyZ2V0LWJ1cm4tYmxvY2staGVpZ2h0IHRhcmdldC1jaGFpbi10aXApKQogICAgICAgIChpbm5lci1jb21taXQtYmxvY2sgYmxvY2sgY29tbWl0LWJsb2NrLWhlaWdodCB0YXJnZXQtYnVybi1ibG9jay1oZWlnaHQgd2l0aGRyYXdhbC1yb290KQogICAgKQopCgo7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Cjs7IEZPUiBORlQgQVNTRVQgVFJBTlNGRVJTCgo7OyBIZWxwZXIgZnVuY3Rpb24gdGhhdCB0cmFuc2ZlcnMgdGhlIHNwZWNpZmllZCBORlQgZnJvbSB0aGUgZ2l2ZW4gc2VuZGVyIHRvIHRoZSBnaXZlbiByZWNpcGllbnQuCjs7IFJldHVybnMgcmVzcG9uc2U8Ym9vbCwgaW50PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChjb250cmFjdC1jYWxsPyBuZnQtY29udHJhY3QgdHJhbnNmZXIgaWQgc2VuZGVyIHJlY2lwaWVudCkpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICApCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdHJhbnNmZXIgc3VjY2VlZGVkCiAgICAgICAgKGFzc2VydHMhIHRyYW5zZmVyLXJlc3VsdCAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtbWludC1jb250cmFjdCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChhcy1jb250cmFjdCAoY29udHJhY3QtY2FsbD8gbmZ0LW1pbnQtY29udHJhY3QgbWludC1mcm9tLXN1Ym5ldCBpZCBzZW5kZXIgcmVjaXBpZW50KSkpCiAgICAgICAgICAgIChtaW50LXJlc3VsdCAodW53cmFwISBjYWxsLXJlc3VsdCAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICkKICAgICAgICA7OyBDaGVjayB0aGF0IHRoZSB0cmFuc2ZlciBzdWNjZWVkZWQKICAgICAgICAoYXNzZXJ0cyEgbWludC1yZXN1bHQgKGVyciBFUlJfTUlOVF9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci10cmFuc2Zlci1vci1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKG5mdC1taW50LWNvbnRyYWN0IDxtaW50LWZyb20tc3VibmV0LXRyYWl0PikKICAgICAgICAoaWQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY2FsbC1yZXN1bHQgKGNvbnRyYWN0LWNhbGw/IG5mdC1jb250cmFjdCBnZXQtb3duZXIgaWQpKQogICAgICAgICAgICAobmZ0LW93bmVyICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgICAgIChjb250cmFjdC1vd25zLW5mdCAoaXMtZXEgbmZ0LW93bmVyIChzb21lIENPTlRSQUNUX0FERFJFU1MpKSkKICAgICAgICAgICAgKG5vLW93bmVyIChpcy1lcSBuZnQtb3duZXIgbm9uZSkpCiAgICAgICAgKQoKICAgICAgICAoaWYgY29udHJhY3Qtb3ducy1uZnQKICAgICAgICAgICAgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICAgICAgICAgIChpZiBuby1vd25lcgogICAgICAgICAgICAgICAgOzsgVHJ5IG1pbnRpbmcgdGhlIGFzc2V0IGlmIHRoZXJlIGlzIG5vIGV4aXN0aW5nIG93bmVyIG9mIHRoaXMgTkZUCiAgICAgICAgICAgICAgICAoaW5uZXItbWludC1uZnQtYXNzZXQgbmZ0LW1pbnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICAgICAgICAgICAgICA7OyBJbiB0aGlzIGNhc2UsIGEgcHJpbmNpcGFsIG90aGVyIHRoYW4gdGhpcyBjb250cmFjdCBvd25zIHRoaXMgTkZULCBzbyBtaW50aW5nIGlzIG5vdCBwb3NzaWJsZQogICAgICAgICAgICAgICAgKGVyciBFUlJfTUlOVF9GQUlMRUQpCiAgICAgICAgICAgICkKICAgICAgICApCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIGRlcG9zaXQgYW4gTkZUIGludG8gdGhlIGNvbnRyYWN0Lgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChkZXBvc2l0LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgICAgIChzdWJuZXQtY29udHJhY3QtaWQgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpKSAoZXJyIEVSUl9ESVNBTExPV0VEX0FTU0VUKSkpCiAgICAgICAgKQoKICAgICAgICA7OyBUcnkgdG8gdHJhbnNmZXIgdGhlIE5GVCB0byB0aGlzIGNvbnRyYWN0CiAgICAgICAgKGFzc2VydHMhICh0cnkhIChpbm5lci10cmFuc2Zlci1uZnQtYXNzZXQgbmZ0LWNvbnRyYWN0IGlkIHNlbmRlciBDT05UUkFDVF9BRERSRVNTKSkgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkKCiAgICAgICAgOzsgRW1pdCBhIHByaW50IGV2ZW50IC0gdGhlIG5vZGUgY29uc3VtZXMgdGhpcwogICAgICAgIChwcmludCB7CiAgICAgICAgICAgIGV2ZW50OiAiZGVwb3NpdC1uZnQiLAogICAgICAgICAgICBsMS1jb250cmFjdC1pZDogKGFzLWNvbnRyYWN0IG5mdC1jb250cmFjdCksCiAgICAgICAgICAgIG5mdC1pZDogaWQsCiAgICAgICAgICAgIHNlbmRlcjogc2VuZGVyLAogICAgICAgICAgICBzdWJuZXQtY29udHJhY3QtaWQ6IHN1Ym5ldC1jb250cmFjdC1pZCwKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7IEhlbHBlciBmdW5jdGlvbiBmb3IgYHdpdGhkcmF3LW5mdC1hc3NldGAKOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChpbm5lci13aXRoZHJhdy1uZnQtYXNzZXQKICAgICAgICAobmZ0LWNvbnRyYWN0IDxuZnQtdHJhaXQ+KQogICAgICAgIChsMi1jb250cmFjdCBwcmluY2lwYWwpCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKG5mdC1taW50LWNvbnRyYWN0IChvcHRpb25hbCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pKQogICAgICAgICh3aXRoZHJhd2FsLXJvb3QgKGJ1ZmYgMzIpKQogICAgICAgICh3aXRoZHJhd2FsLWxlYWYtaGFzaCAoYnVmZiAzMikpCiAgICAgICAgKHNpYmxpbmctaGFzaGVzIChsaXN0IDUwIHsKICAgICAgICAgICAgaGFzaDogKGJ1ZmYgMzIpLAogICAgICAgICAgICBpcy1sZWZ0LXNpZGU6IGJvb2wsCiAgICAgICAgfSkpCiAgICApCiAgICAobGV0ICgoaGFzaGVzLWFyZS12YWxpZCAoY2hlY2std2l0aGRyYXdhbC1oYXNoZXMgd2l0aGRyYXdhbC1yb290IHdpdGhkcmF3YWwtbGVhZi1oYXNoIHNpYmxpbmctaGFzaGVzKSkpCgogICAgICAgIChhc3NlcnRzISAodHJ5ISBoYXNoZXMtYXJlLXZhbGlkKSAoZXJyIEVSUl9WQUxJREFUSU9OX0ZBSUxFRCkpCgogICAgICAgIDs7IGNoZWNrIHRoYXQgdGhlIHdpdGhkcmF3YWwgcmVxdWVzdCBkYXRhIG1hdGNoZXMgdGhlIHN1cHBsaWVkIGxlYWYgaGFzaAogICAgICAgIChhc3NlcnRzISAoaXMtZXEgd2l0aGRyYXdhbC1sZWFmLWhhc2gKICAgICAgICAgICAgICAgICAgICAgICAgIChsZWFmLWhhc2gtd2l0aGRyYXctbmZ0IGwyLWNvbnRyYWN0IGlkIHJlY2lwaWVudCB3aXRoZHJhd2FsLWlkIGhlaWdodCkpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEKICAgICAgICAgICAgICAgIChtYXRjaCBuZnQtbWludC1jb250cmFjdAogICAgICAgICAgICAgICAgICAgIG1pbnQtY29udHJhY3QgKGFzLWNvbnRyYWN0IChpbm5lci10cmFuc2Zlci1vci1taW50LW5mdC1hc3NldCBuZnQtY29udHJhY3QgbWludC1jb250cmFjdCBpZCByZWNpcGllbnQpKQogICAgICAgICAgICAgICAgICAgIChhcy1jb250cmFjdCAoaW5uZXItdHJhbnNmZXItd2l0aG91dC1taW50LW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgcmVjaXBpZW50KSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKGZpbmlzaC13aXRoZHJhdyB7IHdpdGhkcmF3YWwtbGVhZi1oYXNoOiB3aXRoZHJhd2FsLWxlYWYtaGFzaCwgd2l0aGRyYXdhbC1yb290LWhhc2g6IHdpdGhkcmF3YWwtcm9vdCB9KQogICAgICAgICAgICAoZXJyIEVSUl9XSVRIRFJBV0FMX0FMUkVBRFlfUFJPQ0VTU0VEKQogICAgICAgICkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIHdpdGhkcmF3IHRoZSBzcGVjaWZpZWQgTkZUIGZyb20gdGhpcyBjb250cmFjdC4KOzsgSW4gb3JkZXIgZm9yIHRoaXMgd2l0aGRyYXdhbCB0byBnbyB0aHJvdWdoLCB0aGUgZ2l2ZW4gd2l0aGRyYXdhbCBtdXN0IGhhdmUgYmVlbiBpbmNsdWRlZAo7OyBpbiBhIHdpdGhkcmF3YWwgTWVya2xlIHRyZWUgYSBzdWJuZXQgbWluZXIgc3VibWl0dGVkLiBUaGUgdXNlciBtdXN0IHByb3ZpZGUgdGhlIGxlYWYKOzsgaGFzaCBvZiB0aGVpciB3aXRoZHJhd2FsIGFuZCB0aGUgcm9vdCBoYXNoIG9mIHRoZSBzcGVjaWZpYyBNZXJrbGUgdHJlZSB0aGVpciB3aXRoZHJhd2FsCjs7IGlzIGluY2x1ZGVkIGluLiBUaGV5IG11c3QgYWxzbyBwcm92aWRlIGEgbGlzdCBvZiBzaWJsaW5nIGhhc2hlcy4gVGhlIHdpdGhkcmF3IGZ1bmN0aW9uCjs7IHVzZXMgdGhlIHByb3ZpZGVkIGhhc2hlcyB0byBlbnN1cmUgdGhlIHJlcXVlc3RlZCB3aXRoZHJhd2FsIGlzIHZhbGlkLgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljICh3aXRoZHJhdy1uZnQtYXNzZXQKICAgICAgICAobmZ0LWNvbnRyYWN0IDxuZnQtdHJhaXQ+KQogICAgICAgIChpZCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgICh3aXRoZHJhd2FsLWlkIHVpbnQpCiAgICAgICAgKGhlaWdodCB1aW50KQogICAgICAgIChuZnQtbWludC1jb250cmFjdCAob3B0aW9uYWwgPG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQ+KSkKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1sZWFmLWhhc2ggKGJ1ZmYgMzIpKQogICAgICAgIChzaWJsaW5nLWhhc2hlcyAobGlzdCA1MCB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIGFzc2V0IGJlbG9uZ3MgdG8gdGhlIGFsbG93ZWQtY29udHJhY3RzIG1hcAogICAgICAgICAgICAobDItY29udHJhY3QgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBuZnQtY29udHJhY3QpKSAoZXJyIEVSUl9ESVNBTExPV0VEX0FTU0VUKSkpCiAgICAgICAgKQogICAgICAgIChhc3NlcnRzIQogICAgICAgICAgICAodHJ5ISAoaW5uZXItd2l0aGRyYXctbmZ0LWFzc2V0CiAgICAgICAgICAgICAgICBuZnQtY29udHJhY3QKICAgICAgICAgICAgICAgIGwyLWNvbnRyYWN0CiAgICAgICAgICAgICAgICBpZAogICAgICAgICAgICAgICAgcmVjaXBpZW50CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLWlkCiAgICAgICAgICAgICAgICBoZWlnaHQKICAgICAgICAgICAgICAgIG5mdC1taW50LWNvbnRyYWN0CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLXJvb3QKICAgICAgICAgICAgICAgIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICBzaWJsaW5nLWhhc2hlcwogICAgICAgICAgICApKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAocHJpbnQgewogICAgICAgICAgICBldmVudDogIndpdGhkcmF3LW5mdCIsCiAgICAgICAgICAgIGwxLWNvbnRyYWN0LWlkOiAoYXMtY29udHJhY3QgbmZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgbmZ0LWlkOiBpZCwKICAgICAgICAgICAgcmVjaXBpZW50OiByZWNpcGllbnQKICAgICAgICB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7IExpa2UgYGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtbmZ0LWFzc2V0IGJ1dCB3aXRob3V0IGFsbG93aW5nIG9yIHJlcXVpcmluZyBhIG1pbnQgZnVuY3Rpb24uIEluIG9yZGVyIHRvIHdpdGhkcmF3LCB0aGUgdXNlciBtdXN0Cjs7IGhhdmUgdGhlIGFwcHJvcHJpYXRlIGJhbGFuY2UuCihkZWZpbmUtcHJpdmF0ZSAoaW5uZXItdHJhbnNmZXItd2l0aG91dC1taW50LW5mdC1hc3NldAogICAgICAgIChuZnQtY29udHJhY3QgPG5mdC10cmFpdD4pCiAgICAgICAgKGlkIHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgKGNhbGwtcmVzdWx0IChjb250cmFjdC1jYWxsPyBuZnQtY29udHJhY3QgZ2V0LW93bmVyIGlkKSkKICAgICAgICAgICAgKG5mdC1vd25lciAodW53cmFwISBjYWxsLXJlc3VsdCAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICAgICAoY29udHJhY3Qtb3ducy1uZnQgKGlzLWVxIG5mdC1vd25lciAoc29tZSBDT05UUkFDVF9BRERSRVNTKSkpCiAgICAgICAgKQoKICAgICAgICAoYXNzZXJ0cyEgY29udHJhY3Qtb3ducy1uZnQgKGVyciBFUlJfTkZUX05PVF9PV05FRF9CWV9DT05UUkFDVCkpCiAgICAgICAgKGlubmVyLXRyYW5zZmVyLW5mdC1hc3NldCBuZnQtY29udHJhY3QgaWQgQ09OVFJBQ1RfQUREUkVTUyByZWNpcGllbnQpCiAgICApCikKCjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OzsKOzsgRk9SIEZVTkdJQkxFIFRPS0VOIEFTU0VUIFRSQU5TRkVSUwoKOzsgSGVscGVyIGZ1bmN0aW9uIHRoYXQgdHJhbnNmZXJzIGEgc3BlY2lmaWVkIGFtb3VudCBvZiB0aGUgZnVuZ2libGUgdG9rZW4gZnJvbSB0aGUgZ2l2ZW4gc2VuZGVyIHRvIHRoZSBnaXZlbiByZWNpcGllbnQuCjs7IFJldHVybnMgcmVzcG9uc2U8Ym9vbCwgaW50PgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLWZ0LWFzc2V0CiAgICAgICAgKGZ0LWNvbnRyYWN0IDxmdC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChzZW5kZXIgcHJpbmNpcGFsKQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoY29udHJhY3QtY2FsbD8gZnQtY29udHJhY3QgdHJhbnNmZXIgYW1vdW50IHNlbmRlciByZWNpcGllbnQgbWVtbykpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICApCiAgICAgICAgOzsgRklYTUU6IFNJUC0wMTAgZG9lc24ndCByZXF1aXJlIHRoYXQgdHJhbnNmZXIgcmV0dXJucyAob2sgdHJ1ZSkgb24gc3VjY2Vzcywgc28gaXMgdGhpcyBjaGVjayBuZWNlc3Nhcnk/CiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdHJhbnNmZXIgc3VjY2VlZGVkCiAgICAgICAgKGFzc2VydHMhIHRyYW5zZmVyLXJlc3VsdCAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1wcml2YXRlIChpbm5lci1taW50LWZ0LWFzc2V0CiAgICAgICAgKGZ0LW1pbnQtY29udHJhY3QgPG1pbnQtZnJvbS1zdWJuZXQtdHJhaXQ+KQogICAgICAgIChhbW91bnQgdWludCkKICAgICAgICAoc2VuZGVyIHByaW5jaXBhbCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY2FsbC1yZXN1bHQgKGFzLWNvbnRyYWN0IChjb250cmFjdC1jYWxsPyBmdC1taW50LWNvbnRyYWN0IG1pbnQtZnJvbS1zdWJuZXQgYW1vdW50IHNlbmRlciByZWNpcGllbnQpKSkKICAgICAgICAgICAgKG1pbnQtcmVzdWx0ICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgKQogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRyYW5zZmVyIHN1Y2NlZWRlZAogICAgICAgIChhc3NlcnRzISBtaW50LXJlc3VsdCAoZXJyIEVSUl9NSU5UX0ZBSUxFRCkpCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgooZGVmaW5lLXByaXZhdGUgKGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoZnQtbWludC1jb250cmFjdCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoY29udHJhY3QtY2FsbD8gZnQtY29udHJhY3QgZ2V0LWJhbGFuY2UgQ09OVFJBQ1RfQUREUkVTUykpCiAgICAgICAgICAgIChjb250cmFjdC1mdC1iYWxhbmNlICh1bndyYXAhIGNhbGwtcmVzdWx0IChlcnIgRVJSX0NPTlRSQUNUX0NBTExfRkFJTEVEKSkpCiAgICAgICAgICAgIChjb250cmFjdC1vd25zLWVub3VnaCAoPj0gY29udHJhY3QtZnQtYmFsYW5jZSBhbW91bnQpKQogICAgICAgICAgICAoYW1vdW50LXRvLXRyYW5zZmVyIChpZiBjb250cmFjdC1vd25zLWVub3VnaCBhbW91bnQgY29udHJhY3QtZnQtYmFsYW5jZSkpCiAgICAgICAgICAgIChhbW91bnQtdG8tbWludCAoLSBhbW91bnQgYW1vdW50LXRvLXRyYW5zZmVyKSkKICAgICAgICApCgogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRvdGFsIGJhbGFuY2UgYmV0d2VlbiB0aGUgdHJhbnNmZXIgYW5kIG1pbnQgaXMgZXF1YWwgdG8gdGhlIG9yaWdpbmFsIGJhbGFuY2UKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIGFtb3VudCAoKyBhbW91bnQtdG8tdHJhbnNmZXIgYW1vdW50LXRvLW1pbnQpKSAoZXJyIEVSUl9JTl9DT01QVVRBVElPTikpCgogICAgICAgIChhbmQKICAgICAgICAgICAgKD4gYW1vdW50LXRvLXRyYW5zZmVyIHUwKQogICAgICAgICAgICAodHJ5ISAoaW5uZXItdHJhbnNmZXItZnQtYXNzZXQgZnQtY29udHJhY3QgYW1vdW50LXRvLXRyYW5zZmVyIENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICkKICAgICAgICAoYW5kCiAgICAgICAgICAgICg+IGFtb3VudC10by1taW50IHUwKQogICAgICAgICAgICAodHJ5ISAoaW5uZXItbWludC1mdC1hc3NldCBmdC1taW50LWNvbnRyYWN0IGFtb3VudC10by1taW50IENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50KSkKICAgICAgICApCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgo7OyBBIHVzZXIgY2FsbHMgdGhpcyBmdW5jdGlvbiB0byBkZXBvc2l0IGEgZnVuZ2libGUgdG9rZW4gaW50byB0aGUgY29udHJhY3QuCjs7IFRoZSBmdW5jdGlvbiBlbWl0cyBhIHByaW50IHdpdGggZGV0YWlscyBvZiB0aGlzIGV2ZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wdWJsaWMgKGRlcG9zaXQtZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHNlbmRlciBwcmluY2lwYWwpCiAgICAgICAgKG1lbW8gKG9wdGlvbmFsIChidWZmIDM0KSkpCiAgICApCiAgICAobGV0ICgKICAgICAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgICAgIChzdWJuZXQtY29udHJhY3QtaWQgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkpIChlcnIgRVJSX0RJU0FMTE9XRURfQVNTRVQpKSkKICAgICAgICApCiAgICAgICAgOzsgVHJ5IHRvIHRyYW5zZmVyIHRoZSBGVCB0byB0aGlzIGNvbnRyYWN0CiAgICAgICAgKGFzc2VydHMhICh0cnkhIChpbm5lci10cmFuc2Zlci1mdC1hc3NldCBmdC1jb250cmFjdCBhbW91bnQgc2VuZGVyIENPTlRSQUNUX0FERFJFU1MgbWVtbykpIChlcnIgRVJSX1RSQU5TRkVSX0ZBSUxFRCkpCgogICAgICAgIChsZXQgKAogICAgICAgICAgICAgICAgKGZ0LW5hbWUgKHVud3JhcCEgKGNvbnRyYWN0LWNhbGw/IGZ0LWNvbnRyYWN0IGdldC1uYW1lKSAoZXJyIEVSUl9DT05UUkFDVF9DQUxMX0ZBSUxFRCkpKQogICAgICAgICAgICApCiAgICAgICAgICAgIDs7IEVtaXQgYSBwcmludCBldmVudCAtIHRoZSBub2RlIGNvbnN1bWVzIHRoaXMKICAgICAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgICAgIGV2ZW50OiAiZGVwb3NpdC1mdCIsCiAgICAgICAgICAgICAgICBsMS1jb250cmFjdC1pZDogKGFzLWNvbnRyYWN0IGZ0LWNvbnRyYWN0KSwKICAgICAgICAgICAgICAgIGZ0LW5hbWU6IGZ0LW5hbWUsCiAgICAgICAgICAgICAgICBmdC1hbW91bnQ6IGFtb3VudCwKICAgICAgICAgICAgICAgIHNlbmRlcjogc2VuZGVyLAogICAgICAgICAgICAgICAgc3VibmV0LWNvbnRyYWN0LWlkOiBzdWJuZXQtY29udHJhY3QtaWQsCiAgICAgICAgICAgIH0pCiAgICAgICAgKQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKOzsgVGhpcyBmdW5jdGlvbiBwZXJmb3JtcyB2YWxpZGl0eSBjaGVja3MgcmVsYXRlZCB0byB0aGUgd2l0aGRyYXdhbCBhbmQgcGVyZm9ybXMgdGhlIHdpdGhkcmF3YWwgYXMgd2VsbC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHJpdmF0ZSAoaW5uZXItd2l0aGRyYXctZnQtYXNzZXQKICAgICAgICAoZnQtY29udHJhY3QgPGZ0LXRyYWl0PikKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKG1lbW8gKG9wdGlvbmFsIChidWZmIDM0KSkpCiAgICAgICAgKGZ0LW1pbnQtY29udHJhY3QgKG9wdGlvbmFsIDxtaW50LWZyb20tc3VibmV0LXRyYWl0PikpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICAgICAgKHdpdGhkcmF3YWwtbGVhZi1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoZXMgKGxpc3QgNTAgewogICAgICAgICAgICBoYXNoOiAoYnVmZiAzMiksCiAgICAgICAgICAgIGlzLWxlZnQtc2lkZTogYm9vbCwKICAgICAgICB9KSkKICAgICkKICAgIChsZXQgKChoYXNoZXMtYXJlLXZhbGlkIChjaGVjay13aXRoZHJhd2FsLWhhc2hlcyB3aXRoZHJhd2FsLXJvb3Qgd2l0aGRyYXdhbC1sZWFmLWhhc2ggc2libGluZy1oYXNoZXMpKSkKICAgICAgICAoYXNzZXJ0cyEgKHRyeSEgaGFzaGVzLWFyZS12YWxpZCkgKGVyciBFUlJfVkFMSURBVElPTl9GQUlMRUQpKQoKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSB3aXRoZHJhd2FsIHJlcXVlc3QgZGF0YSBtYXRjaGVzIHRoZSBzdXBwbGllZCBsZWFmIGhhc2gKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICAgICAgICAgICAobGVhZi1oYXNoLXdpdGhkcmF3LWZ0IChjb250cmFjdC1vZiBmdC1jb250cmFjdCkgYW1vdW50IHJlY2lwaWVudCB3aXRoZHJhd2FsLWlkIGhlaWdodCkpCiAgICAgICAgICAgICAgICAgIChlcnIgRVJSX1ZBTElEQVRJT05fTEVBRl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEKICAgICAgICAgICAgICAgIChtYXRjaCBmdC1taW50LWNvbnRyYWN0CiAgICAgICAgICAgICAgICAgICAgbWludC1jb250cmFjdCAoYXMtY29udHJhY3QgKGlubmVyLXRyYW5zZmVyLW9yLW1pbnQtZnQtYXNzZXQgZnQtY29udHJhY3QgbWludC1jb250cmFjdCBhbW91bnQgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICAgICAgICAgICAgIChhcy1jb250cmFjdCAoaW5uZXItdHJhbnNmZXItZnQtYXNzZXQgZnQtY29udHJhY3QgYW1vdW50IENPTlRSQUNUX0FERFJFU1MgcmVjaXBpZW50IG1lbW8pKQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICApCiAgICAgICAgICAgIChlcnIgRVJSX1RSQU5TRkVSX0ZBSUxFRCkKICAgICAgICApCgogICAgICAgIChhc3NlcnRzIQogICAgICAgICAgKGZpbmlzaC13aXRoZHJhdyB7IHdpdGhkcmF3YWwtbGVhZi1oYXNoOiB3aXRoZHJhd2FsLWxlYWYtaGFzaCwgd2l0aGRyYXdhbC1yb290LWhhc2g6IHdpdGhkcmF3YWwtcm9vdCB9KQogICAgICAgICAgKGVyciBFUlJfV0lUSERSQVdBTF9BTFJFQURZX1BST0NFU1NFRCkpCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgo7OyBBIHVzZXIgY2FuIGNhbGwgdGhpcyBmdW5jdGlvbiB0byB3aXRoZHJhdyBzb21lIGFtb3VudCBvZiBhIGZ1bmdpYmxlIHRva2VuIGFzc2V0IGZyb20gdGhlCjs7IGNvbnRyYWN0IGFuZCBzZW5kIGl0IHRvIGEgcmVjaXBpZW50Lgo7OyBJbiBvcmRlciBmb3IgdGhpcyB3aXRoZHJhd2FsIHRvIGdvIHRocm91Z2gsIHRoZSBnaXZlbiB3aXRoZHJhd2FsIG11c3QgaGF2ZSBiZWVuIGluY2x1ZGVkCjs7IGluIGEgd2l0aGRyYXdhbCBNZXJrbGUgdHJlZSBhIHN1Ym5ldCBtaW5lciBzdWJtaXR0ZWQuIFRoZSB1c2VyIG11c3QgcHJvdmlkZSB0aGUgbGVhZgo7OyBoYXNoIG9mIHRoZWlyIHdpdGhkcmF3YWwgYW5kIHRoZSByb290IGhhc2ggb2YgdGhlIHNwZWNpZmljIE1lcmtsZSB0cmVlIHRoZWlyIHdpdGhkcmF3YWwKOzsgaXMgaW5jbHVkZWQgaW4uIFRoZXkgbXVzdCBhbHNvIHByb3ZpZGUgYSBsaXN0IG9mIHNpYmxpbmcgaGFzaGVzLiBUaGUgd2l0aGRyYXcgZnVuY3Rpb24KOzsgdXNlcyB0aGUgcHJvdmlkZWQgaGFzaGVzIHRvIGVuc3VyZSB0aGUgcmVxdWVzdGVkIHdpdGhkcmF3YWwgaXMgdmFsaWQuCjs7IFRoZSBmdW5jdGlvbiBlbWl0cyBhIHByaW50IHdpdGggZGV0YWlscyBvZiB0aGlzIGV2ZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wdWJsaWMgKHdpdGhkcmF3LWZ0LWFzc2V0CiAgICAgICAgKGZ0LWNvbnRyYWN0IDxmdC10cmFpdD4pCiAgICAgICAgKGFtb3VudCB1aW50KQogICAgICAgIChyZWNpcGllbnQgcHJpbmNpcGFsKQogICAgICAgICh3aXRoZHJhd2FsLWlkIHVpbnQpCiAgICAgICAgKGhlaWdodCB1aW50KQogICAgICAgIChtZW1vIChvcHRpb25hbCAoYnVmZiAzNCkpKQogICAgICAgIChmdC1taW50LWNvbnRyYWN0IChvcHRpb25hbCA8bWludC1mcm9tLXN1Ym5ldC10cmFpdD4pKQogICAgICAgICh3aXRoZHJhd2FsLXJvb3QgKGJ1ZmYgMzIpKQogICAgICAgICh3aXRoZHJhd2FsLWxlYWYtaGFzaCAoYnVmZiAzMikpCiAgICAgICAgKHNpYmxpbmctaGFzaGVzIChsaXN0IDUwIHsKICAgICAgICAgICAgaGFzaDogKGJ1ZmYgMzIpLAogICAgICAgICAgICBpcy1sZWZ0LXNpZGU6IGJvb2wsCiAgICAgICAgfSkpCiAgICApCiAgICAoYmVnaW4KICAgICAgICA7OyBDaGVjayB0aGF0IHRoZSB3aXRoZHJhdyBhbW91bnQgaXMgcG9zaXRpdmUKICAgICAgICAoYXNzZXJ0cyEgKD4gYW1vdW50IHUwKSAoZXJyIEVSUl9BVFRFTVBUX1RPX1RSQU5TRkVSX1pFUk9fQU1PVU5UKSkKCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgYXNzZXQgYmVsb25ncyB0byB0aGUgYWxsb3dlZC1jb250cmFjdHMgbWFwCiAgICAgICAgKHVud3JhcCEgKG1hcC1nZXQ/IGFsbG93ZWQtY29udHJhY3RzIChjb250cmFjdC1vZiBmdC1jb250cmFjdCkpIChlcnIgRVJSX0RJU0FMTE9XRURfQVNTRVQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgICAgKHRyeSEgKGlubmVyLXdpdGhkcmF3LWZ0LWFzc2V0CiAgICAgICAgICAgICAgICBmdC1jb250cmFjdAogICAgICAgICAgICAgICAgYW1vdW50CiAgICAgICAgICAgICAgICByZWNpcGllbnQKICAgICAgICAgICAgICAgIHdpdGhkcmF3YWwtaWQKICAgICAgICAgICAgICAgIGhlaWdodAogICAgICAgICAgICAgICAgbWVtbwogICAgICAgICAgICAgICAgZnQtbWludC1jb250cmFjdAogICAgICAgICAgICAgICAgd2l0aGRyYXdhbC1yb290CiAgICAgICAgICAgICAgICB3aXRoZHJhd2FsLWxlYWYtaGFzaAogICAgICAgICAgICAgICAgc2libGluZy1oYXNoZXMpKQogICAgICAgICAgICAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpCiAgICAgICAgKQoKICAgICAgICAobGV0ICgKICAgICAgICAgICAgICAgIChmdC1uYW1lICh1bndyYXAhIChjb250cmFjdC1jYWxsPyBmdC1jb250cmFjdCBnZXQtbmFtZSkgKGVyciBFUlJfQ09OVFJBQ1RfQ0FMTF9GQUlMRUQpKSkKICAgICAgICAgICAgKQogICAgICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAgICAgKHByaW50IHsKICAgICAgICAgICAgICAgIGV2ZW50OiAid2l0aGRyYXctZnQiLAogICAgICAgICAgICAgICAgbDEtY29udHJhY3QtaWQ6IChhcy1jb250cmFjdCBmdC1jb250cmFjdCksCiAgICAgICAgICAgICAgICBmdC1uYW1lOiBmdC1uYW1lLAogICAgICAgICAgICAgICAgZnQtYW1vdW50OiBhbW91bnQsCiAgICAgICAgICAgICAgICByZWNpcGllbnQ6IHJlY2lwaWVudCwKICAgICAgICAgICAgfSkKICAgICAgICApCgogICAgICAgIChvayB0cnVlKQogICAgKQopCgoKOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Cjs7IEZPUiBTVFggVFJBTlNGRVJTCgoKOzsgSGVscGVyIGZ1bmN0aW9uIHRoYXQgdHJhbnNmZXJzIHRoZSBnaXZlbiBhbW91bnQgZnJvbSB0aGUgc3BlY2lmaWVkIGZ1bmdpYmxlIHRva2VuIGZyb20gdGhlIGdpdmVuIHNlbmRlciB0byB0aGUgZ2l2ZW4gcmVjaXBpZW50Lgo7OyBSZXR1cm5zIHJlc3BvbnNlPGJvb2wsIGludD4KKGRlZmluZS1wcml2YXRlIChpbm5lci10cmFuc2Zlci1zdHggKGFtb3VudCB1aW50KSAoc2VuZGVyIHByaW5jaXBhbCkgKHJlY2lwaWVudCBwcmluY2lwYWwpKQogICAgKGxldCAoCiAgICAgICAgICAgIChjYWxsLXJlc3VsdCAoc3R4LXRyYW5zZmVyPyBhbW91bnQgc2VuZGVyIHJlY2lwaWVudCkpCiAgICAgICAgICAgICh0cmFuc2Zlci1yZXN1bHQgKHVud3JhcCEgY2FsbC1yZXN1bHQgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkpCiAgICAgICAgKQogICAgICAgIDs7IENoZWNrIHRoYXQgdGhlIHRyYW5zZmVyIHN1Y2NlZWRlZAogICAgICAgIChhc3NlcnRzISB0cmFuc2Zlci1yZXN1bHQgKGVyciBFUlJfVFJBTlNGRVJfRkFJTEVEKSkKCiAgICAgICAgKG9rIHRydWUpCiAgICApCikKCjs7IEEgdXNlciBjYWxscyB0aGlzIGZ1bmN0aW9uIHRvIGRlcG9zaXQgU1RYIGludG8gdGhlIGNvbnRyYWN0Lgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljIChkZXBvc2l0LXN0eCAoYW1vdW50IHVpbnQpIChzZW5kZXIgcHJpbmNpcGFsKSkKICAgIChiZWdpbgogICAgICAgIDs7IFRyeSB0byB0cmFuc2ZlciB0aGUgU1RYIHRvIHRoaXMgY29udHJhY3QKICAgICAgICAoYXNzZXJ0cyEgKHRyeSEgKGlubmVyLXRyYW5zZmVyLXN0eCBhbW91bnQgc2VuZGVyIENPTlRSQUNUX0FERFJFU1MpKSAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQgLSB0aGUgbm9kZSBjb25zdW1lcyB0aGlzCiAgICAgICAgKHByaW50IHsgZXZlbnQ6ICJkZXBvc2l0LXN0eCIsIHNlbmRlcjogc2VuZGVyLCBhbW91bnQ6IGFtb3VudCB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGxlYWYtaGFzaC13aXRoZHJhdy1zdHgKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICApCiAgICAoc2hhNTEyLzI1NiAoY29uY2F0IDB4MDAgKHVud3JhcC1wYW5pYyAodG8tY29uc2Vuc3VzLWJ1ZmY/CiAgICAgICAgewogICAgICAgICAgICB0eXBlOiAic3R4IiwKICAgICAgICAgICAgYW1vdW50OiBhbW91bnQsCiAgICAgICAgICAgIHJlY2lwaWVudDogcmVjaXBpZW50LAogICAgICAgICAgICB3aXRoZHJhd2FsLWlkOiB3aXRoZHJhd2FsLWlkLAogICAgICAgICAgICBoZWlnaHQ6IGhlaWdodAogICAgICAgIH0pKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGxlYWYtaGFzaC13aXRoZHJhdy1uZnQKICAgICAgICAoYXNzZXQtY29udHJhY3QgcHJpbmNpcGFsKQogICAgICAgIChuZnQtaWQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICAgICAod2l0aGRyYXdhbC1pZCB1aW50KQogICAgICAgIChoZWlnaHQgdWludCkKICAgICkKICAgIChzaGE1MTIvMjU2IChjb25jYXQgMHgwMCAodW53cmFwLXBhbmljICh0by1jb25zZW5zdXMtYnVmZj8KICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJuZnQiLAogICAgICAgICAgICBuZnQtaWQ6IG5mdC1pZCwKICAgICAgICAgICAgYXNzZXQtY29udHJhY3Q6IGFzc2V0LWNvbnRyYWN0LAogICAgICAgICAgICByZWNpcGllbnQ6IHJlY2lwaWVudCwKICAgICAgICAgICAgd2l0aGRyYXdhbC1pZDogd2l0aGRyYXdhbC1pZCwKICAgICAgICAgICAgaGVpZ2h0OiBoZWlnaHQKICAgICAgICB9KSkpCiAgICApCikKCihkZWZpbmUtcmVhZC1vbmx5IChsZWFmLWhhc2gtd2l0aGRyYXctZnQKICAgICAgICAoYXNzZXQtY29udHJhY3QgcHJpbmNpcGFsKQogICAgICAgIChhbW91bnQgdWludCkKICAgICAgICAocmVjaXBpZW50IHByaW5jaXBhbCkKICAgICAgICAod2l0aGRyYXdhbC1pZCB1aW50KQogICAgICAgIChoZWlnaHQgdWludCkKICAgICkKICAgIChzaGE1MTIvMjU2IChjb25jYXQgMHgwMCAodW53cmFwLXBhbmljICh0by1jb25zZW5zdXMtYnVmZj8KICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJmdCIsCiAgICAgICAgICAgIGFtb3VudDogYW1vdW50LAogICAgICAgICAgICBhc3NldC1jb250cmFjdDogYXNzZXQtY29udHJhY3QsCiAgICAgICAgICAgIHJlY2lwaWVudDogcmVjaXBpZW50LAogICAgICAgICAgICB3aXRoZHJhd2FsLWlkOiB3aXRoZHJhd2FsLWlkLAogICAgICAgICAgICBoZWlnaHQ6IGhlaWdodAogICAgICAgIH0pKSkKICAgICkKKQoKOzsgQSB1c2VyIGNhbGxzIHRoaXMgZnVuY3Rpb24gdG8gd2l0aGRyYXcgU1RYIGZyb20gdGhpcyBjb250cmFjdC4KOzsgSW4gb3JkZXIgZm9yIHRoaXMgd2l0aGRyYXdhbCB0byBnbyB0aHJvdWdoLCB0aGUgZ2l2ZW4gd2l0aGRyYXdhbCBtdXN0IGhhdmUgYmVlbiBpbmNsdWRlZAo7OyBpbiBhIHdpdGhkcmF3YWwgTWVya2xlIHRyZWUgYSBzdWJuZXQgbWluZXIgc3VibWl0dGVkLiBUaGUgdXNlciBtdXN0IHByb3ZpZGUgdGhlIGxlYWYKOzsgaGFzaCBvZiB0aGVpciB3aXRoZHJhd2FsIGFuZCB0aGUgcm9vdCBoYXNoIG9mIHRoZSBzcGVjaWZpYyBNZXJrbGUgdHJlZSB0aGVpciB3aXRoZHJhd2FsCjs7IGlzIGluY2x1ZGVkIGluLiBUaGV5IG11c3QgYWxzbyBwcm92aWRlIGEgbGlzdCBvZiBzaWJsaW5nIGhhc2hlcy4gVGhlIHdpdGhkcmF3IGZ1bmN0aW9uCjs7IHVzZXMgdGhlIHByb3ZpZGVkIGhhc2hlcyB0byBlbnN1cmUgdGhlIHJlcXVlc3RlZCB3aXRoZHJhd2FsIGlzIHZhbGlkLgo7OyBUaGUgZnVuY3Rpb24gZW1pdHMgYSBwcmludCB3aXRoIGRldGFpbHMgb2YgdGhpcyBldmVudC4KOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHVibGljICh3aXRoZHJhdy1zdHgKICAgICAgICAoYW1vdW50IHVpbnQpCiAgICAgICAgKHJlY2lwaWVudCBwcmluY2lwYWwpCiAgICAgICAgKHdpdGhkcmF3YWwtaWQgdWludCkKICAgICAgICAoaGVpZ2h0IHVpbnQpCiAgICAgICAgKHdpdGhkcmF3YWwtcm9vdCAoYnVmZiAzMikpCiAgICAgICAgKHdpdGhkcmF3YWwtbGVhZi1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoZXMgKGxpc3QgNTAgewogICAgICAgICAgICBoYXNoOiAoYnVmZiAzMiksCiAgICAgICAgICAgIGlzLWxlZnQtc2lkZTogYm9vbCwKICAgICAgICB9KSkKICAgICkKICAgIChsZXQgKChoYXNoZXMtYXJlLXZhbGlkIChjaGVjay13aXRoZHJhd2FsLWhhc2hlcyB3aXRoZHJhd2FsLXJvb3Qgd2l0aGRyYXdhbC1sZWFmLWhhc2ggc2libGluZy1oYXNoZXMpKSkKCiAgICAgICAgKGFzc2VydHMhICh0cnkhIGhhc2hlcy1hcmUtdmFsaWQpIChlcnIgRVJSX1ZBTElEQVRJT05fRkFJTEVEKSkKICAgICAgICA7OyBjaGVjayB0aGF0IHRoZSB3aXRoZHJhd2FsIHJlcXVlc3QgZGF0YSBtYXRjaGVzIHRoZSBzdXBwbGllZCBsZWFmIGhhc2gKICAgICAgICAoYXNzZXJ0cyEgKGlzLWVxIHdpdGhkcmF3YWwtbGVhZi1oYXNoCiAgICAgICAgICAgICAgICAgICAgICAgICAobGVhZi1oYXNoLXdpdGhkcmF3LXN0eCBhbW91bnQgcmVjaXBpZW50IHdpdGhkcmF3YWwtaWQgaGVpZ2h0KSkKICAgICAgICAgICAgICAgICAgKGVyciBFUlJfVkFMSURBVElPTl9MRUFGX0ZBSUxFRCkpCgogICAgICAgIChhc3NlcnRzISAodHJ5ISAoYXMtY29udHJhY3QgKGlubmVyLXRyYW5zZmVyLXN0eCBhbW91bnQgdHgtc2VuZGVyIHJlY2lwaWVudCkpKSAoZXJyIEVSUl9UUkFOU0ZFUl9GQUlMRUQpKQoKICAgICAgICAoYXNzZXJ0cyEKICAgICAgICAgIChmaW5pc2gtd2l0aGRyYXcgeyB3aXRoZHJhd2FsLWxlYWYtaGFzaDogd2l0aGRyYXdhbC1sZWFmLWhhc2gsIHdpdGhkcmF3YWwtcm9vdC1oYXNoOiB3aXRoZHJhd2FsLXJvb3QgfSkKICAgICAgICAgIChlcnIgRVJSX1dJVEhEUkFXQUxfQUxSRUFEWV9QUk9DRVNTRUQpKQoKICAgICAgICA7OyBFbWl0IGEgcHJpbnQgZXZlbnQKICAgICAgICAocHJpbnQgeyBldmVudDogIndpdGhkcmF3LXN0eCIsIHJlY2lwaWVudDogcmVjaXBpZW50LCBhbW91bnQ6IGFtb3VudCB9KQoKICAgICAgICAob2sgdHJ1ZSkKICAgICkKKQoKCjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Owo7OyBHRU5FUkFMIFdJVEhEUkFXQUwgRlVOQ1RJT05TCgo7OyBUaGlzIGZ1bmN0aW9uIGNvbmNhdHMgdGhlIHR3byBnaXZlbiBoYXNoZXMgaW4gdGhlIGNvcnJlY3Qgb3JkZXIuIEl0IGFsc28gcHJlcGVuZHMgdGhlIGJ1ZmYgYDB4MDFgLCB3aGljaCBpcwo7OyBhIHRhZyBkZW5vdGluZyBhIG5vZGUgKHZlcnN1cyBhIGxlYWYpLgo7OyBSZXR1cm5zIGEgYnVmZgooZGVmaW5lLXByaXZhdGUgKGNyZWF0ZS1ub2RlLWhhc2gKICAgICAgICAoY3Vyci1oYXNoIChidWZmIDMyKSkKICAgICAgICAoc2libGluZy1oYXNoIChidWZmIDMyKSkKICAgICAgICAoaXMtc2libGluZy1sZWZ0LXNpZGUgYm9vbCkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoY29uY2F0dGVkLWhhc2ggKGlmIGlzLXNpYmxpbmctbGVmdC1zaWRlCiAgICAgICAgICAgICAgICAgICAgKGNvbmNhdCBzaWJsaW5nLWhhc2ggY3Vyci1oYXNoKQogICAgICAgICAgICAgICAgICAgIChjb25jYXQgY3Vyci1oYXNoIHNpYmxpbmctaGFzaCkKICAgICAgICAgICAgICAgICkpCiAgICAgICAgICApCgogICAgICAgICAgKGNvbmNhdCAweDAxIGNvbmNhdHRlZC1oYXNoKQogICAgKQopCgo7OyBUaGlzIGZ1bmN0aW9uIGhhc2hlcyB0aGUgY3VyciBoYXNoIHdpdGggaXRzIHNpYmxpbmcgaGFzaC4KOzsgUmV0dXJucyAoYnVmZiAzMikKKGRlZmluZS1wcml2YXRlIChoYXNoLWhlbHAKICAgICAgICAoc2libGluZyB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pCiAgICAgICAgKGN1cnItbm9kZS1oYXNoIChidWZmIDMyKSkKICAgICkKICAgIChsZXQgKAogICAgICAgICAgICAoc2libGluZy1oYXNoIChnZXQgaGFzaCBzaWJsaW5nKSkKICAgICAgICAgICAgKGlzLXNpYmxpbmctbGVmdC1zaWRlIChnZXQgaXMtbGVmdC1zaWRlIHNpYmxpbmcpKQogICAgICAgICAgICAobmV3LWJ1ZmYgKGNyZWF0ZS1ub2RlLWhhc2ggY3Vyci1ub2RlLWhhc2ggc2libGluZy1oYXNoIGlzLXNpYmxpbmctbGVmdC1zaWRlKSkKICAgICAgICApCiAgICAgICAoc2hhNTEyLzI1NiBuZXctYnVmZikKICAgICkKKQoKOzsgVGhpcyBmdW5jdGlvbiBjaGVja3M6Cjs7ICAtIFRoYXQgdGhlIHByb3ZpZGVkIHdpdGhkcmF3YWwgcm9vdCBtYXRjaGVzIGEgcHJldmlvdXNseSBzdWJtaXR0ZWQgb25lIChwYXNzZWQgdG8gdGhlIGZ1bmN0aW9uIGBjb21taXQtYmxvY2tgKQo7OyAgLSBUaGF0IHRoZSBjb21wdXRlZCB3aXRoZHJhd2FsIHJvb3QgbWF0Y2hlcyBhIHByZXZpb3VzIHZhbGlkIHdpdGhkcmF3YWwgcm9vdAo7OyAgLSBUaGF0IHRoZSBnaXZlbiB3aXRoZHJhd2FsIGxlYWYgaGFzaCBoYXMgbm90IGJlZW4gcHJldmlvdXNseSBwcm9jZXNzZWQKOzsgUmV0dXJucyByZXNwb25zZTxib29sLCBpbnQ+CihkZWZpbmUtcHJpdmF0ZSAoY2hlY2std2l0aGRyYXdhbC1oYXNoZXMKICAgICAgICAod2l0aGRyYXdhbC1yb290IChidWZmIDMyKSkKICAgICAgICAod2l0aGRyYXdhbC1sZWFmLWhhc2ggKGJ1ZmYgMzIpKQogICAgICAgIChzaWJsaW5nLWhhc2hlcyAobGlzdCA1MCB7CiAgICAgICAgICAgIGhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgaXMtbGVmdC1zaWRlOiBib29sLAogICAgICAgIH0pKQogICAgKQogICAgKGJlZ2luCiAgICAgICAgOzsgQ2hlY2sgdGhhdCB0aGUgdXNlciBzdWJtaXR0ZWQgYSB2YWxpZCB3aXRoZHJhd2FsIHJvb3QKICAgICAgICAoYXNzZXJ0cyEgKGlzLXNvbWUgKG1hcC1nZXQ/IHdpdGhkcmF3YWwtcm9vdHMtbWFwIHdpdGhkcmF3YWwtcm9vdCkpIChlcnIgRVJSX0lOVkFMSURfTUVSS0xFX1JPT1QpKQoKICAgICAgICA7OyBDaGVjayB0aGF0IHRoaXMgd2l0aGRyYXdhbCBsZWFmIGhhcyBub3QgYmVlbiBwcm9jZXNzZWQgYmVmb3JlCiAgICAgICAgKGFzc2VydHMhCiAgICAgICAgICAgIChpcy1ub25lCiAgICAgICAgICAgICAobWFwLWdldD8gcHJvY2Vzc2VkLXdpdGhkcmF3YWwtbGVhdmVzLW1hcAogICAgICAgICAgICAgICAgICAgICAgIHsgd2l0aGRyYXdhbC1sZWFmLWhhc2g6IHdpdGhkcmF3YWwtbGVhZi1oYXNoLCB3aXRoZHJhd2FsLXJvb3QtaGFzaDogd2l0aGRyYXdhbC1yb290IH0pKQogICAgICAgICAgICAoZXJyIEVSUl9XSVRIRFJBV0FMX0FMUkVBRFlfUFJPQ0VTU0VEKSkKCiAgICAgICAgKGxldCAoKGNhbGN1bGF0ZWQtd2l0aGRyYXdhbC1yb290IChmb2xkIGhhc2gtaGVscCBzaWJsaW5nLWhhc2hlcyB3aXRoZHJhd2FsLWxlYWYtaGFzaCkpCiAgICAgICAgICAgICAgKHJvb3RzLW1hdGNoIChpcy1lcSBjYWxjdWxhdGVkLXdpdGhkcmF3YWwtcm9vdCB3aXRoZHJhd2FsLXJvb3QpKSkKICAgICAgICAgICAgIChpZiByb290cy1tYXRjaAogICAgICAgICAgICAgICAgKG9rIHRydWUpCiAgICAgICAgICAgICAgICAoZXJyIEVSUl9NRVJLTEVfUk9PVF9ET0VTX05PVF9NQVRDSCkpCiAgICAgICAgKQogICAgKQopCgo7OyBUaGlzIGZ1bmN0aW9uIHNob3VsZCBiZSBjYWxsZWQgYWZ0ZXIgdGhlIGFzc2V0IGluIHF1ZXN0aW9uIGhhcyBiZWVuIHRyYW5zZmVycmVkLgo7OyBJdCBhZGRzIHRoZSB3aXRoZHJhd2FsIGxlYWYgaGFzaCB0byBhIG1hcCBvZiBwcm9jZXNzZWQgbGVhdmVzLiBUaGlzIGVuc3VyZXMgdGhhdAo7OyB0aGlzIHdpdGhkcmF3YWwgbGVhZiBjYW4ndCBiZSB1c2VkIGFnYWluIHRvIHdpdGhkcmF3IGFkZGl0aW9uYWwgZnVuZHMuCjs7IFJldHVybnMgYm9vbAooZGVmaW5lLXByaXZhdGUgKGZpbmlzaC13aXRoZHJhdwogICAgICAgICh3aXRoZHJhdy1pbmZvIHsKICAgICAgICAgICAgd2l0aGRyYXdhbC1sZWFmLWhhc2g6IChidWZmIDMyKSwKICAgICAgICAgICAgd2l0aGRyYXdhbC1yb290LWhhc2g6IChidWZmIDMyKQogICAgICAgIH0pCiAgICApCiAgICAobWFwLWluc2VydCBwcm9jZXNzZWQtd2l0aGRyYXdhbC1sZWF2ZXMtbWFwIHdpdGhkcmF3LWluZm8gdHJ1ZSkKKQo=", + "clarity_version": 2, + "cost": 290960, + "location": { + "path": "./.cache/requirements/ST13F481SBR0R7Z6NMMH8YV2FJJYXA5JPA0AD3HP9.subnet-v1-2.clar" + } + }, + { + "transaction_type": "ContractPublish", + "contract_name": "px", + "expected_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "location": { + "path": "contracts/px.clar" + }, + "source": "Cjs7IHRpdGxlOiBweAo7OyB2ZXJzaW9uOgo7OyBzdW1tYXJ5Ogo7OyBkZXNjcmlwdGlvbjogQWxsb3dzIHVzZXJzIHRvIHBheSB0byB1cGRhdGUgZGF0YSBpbiBhIG1hdHJpeC4gCjs7ICBFYWNoIG1hdHJpeCB2YWx1ZSBtdXN0IGJlIGEgaGV4YWRlY2ltYWwgdmFsdWUgZnJvbSAweDAwMDAwMCB0byAweGZmZmZmZiwgcmVwcmVzZW50aW5nIGEgY29sb3IgdG8gYmUgZGlzcGxheWVkIG9uIGEgZ3JpZCBpbiBhIHdlYiBwYWdlLiAKOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuCgo7OyB0cmFpdHMKOzsKCjs7IHRva2VuIGRlZmluaXRpb25zCjs7IAoKOzsgY29uc3RhbnRzCjs7CihkZWZpbmUtY29uc3RhbnQgTUFYX0xPQyB1MTAwKQooZGVmaW5lLWNvbnN0YW50IE1BWF9WQUwgMHhmZmZmZmYpCihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMCkKKGRlZmluZS1jb25zdGFudCBBTExfTE9DUyAobGlzdCB1MCB1MSB1MiB1MyB1NCB1NSB1NiB1NyB1OCB1OSB1MTAgdTExIHUxMiB1MTMgdTE0IHUxNSB1MTYgdTE3IHUxOCB1MTkgdTIwIHUyMSB1MjIgdTIzIHUyNCB1MjUgdTI2IHUyNyB1MjggdTI5IHUzMCB1MzEgdTMyIHUzMyB1MzQgdTM1IHUzNiB1MzcgdTM4IHUzOSB1NDAgdTQxIHU0MiB1NDMgdTQ0IHU0NSB1NDYgdTQ3IHU0OCB1NDkgdTUwIHU1MSB1NTIgdTUzIHU1NCB1NTUgdTU2IHU1NyB1NTggdTU5IHU2MCB1NjEgdTYyIHU2MyB1NjQgdTY1IHU2NiB1NjcgdTY4IHU2OSB1NzAgdTcxIHU3MiB1NzMgdTc0IHU3NSB1NzYgdTc3IHU3OCB1NzkgdTgwIHU4MSB1ODIgdTgzIHU4NCB1ODUgdTg2IHU4NyB1ODggdTg5IHU5MCB1OTEgdTkyIHU5MyB1OTQgdTk1IHU5NiB1OTcgdTk4IHU5OSkpCjs7IGRhdGEgdmFycwo7OwoKOzsgZGF0YSBtYXBzCjs7CihkZWZpbmUtbWFwIHBpeGVscyB1aW50IChidWZmIDMpKQoKOzsgcHVibGljIGZ1bmN0aW9ucwo7OwooZGVmaW5lLXB1YmxpYyAoc2V0LXZhbHVlLWF0IChsb2MgdWludCkgKHZhbHVlIChidWZmIDMpKSkgCiAgICAoYmVnaW4gCiAgICAgICAgKGlmICg+PSBsb2MgTUFYX0xPQykKICAgICAgICAgICAgKGVyciAiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy4iKQogICAgICAgICAgICAoaWYgKD4gdmFsdWUgTUFYX1ZBTCkKICAgICAgICAgICAgICAgIChlcnIgIlZhbHVlIG11c3QgYmUgbGVzcyB0aGFuIDB4ZmZmZmZmLiIpCiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTCkKICAgICAgICAgICAgICAgICAgICAoZXJyICJWYWx1ZSBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAweDAwMDAwMC4iKQogICAgICAgICAgICAgICAgICAgIChvayAobWFwLXNldCBwaXhlbHMgbG9jIHZhbHVlKSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICkKICAgICkKKQo7OyByZWFkIG9ubHkgZnVuY3Rpb25zCjs7CgooZGVmaW5lLXJlYWQtb25seSAoZ2V0LXZhbHVlLWF0IChsb2MgdWludCkpCiAgICAoaWYgKD49IGxvYyBNQVhfTE9DKQogICAgICAgIChlcnIgIk91dCBvZiBib3VuZHMuIikKICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGdldC1hbGwpIAogICAgKG1hcCBnZXQtdmFsdWUtYXQgQUxMX0xPQ1MpCikKCihkZWZpbmUtcmVhZC1vbmx5IChnZW5lc2lzLXRpbWUgKGhlaWdodCB1aW50KSkKICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpCikKOzsgcHJpdmF0ZSBmdW5jdGlvbnMKOzsK", + "clarity_version": 2, + "cost": 18060, + "anchor_block_only": true + } + ], + "epoch": "2.1" + }, + { + "id": 3, + "transactions": [ + { + "transaction_type": "ContractCall", + "contract_id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.px", + "expected_sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "method": "set-value-at", + "parameters": ["u0", "0xfffffa"], + "cost": 2240, + "anchor_block_only": false + }, + { + "transaction_type": "StxTransfer", + "expected_sender": "ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB", + "recipient": "ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0", + "mstx_amount": 200, + "memo": "0xabcabcabcaba00000000000000000000000000000000000000000000000000000000", + "cost": 2240, + "anchor_block_only": true + } + ], + "epoch": "2.1" + } + ], + "contracts": [ + { + "contract_id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.px", + "path": "/Users/micaiahreid/work/stx-px/contracts/px.clar", + "source": "Cjs7IHRpdGxlOiBweAo7OyB2ZXJzaW9uOgo7OyBzdW1tYXJ5Ogo7OyBkZXNjcmlwdGlvbjogQWxsb3dzIHVzZXJzIHRvIHBheSB0byB1cGRhdGUgZGF0YSBpbiBhIG1hdHJpeC4gCjs7ICBFYWNoIG1hdHJpeCB2YWx1ZSBtdXN0IGJlIGEgaGV4YWRlY2ltYWwgdmFsdWUgZnJvbSAweDAwMDAwMCB0byAweGZmZmZmZiwgcmVwcmVzZW50aW5nIGEgY29sb3IgdG8gYmUgZGlzcGxheWVkIG9uIGEgZ3JpZCBpbiBhIHdlYiBwYWdlLiAKOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuCgo7OyB0cmFpdHMKOzsKCjs7IHRva2VuIGRlZmluaXRpb25zCjs7IAoKOzsgY29uc3RhbnRzCjs7CihkZWZpbmUtY29uc3RhbnQgTUFYX0xPQyB1MTAwKQooZGVmaW5lLWNvbnN0YW50IE1BWF9WQUwgMHhmZmZmZmYpCihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMCkKKGRlZmluZS1jb25zdGFudCBBTExfTE9DUyAobGlzdCB1MCB1MSB1MiB1MyB1NCB1NSB1NiB1NyB1OCB1OSB1MTAgdTExIHUxMiB1MTMgdTE0IHUxNSB1MTYgdTE3IHUxOCB1MTkgdTIwIHUyMSB1MjIgdTIzIHUyNCB1MjUgdTI2IHUyNyB1MjggdTI5IHUzMCB1MzEgdTMyIHUzMyB1MzQgdTM1IHUzNiB1MzcgdTM4IHUzOSB1NDAgdTQxIHU0MiB1NDMgdTQ0IHU0NSB1NDYgdTQ3IHU0OCB1NDkgdTUwIHU1MSB1NTIgdTUzIHU1NCB1NTUgdTU2IHU1NyB1NTggdTU5IHU2MCB1NjEgdTYyIHU2MyB1NjQgdTY1IHU2NiB1NjcgdTY4IHU2OSB1NzAgdTcxIHU3MiB1NzMgdTc0IHU3NSB1NzYgdTc3IHU3OCB1NzkgdTgwIHU4MSB1ODIgdTgzIHU4NCB1ODUgdTg2IHU4NyB1ODggdTg5IHU5MCB1OTEgdTkyIHU5MyB1OTQgdTk1IHU5NiB1OTcgdTk4IHU5OSkpCjs7IGRhdGEgdmFycwo7OwoKOzsgZGF0YSBtYXBzCjs7CihkZWZpbmUtbWFwIHBpeGVscyB1aW50IChidWZmIDMpKQoKOzsgcHVibGljIGZ1bmN0aW9ucwo7OwooZGVmaW5lLXB1YmxpYyAoc2V0LXZhbHVlLWF0IChsb2MgdWludCkgKHZhbHVlIChidWZmIDMpKSkgCiAgICAoYmVnaW4gCiAgICAgICAgKGlmICg+PSBsb2MgTUFYX0xPQykKICAgICAgICAgICAgKGVyciAiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy4iKQogICAgICAgICAgICAoaWYgKD4gdmFsdWUgTUFYX1ZBTCkKICAgICAgICAgICAgICAgIChlcnIgIlZhbHVlIG11c3QgYmUgbGVzcyB0aGFuIDB4ZmZmZmZmLiIpCiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTCkKICAgICAgICAgICAgICAgICAgICAoZXJyICJWYWx1ZSBtdXN0IGJlIGdyZWF0ZXIgdGhhbiAweDAwMDAwMC4iKQogICAgICAgICAgICAgICAgICAgIChvayAobWFwLXNldCBwaXhlbHMgbG9jIHZhbHVlKSkKICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgKQogICAgICAgICkKICAgICkKKQo7OyByZWFkIG9ubHkgZnVuY3Rpb25zCjs7CgooZGVmaW5lLXJlYWQtb25seSAoZ2V0LXZhbHVlLWF0IChsb2MgdWludCkpCiAgICAoaWYgKD49IGxvYyBNQVhfTE9DKQogICAgICAgIChlcnIgIk91dCBvZiBib3VuZHMuIikKICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSkKICAgICkKKQoKKGRlZmluZS1yZWFkLW9ubHkgKGdldC1hbGwpIAogICAgKG1hcCBnZXQtdmFsdWUtYXQgQUxMX0xPQ1MpCikKCihkZWZpbmUtcmVhZC1vbmx5IChnZW5lc2lzLXRpbWUgKGhlaWdodCB1aW50KSkKICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpCikKOzsgcHJpdmF0ZSBmdW5jdGlvbnMKOzsK" + } + ] + }, + "network_manifest": { + "network": { + "name": "devnet", + "stacks_node_rpc_address": null, + "bitcoin_node_rpc_address": null, + "deployment_fee_rate": 10, + "sats_per_bytes": 10 + }, + "accounts": [ + { + "label": "deployer", + "mnemonic": "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "btc_address": "mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH", + "is_mainnet": false + }, + { + "label": "faucet", + "mnemonic": "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6", + "btc_address": "mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d", + "is_mainnet": false + }, + { + "label": "wallet_1", + "mnemonic": "crazy vibrant runway diagram beach language above aerobic maze coral this gas mirror output vehicle cover usage ecology unfold room feel file rocket expire", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "STB8E0SMACY4A6DCCH4WE48YGX3P877407QW176V", + "btc_address": "mha4u7F3e93P9Xy1WQgVvGtYtynnJtT22x", + "is_mainnet": false + }, + { + "label": "wallet_2", + "mnemonic": "hold excess usual excess ring elephant install account glad dry fragile donkey gaze humble truck breeze nation gasp vacuum limb head keep delay hospital", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG", + "btc_address": "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG", + "is_mainnet": false + }, + { + "label": "wallet_3", + "mnemonic": "cycle puppy glare enroll cost improve round trend wrist mushroom scorpion tower claim oppose clever elephant dinosaur eight problem before frozen dune wagon high", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC", + "btc_address": "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7", + "is_mainnet": false + }, + { + "label": "wallet_4", + "mnemonic": "board list obtain sugar hour worth raven scout denial thunder horse logic fury scorpion fold genuine phrase wealth news aim below celery when cabin", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND", + "btc_address": "mg1C76bNTutiCDV3t9nWhZs3Dc8LzUufj8", + "is_mainnet": false + }, + { + "label": "wallet_5", + "mnemonic": "hurry aunt blame peanut heavy update captain human rice crime juice adult scale device promote vast project quiz unit note reform update climb purchase", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB", + "btc_address": "mweN5WVqadScHdA81aATSdcVr4B6dNokqx", + "is_mainnet": false + }, + { + "label": "wallet_6", + "mnemonic": "area desk dutch sign gold cricket dawn toward giggle vibrant indoor bench warfare wagon number tiny universe sand talk dilemma pottery bone trap buddy", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0", + "btc_address": "mzxXgV6e4BZSsz8zVHm3TmqbECt7mbuErt", + "is_mainnet": false + }, + { + "label": "wallet_7", + "mnemonic": "prevent gallery kind limb income control noise together echo rival record wedding sense uncover school version force bleak nuclear include danger skirt enact arrow", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ", + "btc_address": "n37mwmru2oaVosgfuvzBwgV2ysCQRrLko7", + "is_mainnet": false + }, + { + "label": "wallet_8", + "mnemonic": "female adjust gallery certain visit token during great side clown fitness like hurt clip knife warm bench start reunion globe detail dream depend fortune", + "derivation": "m/44'/5757'/0'/0/0", + "balance": 100000000000000, + "stx_address": "ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP", + "btc_address": "n2v875jbJ4RjBnTjgbfikDfnwsDV5iUByw", + "is_mainnet": false + } + ], + "devnet_settings": { + "name": "devnet", + "network_id": null, + "orchestrator_ingestion_port": 20445, + "orchestrator_control_port": 20446, + "bitcoin_node_p2p_port": 18444, + "bitcoin_node_rpc_port": 18443, + "bitcoin_node_username": "devnet", + "bitcoin_node_password": "devnet", + "stacks_node_p2p_port": 20444, + "stacks_node_rpc_port": 20443, + "stacks_node_wait_time_for_microblocks": 50, + "stacks_node_first_attempt_time_ms": 500, + "stacks_node_subsequent_attempt_time_ms": 1000, + "stacks_node_events_observers": ["host.docker.internal:20455"], + "stacks_node_env_vars": [], + "stacks_api_port": 3999, + "stacks_api_events_port": 3700, + "stacks_api_env_vars": [], + "stacks_explorer_port": 8000, + "stacks_explorer_env_vars": [], + "bitcoin_explorer_port": 8001, + "bitcoin_controller_block_time": 60000, + "bitcoin_controller_automining_disabled": false, + "miner_stx_address": "ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ", + "miner_secret_key_hex": "3b68e410cc7f9b8bae76f2f2991b69ecd0627c95da22a904065dfb2a73d0585f01", + "miner_btc_address": "n3GRiDLKWuKLCw1DZmV75W1mE35qmW2tQm", + "miner_mnemonic": "fragile loan twenty basic net assault jazz absorb diet talk art shock innocent float punch travel gadget embrace caught blossom hockey surround initial reduce", + "miner_derivation_path": "m/44'/5757'/0'/0/0", + "miner_coinbase_recipient": "ST3Q96TFVE6E0Q91XVX6S8RWAJW5R8XTZ8YEBM8RQ", + "faucet_stx_address": "STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6", + "faucet_secret_key_hex": "de433bdfa14ec43aa1098d5be594c8ffb20a31485ff9de2923b2689471c401b801", + "faucet_btc_address": "mjSrB3wS4xab3kYqFktwBzfTdPg367ZJ2d", + "faucet_mnemonic": "shadow private easily thought say logic fault paddle word top book during ignore notable orange flight clock image wealth health outside kitten belt reform", + "faucet_derivation_path": "m/44'/5757'/0'/0/0", + "working_dir": "/Users/micaiahreid/work/stx-px/tmp", + "postgres_port": 5432, + "postgres_username": "postgres", + "postgres_password": "postgres", + "stacks_api_postgres_database": "stacks_api", + "subnet_api_postgres_database": "subnet_api", + "pox_stacking_orders": [ + { + "start_at_cycle": 3, + "duration": 12, + "wallet": "wallet_1", + "slots": 2, + "btc_address": "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC" + }, + { + "start_at_cycle": 3, + "duration": 12, + "wallet": "wallet_2", + "slots": 1, + "btc_address": "muYdXKmX9bByAueDe6KFfHd5Ff1gdN9ErG" + }, + { + "start_at_cycle": 3, + "duration": 12, + "wallet": "wallet_3", + "slots": 1, + "btc_address": "mvZtbibDAAA3WLpY7zXXFqRa3T4XSknBX7" } - ] + ], + "execute_script": [], + "bitcoin_node_image_url": "quay.io/hirosystems/bitcoind:devnet-v3", + "stacks_node_image_url": "quay.io/hirosystems/stacks-node:devnet-2.4.0.0.0", + "stacks_api_image_url": "hirosystems/stacks-blockchain-api:latest", + "stacks_explorer_image_url": "hirosystems/explorer:latest", + "postgres_image_url": "postgres:14", + "bitcoin_explorer_image_url": "quay.io/hirosystems/bitcoin-explorer:devnet", + "disable_bitcoin_explorer": true, + "disable_stacks_explorer": true, + "disable_stacks_api": false, + "bind_containers_volumes": false, + "enable_subnet_node": false, + "subnet_node_image_url": "hirosystems/stacks-subnets:0.8.1", + "subnet_leader_stx_address": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", + "subnet_leader_secret_key_hex": "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601", + "subnet_leader_btc_address": "mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH", + "subnet_leader_mnemonic": "twice kind fence tip hidden tilt action fragile skin nothing glory cousin green tomorrow spring wrist shed math olympic multiply hip blue scout claw", + "subnet_leader_derivation_path": "m/44'/5757'/0'/0/0", + "subnet_node_p2p_port": 30444, + "subnet_node_rpc_port": 30443, + "subnet_events_ingestion_port": 30445, + "subnet_node_events_observers": [], + "subnet_contract_id": "ST173JK7NZBA4BS05ZRATQH1K89YJMTGEH1Z5J52E.subnet-v3-0-1", + "remapped_subnet_contract_id": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.subnet-v3-0-1", + "subnet_node_env_vars": [], + "subnet_api_image_url": "hirosystems/stacks-blockchain-api:latest", + "subnet_api_port": 13999, + "subnet_api_events_port": 13700, + "subnet_api_env_vars": [], + "disable_subnet_api": true, + "docker_host": "unix:///var/run/docker.sock", + "components_host": "127.0.0.1", + "epoch_2_0": 100, + "epoch_2_05": 100, + "epoch_2_1": 101, + "epoch_2_2": 103, + "epoch_2_3": 104, + "epoch_2_4": 105, + "pox_2_activation": 102, + "use_docker_gateway_routing": false, + "docker_platform": "linux/amd64" } }, - "contracts": [ - { + "project_manifest": { + "project": { "name": "px", - "source": "XG47OyB0aXRsZTogcHhcbjs7IHZlcnNpb246XG47OyBzdW1tYXJ5OlxuOzsgZGVzY3JpcHRpb246IEFsbG93cyB1c2VycyB0byBwYXkgdG8gdXBkYXRlIGRhdGEgaW4gYSBtYXRyaXguIFxuOzsgIEVhY2ggbWF0cml4IHZhbHVlIG11c3QgYmUgYSBoZXhhZGVjaW1hbCB2YWx1ZSBmcm9tIDB4MDAwMDAwIHRvIDB4ZmZmZmZmLCByZXByZXNlbnRpbmcgYSBjb2xvciB0byBiZSBkaXNwbGF5ZWQgb24gYSBncmlkIGluIGEgd2ViIHBhZ2UuIFxuOzsgIEVhY2ggbWF0cml4IGtleSBjb3JyZXNwb25kcyB0byB0aGUgbG9jYXRpb24gb2YgdGhlIGdyaWQsIHdoaWNoIGlzIDEwMHgxMDAgY2VsbHMuXG5cbjs7IHRyYWl0c1xuOztcblxuOzsgdG9rZW4gZGVmaW5pdGlvbnNcbjs7IFxuXG47OyBjb25zdGFudHNcbjs7XG4oZGVmaW5lLWNvbnN0YW50IE1BWF9MT0MgdTEwMClcbihkZWZpbmUtY29uc3RhbnQgTUFYX1ZBTCAweGZmZmZmZilcbihkZWZpbmUtY29uc3RhbnQgTUlOX1ZBTCAweDAwMDAwMClcbihkZWZpbmUtY29uc3RhbnQgQUxMX0xPQ1MgKGxpc3QgdTAgdTEgdTIgdTMgdTQgdTUgdTYgdTcgdTggdTkgdTEwIHUxMSB1MTIgdTEzIHUxNCB1MTUgdTE2IHUxNyB1MTggdTE5IHUyMCB1MjEgdTIyIHUyMyB1MjQgdTI1IHUyNiB1MjcgdTI4IHUyOSB1MzAgdTMxIHUzMiB1MzMgdTM0IHUzNSB1MzYgdTM3IHUzOCB1MzkgdTQwIHU0MSB1NDIgdTQzIHU0NCB1NDUgdTQ2IHU0NyB1NDggdTQ5IHU1MCB1NTEgdTUyIHU1MyB1NTQgdTU1IHU1NiB1NTcgdTU4IHU1OSB1NjAgdTYxIHU2MiB1NjMgdTY0IHU2NSB1NjYgdTY3IHU2OCB1NjkgdTcwIHU3MSB1NzIgdTczIHU3NCB1NzUgdTc2IHU3NyB1NzggdTc5IHU4MCB1ODEgdTgyIHU4MyB1ODQgdTg1IHU4NiB1ODcgdTg4IHU4OSB1OTAgdTkxIHU5MiB1OTMgdTk0IHU5NSB1OTYgdTk3IHU5OCB1OTkpKVxuOzsgZGF0YSB2YXJzXG47O1xuXG47OyBkYXRhIG1hcHNcbjs7XG4oZGVmaW5lLW1hcCBwaXhlbHMgdWludCAoYnVmZiAzKSlcblxuOzsgcHVibGljIGZ1bmN0aW9uc1xuOztcbihkZWZpbmUtcHVibGljIChzZXQtdmFsdWUtYXQgKGxvYyB1aW50KSAodmFsdWUgKGJ1ZmYgMykpKSBcbiAgICAoYmVnaW4gXG4gICAgICAgIChpZiAoPj0gbG9jIE1BWF9MT0MpXG4gICAgICAgICAgICAoZXJyIFwiTG9jYXRpb24gb3V0IG9mIGJvdW5kcy5cIilcbiAgICAgICAgICAgIChpZiAoPiB2YWx1ZSBNQVhfVkFMKVxuICAgICAgICAgICAgICAgIChlcnIgXCJWYWx1ZSBtdXN0IGJlIGxlc3MgdGhhbiAweGZmZmZmZi5cIilcbiAgICAgICAgICAgICAgICAoaWYgKDwgdmFsdWUgTUlOX1ZBTClcbiAgICAgICAgICAgICAgICAgICAgKGVyciBcIlZhbHVlIG11c3QgYmUgZ3JlYXRlciB0aGFuIDB4MDAwMDAwLlwiKVxuICAgICAgICAgICAgICAgICAgICAob2sgKG1hcC1zZXQgcGl4ZWxzIGxvYyB2YWx1ZSkpXG4gICAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgKVxuICAgICAgICApXG4gICAgKVxuKVxuOzsgcmVhZCBvbmx5IGZ1bmN0aW9uc1xuOztcblxuKGRlZmluZS1yZWFkLW9ubHkgKGdldC12YWx1ZS1hdCAobG9jIHVpbnQpKVxuICAgIChpZiAoPj0gbG9jIE1BWF9MT0MpXG4gICAgICAgIChlcnIgXCJPdXQgb2YgYm91bmRzLlwiKVxuICAgICAgICAob2sgKGRlZmF1bHQtdG8gMHhmZmZmZmYgKG1hcC1nZXQ/IHBpeGVscyBsb2MpKSlcbiAgICApXG4pXG5cbihkZWZpbmUtcmVhZC1vbmx5IChnZXQtYWxsKSBcbiAgICAobWFwIGdldC12YWx1ZS1hdCBBTExfTE9DUylcbilcblxuKGRlZmluZS1yZWFkLW9ubHkgKGdlbmVzaXMtdGltZSAoaGVpZ2h0IHVpbnQpKVxuICAgIChnZXQtYmxvY2staW5mbz8gdGltZSBoZWlnaHQpXG4pXG47OyBwcml2YXRlIGZ1bmN0aW9uc1xuOztcbg==", - "clarity_version": 2, - "epoch": 2.1, - "deployer": null + "description": "my description", + "authors": ["test1", "test2"], + "telemetry": false, + "cache_dir": ".cache", + "requirements": [] + }, + "contracts": { + "px": { + "path": "contracts/px.clar", + "clarity_version": 2, + "epoch": 2.1 + } + }, + "repl": { + "analysis": { + "passes": [], + "check_checker": { + "strict": false, + "trusted_sender": false, + "trusted_caller": false, + "callee_filter": false + } + } } - ] + } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d3af9a7..86f3763 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -9,11 +9,12 @@ use super::*; use hyper::{ body, header::{ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN}, - http::HeaderValue, + http::{request::Builder, HeaderValue}, Client, HeaderMap, Method, StatusCode, }; use k8s_openapi::api::core::v1::Namespace; use stacks_devnet_api::{ + api_config::{AuthConfig, ResponderConfig}, config::StacksDevnetConfig, resources::service::{ get_service_from_path_part, get_service_port, get_service_url, ServicePort, @@ -25,6 +26,11 @@ use stacks_devnet_api::{ use test_case::test_case; use tower_test::mock::{self, Handle}; +const VERSION: &str = env!("CARGO_PKG_VERSION"); +const PRJ_NAME: &str = env!("CARGO_PKG_NAME"); +fn get_version_info() -> String { + format!("{{\"version\":\"{PRJ_NAME} v{VERSION}\"}}") +} fn get_template_config() -> StacksDevnetConfig { let file_path = "src/tests/fixtures/stacks-devnet-config.json"; let file = File::open(file_path) @@ -48,10 +54,16 @@ async fn get_k8s_manager() -> (StacksDevnetApiK8sManager, Context) { let logger = hiro_system_kit::log::setup_logger(); let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); let ctx = Context::empty(); - let k8s_manager = StacksDevnetApiK8sManager::default(&ctx).await; + let k8s_manager = StacksDevnetApiK8sManager::new(&ctx).await; (k8s_manager, ctx) } +fn get_request_builder(request_path: &str, method: Method, user_id: &str) -> Builder { + Request::builder() + .uri(request_path) + .method(method) + .header("x-auth-request-user", user_id) +} fn get_random_namespace() -> String { let mut rng = rand::thread_rng(); let random_digit: u64 = rand::Rng::gen(&mut rng); @@ -105,9 +117,10 @@ enum TestBody { #[test_case("/api/v1/networks", Method::POST, Some(TestBody::CreateNetwork), true => using assert_cannot_create_devnet_err; "409 for create network POST request if devnet exists")] #[test_case("/api/v1/network/{namespace}", Method::GET, None, true => using assert_get_network; "200 for network GET request to existing network")] #[test_case("/api/v1/network/{namespace}", Method::HEAD, None, true => is equal_to (StatusCode::OK, "Ok".to_string()); "200 for network HEAD request to existing network")] -#[test_case("/api/v1/network/{namespace}/stacks-node/v2/info/", Method::GET, None, true => using assert_failed_proxy; "proxies requests to downstream nodes")] +#[test_case("/api/v1/network/{namespace}/stacks-blockchain/v2/info/", Method::GET, None, true => using assert_failed_proxy; "proxies requests to downstream nodes")] #[serial_test::serial] #[tokio::test] +#[cfg_attr(not(feature = "k8s_tests"), ignore)] async fn it_responds_to_valid_requests_with_deploy( mut request_path: &str, method: Method, @@ -124,13 +137,14 @@ async fn it_responds_to_valid_requests_with_deploy( let (k8s_manager, ctx) = get_k8s_manager().await; - let request_builder = Request::builder().uri(request_path).method(method); + let request_builder = get_request_builder(request_path, method, &namespace); let _ = k8s_manager.deploy_namespace(&namespace).await.unwrap(); let mut config = get_template_config(); config.namespace = namespace.to_owned(); - let validated_config = config.to_validated_config(ctx.clone()).unwrap(); + let validated_config = config.to_validated_config(&namespace, ctx.clone()).unwrap(); + let user_id = &namespace; let _ = k8s_manager.deploy_devnet(validated_config).await.unwrap(); // short delay to allow assets to start sleep(Duration::new(5, 0)); @@ -145,14 +159,9 @@ async fn it_responds_to_valid_requests_with_deploy( }; let request: Request = request_builder.body(body).unwrap(); - let mut response = handle_request( - request, - k8s_manager.clone(), - ResponderConfig::default(), - ctx, - ) - .await - .unwrap(); + let mut response = handle_request(request, k8s_manager.clone(), ApiConfig::default(), ctx) + .await + .unwrap(); let body = response.body_mut(); let bytes = body::to_bytes(body).await.unwrap().to_vec(); @@ -160,7 +169,7 @@ async fn it_responds_to_valid_requests_with_deploy( let mut status = response.status(); if tear_down { - match k8s_manager.delete_devnet(namespace).await { + match k8s_manager.delete_devnet(namespace, user_id).await { Ok(_) => {} Err(e) => { body_str = e.message; @@ -173,11 +182,14 @@ async fn it_responds_to_valid_requests_with_deploy( } #[test_case("any", Method::OPTIONS, false => is equal_to (StatusCode::OK, "Ok".to_string()); "200 for any OPTIONS request")] +#[test_case("/", Method::GET, false => is equal_to (StatusCode::OK, get_version_info()); "200 for GET /")] +#[test_case("/api/v1/status", Method::GET, false => is equal_to (StatusCode::OK, get_version_info()); "200 for GET /api/v1/status")] #[test_case("/api/v1/network/{namespace}", Method::DELETE, true => using assert_cannot_delete_devnet_err; "409 for network DELETE request to non-existing network")] #[test_case("/api/v1/network/{namespace}", Method::GET, true => using assert_not_all_assets_exist_err; "404 for network GET request to non-existing network")] #[test_case("/api/v1/network/{namespace}", Method::HEAD, true => is equal_to (StatusCode::NOT_FOUND, "not found".to_string()); "404 for network HEAD request to non-existing network")] -#[test_case("/api/v1/network/{namespace}/stacks-node/v2/info/", Method::GET, true => using assert_not_all_assets_exist_err; "404 for proxy requests to downstream nodes of non-existing network")] +#[test_case("/api/v1/network/{namespace}/stacks-blockchain/v2/info/", Method::GET, true => using assert_not_all_assets_exist_err; "404 for proxy requests to downstream nodes of non-existing network")] #[tokio::test] +#[cfg_attr(not(feature = "k8s_tests"), ignore)] async fn it_responds_to_valid_requests( mut request_path: &str, method: Method, @@ -193,21 +205,16 @@ async fn it_responds_to_valid_requests( let (k8s_manager, ctx) = get_k8s_manager().await; - let request_builder = Request::builder().uri(request_path).method(method); + let request_builder = get_request_builder(request_path, method, &namespace); if set_up { let _ = k8s_manager.deploy_namespace(&namespace).await.unwrap(); } let request: Request = request_builder.body(Body::empty()).unwrap(); - let mut response = handle_request( - request, - k8s_manager.clone(), - ResponderConfig::default(), - ctx, - ) - .await - .unwrap(); + let mut response = handle_request(request, k8s_manager.clone(), ApiConfig::default(), ctx) + .await + .unwrap(); let body = response.body_mut(); let bytes = body::to_bytes(body).await.unwrap().to_vec(); @@ -263,51 +270,96 @@ async fn get_mock_k8s_manager() -> (StacksDevnetApiK8sManager, Context) { let logger = hiro_system_kit::log::setup_logger(); let _guard = hiro_system_kit::log::setup_global_logger(logger.clone()); let ctx = Context::empty(); - let k8s_manager = StacksDevnetApiK8sManager::new(mock_service, "default", &ctx).await; + let k8s_manager = StacksDevnetApiK8sManager::from_service(mock_service, "default", &ctx).await; (k8s_manager, ctx) } -#[test_case("/path", Method::GET => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /path")] -#[test_case("/api", Method::GET => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /api")] -#[test_case("/api/v1", Method::GET => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /api/v1")] -#[test_case("/api/v1/network2", Method::GET => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /api/v1/network2")] -#[test_case("/api/v1/network/undeployed", Method::GET => +#[test_case("/path", Method::GET, "some-user" => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /path")] +#[test_case("/api", Method::GET, "some-user" => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /api")] +#[test_case("/api/v1", Method::GET, "some-user" => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /api/v1")] +#[test_case("/api/v1/network2", Method::GET, "some-user" => is equal_to (StatusCode::BAD_REQUEST, "invalid request path".to_string()) ; "400 for invalid requet path /api/v1/network2")] +#[test_case("/api/v1/network/undeployed", Method::GET, "undeployed" => is equal_to (StatusCode::NOT_FOUND, "network undeployed does not exist".to_string()); "404 for undeployed namespace")] -#[test_case("/api/v1/network/500_err", Method::GET => +#[test_case("/api/v1/network/500_err", Method::GET, "500_err" => is equal_to (StatusCode::INTERNAL_SERVER_ERROR, "error getting namespace 500_err: \"\"".to_string()); "forwarded error if fetching namespace returns error")] -#[test_case("/api/v1/network/test", Method::POST => +#[test_case("/api/v1/network/test", Method::POST, "test" => is equal_to (StatusCode::METHOD_NOT_ALLOWED, "can only GET/DELETE/HEAD at provided route".to_string()); "405 for network route with POST request")] -#[test_case("/api/v1/network/test/commands", Method::GET => +#[test_case("/api/v1/network/test/commands", Method::GET, "test" => is equal_to (StatusCode::NOT_FOUND, "commands route in progress".to_string()); "404 for network commands route")] -#[test_case("/api/v1/network/", Method::GET => +#[test_case("/api/v1/network/", Method::GET, "test" => is equal_to (StatusCode::BAD_REQUEST, "no network id provided".to_string()); "400 for missing namespace")] -#[test_case("/api/v1/networks", Method::GET => +#[test_case("/api/v1/networks", Method::GET, "test" => is equal_to (StatusCode::METHOD_NOT_ALLOWED, "network creation must be a POST request".to_string()); "405 for network creation request with GET method")] -#[test_case("/api/v1/networks", Method::DELETE => +#[test_case("/api/v1/networks", Method::DELETE, "test" => is equal_to (StatusCode::METHOD_NOT_ALLOWED, "network creation must be a POST request".to_string()); "405 for network creation request with DELETE method")] -#[test_case("/api/v1/networks", Method::POST => +#[test_case("/api/v1/networks", Method::POST, "test" => is equal_to (StatusCode::BAD_REQUEST, "invalid configuration to create network: EOF while parsing a value at line 1 column 0".to_string()); "400 for network creation request invalid config")] +#[test_case("/api/v1/network/test", Method::GET, "wrong-id" => + is equal_to (StatusCode::BAD_REQUEST, "network id must match authenticated user id".to_string()); "400 for request with non-matching user")] #[tokio::test] async fn it_responds_to_invalid_requests( request_path: &str, method: Method, + user_id: &str, ) -> (StatusCode, String) { let (k8s_manager, ctx) = get_mock_k8s_manager().await; + let request_builder = get_request_builder(request_path, method, &user_id); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request(request, k8s_manager.clone(), ApiConfig::default(), ctx) + .await + .unwrap(); + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + (response.status(), body_str) +} + +#[tokio::test] +async fn it_responds_to_invalid_request_header() { + let (k8s_manager, ctx) = get_mock_k8s_manager().await; + + let request_builder = Request::builder() + .uri("/api/v1/network/test") + .method(Method::GET); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request( + request, + k8s_manager.clone(), + ApiConfig::default(), + ctx.clone(), + ) + .await + .unwrap(); + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + assert_eq!(response.status(), 400); + assert_eq!(body_str, "missing required auth header".to_string()); +} + +#[test_case("/api/v1/network/test", Method::OPTIONS => is equal_to "Ok".to_string())] +#[test_case("/api/v1/status", Method::GET => is equal_to get_version_info() )] +#[test_case("/", Method::GET => is equal_to get_version_info())] +#[tokio::test] +async fn it_ignores_request_header_for_some_requests(request_path: &str, method: Method) -> String { + let (k8s_manager, ctx) = get_mock_k8s_manager().await; + let request_builder = Request::builder().uri(request_path).method(method); let request: Request = request_builder.body(Body::empty()).unwrap(); let mut response = handle_request( request, k8s_manager.clone(), - ResponderConfig::default(), - ctx, + ApiConfig::default(), + ctx.clone(), ) .await .unwrap(); + assert_eq!(response.status(), 200); let body = response.body_mut(); let bytes = body::to_bytes(body).await.unwrap().to_vec(); let body_str = String::from_utf8(bytes).unwrap(); - (response.status(), body_str) + body_str } #[test_case("" => is equal_to PathParts { route: String::new(), ..Default::default() }; "for empty path")] @@ -327,7 +379,7 @@ fn request_paths_are_parsed_correctly(path: &str) -> PathParts { #[tokio::test] async fn request_mutation_should_create_valid_proxy_destination() { - let path = "/api/v1/some-route/some-network/stacks-node/the//remaining///path"; + let path = "/api/v1/some-route/some-network/stacks-blockchain/the//remaining///path"; let path_parts = get_standardized_path_parts(path); let network = path_parts.network.unwrap(); let subroute = path_parts.subroute.unwrap(); @@ -339,15 +391,15 @@ async fn request_mutation_should_create_valid_proxy_destination() { get_service_url(&network, service.clone()), get_service_port(service, ServicePort::RPC).unwrap() ); - let request_builder = Request::builder().uri("/").method("POST"); + let request_builder = get_request_builder("/", Method::POST, "some-network"); let request: Request = request_builder.body(Body::empty()).unwrap(); let request = mutate_request_for_proxy(request, &forward_url, &remainder); let actual_url = request.uri().to_string(); let expected = format!( "http://{}.{}.svc.cluster.local:{}/{}", - StacksDevnetService::StacksNode, + StacksDevnetService::StacksBlockchain, network, - get_service_port(StacksDevnetService::StacksNode, ServicePort::RPC).unwrap(), + get_service_port(StacksDevnetService::StacksBlockchain, ServicePort::RPC).unwrap(), &remainder ); assert_eq!(actual_url, expected); @@ -358,10 +410,11 @@ fn responder_allows_configuring_allowed_origins() { let config = ResponderConfig { allowed_origins: Some(vec!["*".to_string()]), allowed_methods: Some(vec!["GET".to_string()]), + allowed_headers: None, }; let mut headers = HeaderMap::new(); headers.append("ORIGIN", HeaderValue::from_str("example.com").unwrap()); - let responder = Responder::new(config, headers).unwrap(); + let responder = Responder::new(config, headers, Context::empty()).unwrap(); let builder = responder.response_builder(); let built_headers = builder.headers_ref().unwrap(); assert_eq!(built_headers.get(ACCESS_CONTROL_ALLOW_ORIGIN).unwrap(), "*"); @@ -371,11 +424,50 @@ fn responder_allows_configuring_allowed_origins() { ); } +#[serial_test::serial] +#[tokio::test] +#[cfg_attr(not(feature = "k8s_tests"), ignore)] +async fn namespace_prefix_config_prepends_header() { + let (k8s_manager, ctx) = get_k8s_manager().await; + + // using the ApiConfig's `namespace_prefix` field will add the prefix + // before the `user_id` as the authenticated user, which should match the request path + let namespace = &get_random_namespace(); + let _ = k8s_manager.deploy_namespace(&namespace).await.unwrap(); + + let (namespace_prefix, user_id) = namespace.split_at(4); + let api_config = ApiConfig { + auth_config: AuthConfig { + namespace_prefix: Some(namespace_prefix.to_string()), + ..Default::default() + }, + ..Default::default() + }; + + let request_builder = get_request_builder( + &format!("/api/v1/network/{namespace}"), + Method::HEAD, + user_id, + ); + let request: Request = request_builder.body(Body::empty()).unwrap(); + let mut response = handle_request(request, k8s_manager.clone(), api_config, ctx.clone()) + .await + .unwrap(); + + let body = response.body_mut(); + let bytes = body::to_bytes(body).await.unwrap().to_vec(); + let body_str = String::from_utf8(bytes).unwrap(); + assert_eq!(response.status(), 404); + assert_eq!(body_str, "not found"); +} + #[test] -fn responder_config_reads_from_file() { - let config = ResponderConfig::from_path("Config.toml"); - assert!(config.allowed_methods.is_some()); - assert!(config.allowed_origins.is_some()); +fn config_reads_from_file() { + let config = ApiConfig::from_path("Config.toml"); + assert!(config.http_response_config.allowed_methods.is_some()); + assert!(config.http_response_config.allowed_origins.is_some()); + assert!(config.auth_config.auth_header.is_some()); + assert!(config.auth_config.namespace_prefix.is_some()); } #[tokio::test] @@ -385,11 +477,8 @@ async fn main_starts_server() { }); sleep(Duration::new(1, 0)); let client = Client::new(); - let request_builder = Request::builder() - .uri("http://localhost:8477") - .method(Method::OPTIONS) - .body(Body::empty()) - .unwrap(); - let response = client.request(request_builder).await; + let request_builder = get_request_builder("http://localhost:8477", Method::OPTIONS, "user-id"); + let request = request_builder.body(Body::empty()).unwrap(); + let response = client.request(request).await; assert_eq!(response.unwrap().status(), 200); } diff --git a/templates/bitcoind-chain-coordinator-pod.template.yaml b/templates/bitcoind-chain-coordinator-pod.template.yaml deleted file mode 100644 index 718a149..0000000 --- a/templates/bitcoind-chain-coordinator-pod.template.yaml +++ /dev/null @@ -1,75 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - name: bitcoind-chain-coordinator - name: bitcoind-chain-coordinator - namespace: "{namespace}" -spec: - containers: - - command: - - /usr/local/bin/bitcoind - - -conf=/etc/bitcoin/bitcoin.conf - - -nodebuglogfile - - -pid=/run/bitcoind.pid - image: quay.io/hirosystems/bitcoind:devnet-v3 - imagePullPolicy: IfNotPresent - name: bitcoind-container - ports: - - containerPort: 18444 - name: p2p - protocol: TCP - - containerPort: 18443 - name: rpc - protocol: TCP - volumeMounts: - - mountPath: /etc/bitcoin - name: bitcoind-conf-volume - readOnly: true - - command: - - ./stacks-network - - --namespace=$(NAMESPACE) - - --manifest-path=/etc/stacks-network/project/Clarinet.toml - - --deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml - - --project-root-path=/etc/stacks-network/project/ - env: - - name: NAMESPACE - valueFrom: - configMapKeyRef: - name: namespace-conf - key: NAMESPACE - image: quay.io/hirosystems/stacks-network-orchestrator:latest - imagePullPolicy: Always - name: chain-coordinator-container - ports: - - containerPort: 20445 - name: coordinator-in - protocol: TCP - - containerPort: 20446 - name: coordinator-con - protocol: TCP - volumeMounts: - - mountPath: /etc/stacks-network/project - name: project-manifest-conf-volume - - mountPath: /etc/stacks-network/project/settings - name: devnet-conf-volume - - mountPath: /etc/stacks-network/project/deployments - name: deployment-plan-conf-volume - - mountPath: /etc/stacks-network/project/contracts - name: project-dir-conf-volume - volumes: - - configMap: - name: bitcoind-conf - name: bitcoind-conf-volume - - configMap: - name: project-manifest-conf - name: project-manifest-conf-volume - - configMap: - name: devnet-conf - name: devnet-conf-volume - - configMap: - name: deployment-plan-conf - name: deployment-plan-conf-volume - - configMap: - name: project-dir-conf - name: project-dir-conf-volume diff --git a/templates/bitcoind-chain-coordinator-service.template.yaml b/templates/bitcoind-chain-coordinator-service.template.yaml deleted file mode 100644 index 961474d..0000000 --- a/templates/bitcoind-chain-coordinator-service.template.yaml +++ /dev/null @@ -1,25 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: bitcoind-chain-coordinator-service - namespace: "{namespace}" -spec: - ports: - - name: p2p - port: 18444 - protocol: TCP - targetPort: 18444 - - name: rpc - port: 18443 - protocol: TCP - targetPort: 18443 - - name: coordinator-in - port: 20445 - protocol: TCP - targetPort: 20445 - - name: coordinator-con - port: 20446 - protocol: TCP - targetPort: 20446 - selector: - name: bitcoind-chain-coordinator diff --git a/templates/bitcoind-configmap.template.yaml b/templates/bitcoind-configmap.template.yaml deleted file mode 100644 index e3a0b84..0000000 --- a/templates/bitcoind-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - bitcoin.conf: "{data}" -kind: ConfigMap -metadata: - name: bitcoind-conf - namespace: "{namespace}" diff --git a/templates/chain-coord-deployment-plan-configmap.template.yaml b/templates/chain-coord-deployment-plan-configmap.template.yaml deleted file mode 100644 index 6905c73..0000000 --- a/templates/chain-coord-deployment-plan-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - key: "{value}" -kind: ConfigMap -metadata: - name: deployment-plan-conf - namespace: "{namespace}" diff --git a/templates/chain-coord-devnet-configmap.template.yaml b/templates/chain-coord-devnet-configmap.template.yaml deleted file mode 100644 index 0e58907..0000000 --- a/templates/chain-coord-devnet-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - key: "{value}" -kind: ConfigMap -metadata: - name: devnet-conf - namespace: "{namespace}" diff --git a/templates/chain-coord-namespace-configmap.template.yaml b/templates/chain-coord-namespace-configmap.template.yaml deleted file mode 100644 index 15f97bc..0000000 --- a/templates/chain-coord-namespace-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - key: "{value}" -kind: ConfigMap -metadata: - name: namespace-conf - namespace: "{namespace}" diff --git a/templates/chain-coord-project-dir-configmap.template.yaml b/templates/chain-coord-project-dir-configmap.template.yaml deleted file mode 100644 index c62df2c..0000000 --- a/templates/chain-coord-project-dir-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - key: "{value}" -kind: ConfigMap -metadata: - name: project-dir-conf - namespace: "{namespace}" diff --git a/templates/chain-coord-project-manifest-configmap.template.yaml b/templates/chain-coord-project-manifest-configmap.template.yaml deleted file mode 100644 index 1a70f6b..0000000 --- a/templates/chain-coord-project-manifest-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - key: "{value}" -kind: ConfigMap -metadata: - name: project-manifest-conf - namespace: "{namespace}" diff --git a/templates/ci/stacks-devnet-api.template.yaml b/templates/ci/stacks-devnet-api.template.yaml new file mode 100644 index 0000000..1c1b679 --- /dev/null +++ b/templates/ci/stacks-devnet-api.template.yaml @@ -0,0 +1,81 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: stacks-devnet-api + namespace: devnet + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: stacks-devnet-api +rules: + - apiGroups: [""] + resources: ["pods", "pods/status", "services", "configmaps", "persistentvolumeclaims"] + verbs: ["get", "delete", "create", "list", "deletecollection"] + - apiGroups: ["apps"] + resources: ["deployments", "statefulsets"] + verbs: ["get", "delete", "create", "list"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: stacks-devnet-api +subjects: + - kind: ServiceAccount + name: stacks-devnet-api + namespace: devnet +roleRef: + kind: ClusterRole + name: stacks-devnet-api + apiGroup: rbac.authorization.k8s.io + +--- + +apiVersion: v1 +kind: Pod +metadata: + labels: + name: stacks-devnet-api + name: stacks-devnet-api + namespace: devnet +spec: + serviceAccountName: stacks-devnet-api + containers: + - command: ["stacks-devnet-api"] + name: stacks-devnet-api + image: hirosystems/stacks-devnet-api:ci + imagePullPolicy: Never + ports: + - containerPort: 8477 + name: api + protocol: TCP + volumeMounts: + - name: config-volume + mountPath: /etc/config + volumes: + - name: config-volume + configMap: + name: stacks-devnet-api + +--- +apiVersion: v1 +kind: Service +metadata: + name: stacks-devnet-api + namespace: devnet +spec: + ports: + - name: api + port: 8477 + protocol: TCP + targetPort: 8477 + nodePort: 30000 + selector: + name: stacks-devnet-api + type: NodePort + diff --git a/templates/configmaps/bitcoind.template.yaml b/templates/configmaps/bitcoind.template.yaml new file mode 100644 index 0000000..d2fc4ac --- /dev/null +++ b/templates/configmaps/bitcoind.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + bitcoin.conf: "{data}" +kind: ConfigMap +metadata: + name: bitcoind + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: bitcoind + app.kubernetes.io/component: bitcoind + argocd.argoproj.io/instance: platform-user-resources.platform diff --git a/templates/configmaps/chain-coord-deployment-plan.template.yaml b/templates/configmaps/chain-coord-deployment-plan.template.yaml new file mode 100644 index 0000000..47810d3 --- /dev/null +++ b/templates/configmaps/chain-coord-deployment-plan.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: deployment-plan + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: deployment-plan + app.kubernetes.io/component: deployment-plan + argocd.argoproj.io/instance: platform-user-resources.platform diff --git a/templates/configmaps/chain-coord-devnet.template.yaml b/templates/configmaps/chain-coord-devnet.template.yaml new file mode 100644 index 0000000..63f1e56 --- /dev/null +++ b/templates/configmaps/chain-coord-devnet.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: devnet + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: devnet + app.kubernetes.io/component: devnet + argocd.argoproj.io/instance: platform-user-resources.platform diff --git a/templates/configmaps/chain-coord-project-dir.template.yaml b/templates/configmaps/chain-coord-project-dir.template.yaml new file mode 100644 index 0000000..dd78f61 --- /dev/null +++ b/templates/configmaps/chain-coord-project-dir.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: project-dir + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: project-dir + app.kubernetes.io/component: project-dir + argocd.argoproj.io/instance: platform-user-resources.platform diff --git a/templates/configmaps/chain-coord-project-manifest.template.yaml b/templates/configmaps/chain-coord-project-manifest.template.yaml new file mode 100644 index 0000000..f41d0e9 --- /dev/null +++ b/templates/configmaps/chain-coord-project-manifest.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: project-manifest + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: project-manifest + app.kubernetes.io/component: project-manifest + argocd.argoproj.io/instance: platform-user-resources.platform diff --git a/templates/configmaps/stacks-blockchain-api-pg.template.yaml b/templates/configmaps/stacks-blockchain-api-pg.template.yaml new file mode 100644 index 0000000..1cdd8ca --- /dev/null +++ b/templates/configmaps/stacks-blockchain-api-pg.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: stacks-blockchain-api-pg + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain-api-pg + app.kubernetes.io/component: stacks-blockchain-api-pg + argocd.argoproj.io/instance: platform-user-resources.platform diff --git a/templates/configmaps/stacks-blockchain-api.template.yaml b/templates/configmaps/stacks-blockchain-api.template.yaml new file mode 100644 index 0000000..162af14 --- /dev/null +++ b/templates/configmaps/stacks-blockchain-api.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + key: "{value}" +kind: ConfigMap +metadata: + name: stacks-blockchain-api + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain-api + app.kubernetes.io/component: stacks-blockchain-api + argocd.argoproj.io/instance: platform-user-resources.platform diff --git a/templates/configmaps/stacks-blockchain.template.yaml b/templates/configmaps/stacks-blockchain.template.yaml new file mode 100644 index 0000000..a4c80b1 --- /dev/null +++ b/templates/configmaps/stacks-blockchain.template.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +data: + Stacks.toml: "{data}" +kind: ConfigMap +metadata: + name: stacks-blockchain + namespace: "{namespace}" + labels: + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain + app.kubernetes.io/component: stacks-blockchain + argocd.argoproj.io/instance: platform-user-resources.platform \ No newline at end of file diff --git a/templates/deployments/bitcoind-chain-coordinator.template.yaml b/templates/deployments/bitcoind-chain-coordinator.template.yaml new file mode 100644 index 0000000..2f1ac1a --- /dev/null +++ b/templates/deployments/bitcoind-chain-coordinator.template.yaml @@ -0,0 +1,114 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: bitcoind-chain-coordinator + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: bitcoind-chain-coordinator + argocd.argoproj.io/instance: platform-user-resources.platform + name: bitcoind-chain-coordinator + namespace: "{namespace}" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: bitcoind-chain-coordinator + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: bitcoind-chain-coordinator + template: + metadata: + labels: + app.kubernetes.io/component: bitcoind-chain-coordinator + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: bitcoind-chain-coordinator + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: cloud.google.com/gke-preemptible + operator: DoesNotExist + containers: + - command: + - /usr/local/bin/bitcoind + - -conf=/etc/bitcoin/bitcoin.conf + - -nodebuglogfile + - -pid=/run/bitcoind.pid + image: quay.io/hirosystems/bitcoind:devnet-v3 + imagePullPolicy: IfNotPresent + name: bitcoind + ports: + - containerPort: 18444 + name: p2p + protocol: TCP + - containerPort: 18443 + name: rpc + protocol: TCP + volumeMounts: + - mountPath: /etc/bitcoin + name: bitcoind + readOnly: true + resources: + requests: + cpu: 250m + memory: 750Mi # todo: revisit allocation + limits: + memory: 750Mi # todo: revisit allocation + - command: + - ./stacks-network + - --namespace=$(NAMESPACE) + - --manifest-path=/etc/stacks-network/project/Clarinet.toml + - --network-manifest-path=/etc/stacks-network/project/settings/Devnet.toml + - --deployment-plan-path=/etc/stacks-network/project/deployments/default.devnet-plan.yaml + - --project-root-path=/etc/stacks-network/project/ + env: + - name: NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + image: hirosystems/stacks-network-orchestrator@sha256:e9c88e46adb10deba74e29883533de747a2200665f919d1708a5ea0f2638d32a + imagePullPolicy: IfNotPresent + name: chain-coordinator + ports: + - containerPort: 20445 + name: coordinator-in + protocol: TCP + - containerPort: 20446 + name: coordinator-con + protocol: TCP + volumeMounts: + - mountPath: /etc/stacks-network/project + name: project-manifest + - mountPath: /etc/stacks-network/project/settings + name: devnet + - mountPath: /etc/stacks-network/project/deployments + name: deployment-plan + - mountPath: /etc/stacks-network/project/contracts + name: project-dir + resources: + requests: + cpu: 250m + memory: 256Mi + limits: + memory: 256Mi + volumes: + - configMap: + name: bitcoind + name: bitcoind + - configMap: + name: project-manifest + name: project-manifest + - configMap: + name: devnet + name: devnet + - configMap: + name: deployment-plan + name: deployment-plan + - configMap: + name: project-dir + name: project-dir \ No newline at end of file diff --git a/templates/deployments/stacks-blockchain.template.yaml b/templates/deployments/stacks-blockchain.template.yaml new file mode 100644 index 0000000..824c1a2 --- /dev/null +++ b/templates/deployments/stacks-blockchain.template.yaml @@ -0,0 +1,70 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/component: stacks-blockchain + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain + argocd.argoproj.io/instance: platform-user-resources.platform + name: stacks-blockchain + namespace: "{namespace}" +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: stacks-blockchain + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain + template: + metadata: + labels: + app.kubernetes.io/component: stacks-blockchain + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: cloud.google.com/gke-preemptible + operator: DoesNotExist + containers: + - command: + - stacks-node + - start + - --config=/src/stacks-blockchain/Stacks.toml + env: + - name: STACKS_LOG_PP + value: "1" + - name: BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE + value: "1" + - name: STACKS_LOG_DEBUG + value: "0" + image: quay.io/hirosystems/stacks-node:devnet-v3 + imagePullPolicy: IfNotPresent + name: stacks-blockchain + ports: + - containerPort: 20444 + name: p2p + protocol: TCP + - containerPort: 20443 + name: rpc + protocol: TCP + volumeMounts: + - mountPath: /src/stacks-blockchain + name: stacks-blockchain + readOnly: true + resources: + requests: + cpu: 250m + memory: 750Mi # todo: revisit allocation + limits: + memory: 750Mi # todo: revisit allocation + volumes: + - configMap: + name: stacks-blockchain + name: stacks-blockchain \ No newline at end of file diff --git a/templates/initial-config/storage-class.yaml b/templates/initial-config/storage-class.yaml index b7a6d1c..cdb3e09 100644 --- a/templates/initial-config/storage-class.yaml +++ b/templates/initial-config/storage-class.yaml @@ -1,7 +1,7 @@ apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: - name: devnet-storage-class + name: premium-rwo annotations: openebs.io/cas-type: local cas.openebs.io/config: | diff --git a/templates/services/bitcoind-chain-coordinator.template.yaml b/templates/services/bitcoind-chain-coordinator.template.yaml new file mode 100644 index 0000000..e0e3c45 --- /dev/null +++ b/templates/services/bitcoind-chain-coordinator.template.yaml @@ -0,0 +1,34 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: bitcoind-chain-coordinator + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: bitcoind-chain-coordinator + argocd.argoproj.io/instance: platform-user-resources.platform + name: bitcoind-chain-coordinator + namespace: "{namespace}" +spec: + ports: + - name: tcp-p2p + port: 18444 + protocol: TCP + targetPort: 18444 + - name: tcp-rpc + port: 18443 + protocol: TCP + targetPort: 18443 + - name: http-coordinator-in + port: 20445 + protocol: TCP + targetPort: 20445 + - name: http-coordinator-con + port: 20446 + protocol: TCP + targetPort: 20446 + selector: + app.kubernetes.io/component: bitcoind-chain-coordinator + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: bitcoind-chain-coordinator diff --git a/templates/services/stacks-blockchain-api.template.yaml b/templates/services/stacks-blockchain-api.template.yaml new file mode 100644 index 0000000..d53e448 --- /dev/null +++ b/templates/services/stacks-blockchain-api.template.yaml @@ -0,0 +1,30 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: stacks-blockchain-api + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain-api + argocd.argoproj.io/instance: platform-user-resources.platform + name: stacks-blockchain-api + namespace: "{namespace}" +spec: + ports: + - name: http-api + port: 3999 + protocol: TCP + targetPort: 3999 + - name: tcp-postgres + port: 5432 + protocol: TCP + targetPort: 5432 + - name: tcp-eventport + port: 3700 + protocol: TCP + targetPort: 3700 + selector: + app.kubernetes.io/component: stacks-blockchain-api + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain-api diff --git a/templates/services/stacks-blockchain.template.yaml b/templates/services/stacks-blockchain.template.yaml new file mode 100644 index 0000000..4391fea --- /dev/null +++ b/templates/services/stacks-blockchain.template.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/component: stacks-blockchain + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain + argocd.argoproj.io/instance: platform-user-resources.platform + name: stacks-blockchain + namespace: "{namespace}" +spec: + ports: + - name: tcp-p2p + port: 20444 + protocol: TCP + - name: http-rpc + port: 20443 + protocol: TCP + selector: + app.kubernetes.io/component: stacks-blockchain + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain diff --git a/templates/stacks-api-configmap.template.yaml b/templates/stacks-api-configmap.template.yaml deleted file mode 100644 index 12d6395..0000000 --- a/templates/stacks-api-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - key: "{value}" -kind: ConfigMap -metadata: - name: stacks-api-conf - namespace: "{namespace}" diff --git a/templates/stacks-api-pod.template.yaml b/templates/stacks-api-pod.template.yaml deleted file mode 100644 index 3d27862..0000000 --- a/templates/stacks-api-pod.template.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - name: stacks-api - name: stacks-api - namespace: "{namespace}" -spec: - containers: - - name: stacks-api-container - envFrom: - - configMapRef: - name: stacks-api-conf - optional: false - image: hirosystems/stacks-blockchain-api - imagePullPolicy: Never - ports: - - containerPort: 3999 - name: api - protocol: TCP - - containerPort: 3700 - name: eventport - protocol: TCP - - name: stacks-api-postgres - envFrom: - - configMapRef: - name: stacks-api-postgres-conf - optional: false - image: postgres:14 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 5432 - name: postgres - protocol: TCP - volumeMounts: - - mountPath: /var/lib/postgresql/data - name: stacks-api-conf-volume - volumes: - - name: stacks-api-conf-volume - persistentVolumeClaim: - claimName: stacks-api-pvc \ No newline at end of file diff --git a/templates/stacks-api-postgres-configmap.template.yaml b/templates/stacks-api-postgres-configmap.template.yaml deleted file mode 100644 index 319203c..0000000 --- a/templates/stacks-api-postgres-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - key: "{value}" -kind: ConfigMap -metadata: - name: stacks-api-postgres-conf - namespace: "{namespace}" diff --git a/templates/stacks-api-pvc.template.yaml b/templates/stacks-api-pvc.template.yaml deleted file mode 100644 index cb90749..0000000 --- a/templates/stacks-api-pvc.template.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: stacks-api-pvc - namespace: "{namespace}" -spec: - accessModes: - - ReadWriteOnce - resources: - limits: - storage: 750Mi - requests: - storage: 500Mi - storageClassName: devnet-storage-class - volumeMode: Filesystem diff --git a/templates/stacks-api-service.template.yaml b/templates/stacks-api-service.template.yaml deleted file mode 100644 index 3a23341..0000000 --- a/templates/stacks-api-service.template.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: stacks-api-service - namespace: "{namespace}" -spec: - ports: - - name: api - port: 3999 - protocol: TCP - targetPort: 3999 - - name: postgres - port: 5432 - protocol: TCP - targetPort: 5432 - - name: eventport - port: 3700 - protocol: TCP - targetPort: 3700 - selector: - name: stacks-api diff --git a/templates/stacks-devnet-api.template.yaml b/templates/stacks-devnet-api.template.yaml index 05675dc..2f2727d 100644 --- a/templates/stacks-devnet-api.template.yaml +++ b/templates/stacks-devnet-api.template.yaml @@ -1,32 +1,38 @@ apiVersion: v1 kind: ServiceAccount metadata: - name: stacks-devnet-api-service-account + name: stacks-devnet-api namespace: devnet --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: - name: stacks-devnet-api-service-account + name: stacks-devnet-api rules: - apiGroups: [""] - # TODO: production version should not be able to create/delete namespaces (only get) - resources: ["pods", "pods/status", "services", "configmaps", "persistentvolumeclaims", "namespaces"] - verbs: ["get", "delete", "create"] + resources: ["pods", "pods/status", "services", "configmaps", "persistentvolumeclaims"] + verbs: ["get", "delete", "create", "list", "deletecollection"] + - apiGroups: ["apps"] + resources: ["deployments", "statefulsets"] + verbs: ["get", "delete", "create", "list"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["get"] + --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: - name: stacks-devnet-api-service-account + name: stacks-devnet-api subjects: - kind: ServiceAccount - name: stacks-devnet-api-service-account + name: stacks-devnet-api namespace: devnet roleRef: kind: ClusterRole - name: stacks-devnet-api-service-account + name: stacks-devnet-api apiGroup: rbac.authorization.k8s.io --- @@ -39,13 +45,12 @@ metadata: name: stacks-devnet-api namespace: devnet spec: - serviceAccountName: stacks-devnet-api-service-account + serviceAccountName: stacks-devnet-api containers: - - command: - - ./stacks-devnet-api - name: stacks-devnet-api-container - image: quay.io/hirosystems/stacks-devnet-api:latest - imagePullPolicy: Always + - command: ["stacks-devnet-api"] + name: stacks-devnet-api + image: hirosystems/stacks-devnet-api:latest + imagePullPolicy: IfNotPresent ports: - containerPort: 8477 name: api @@ -56,13 +61,13 @@ spec: volumes: - name: config-volume configMap: - name: stacks-devnet-api-conf + name: stacks-devnet-api --- apiVersion: v1 kind: Service metadata: - name: stacks-devnet-api-service + name: stacks-devnet-api namespace: devnet spec: ports: diff --git a/templates/stacks-node-configmap.template.yaml b/templates/stacks-node-configmap.template.yaml deleted file mode 100644 index 5ca101a..0000000 --- a/templates/stacks-node-configmap.template.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -data: - Stacks.toml: "{data}" -kind: ConfigMap -metadata: - name: stacks-node-conf - namespace: "{namespace}" diff --git a/templates/stacks-node-pod.template.yaml b/templates/stacks-node-pod.template.yaml deleted file mode 100644 index e49706f..0000000 --- a/templates/stacks-node-pod.template.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - labels: - name: stacks-node - name: stacks-node - namespace: "{namespace}" -spec: - containers: - - command: - - stacks-node - - start - - --config=/src/stacks-node/Stacks.toml - env: - - name: STACKS_LOG_PP - value: "1" - - name: BLOCKSTACK_USE_TEST_GENESIS_CHAINSTATE - value: "1" - - name: STACKS_LOG_DEBUG - value: "0" - image: quay.io/hirosystems/stacks-node:devnet-v3 - imagePullPolicy: IfNotPresent - name: stacks-node-container - ports: - - containerPort: 20444 - name: p2p - protocol: TCP - - containerPort: 20443 - name: rpc - protocol: TCP - volumeMounts: - - mountPath: /src/stacks-node - name: stacks-node-conf-volume - readOnly: true - volumes: - - configMap: - name: stacks-node-conf - name: stacks-node-conf-volume \ No newline at end of file diff --git a/templates/stacks-node-service.template.yaml b/templates/stacks-node-service.template.yaml deleted file mode 100644 index 190b98c..0000000 --- a/templates/stacks-node-service.template.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: stacks-node-service - namespace: "{namespace}" -spec: - ports: - - name: p2p - port: 20444 - protocol: TCP - - name: rpc - port: 20443 - protocol: TCP - selector: - name: stacks-node diff --git a/templates/stateful-sets/stacks-blockchain-api.template.yaml b/templates/stateful-sets/stacks-blockchain-api.template.yaml new file mode 100644 index 0000000..cb0ba35 --- /dev/null +++ b/templates/stateful-sets/stacks-blockchain-api.template.yaml @@ -0,0 +1,87 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + labels: + app.kubernetes.io/component: stacks-blockchain-api + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain-api + argocd.argoproj.io/instance: platform-user-resources.platform + name: stacks-blockchain-api + namespace: "{namespace}" +spec: + replicas: 1 + serviceName: stacks-blockchain-api + selector: + matchLabels: + app.kubernetes.io/component: stacks-blockchain-api + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain-api + template: + metadata: + labels: + app.kubernetes.io/component: stacks-blockchain-api + app.kubernetes.io/instance: "{user_id}" + app.kubernetes.io/managed-by: stacks-devnet-api + app.kubernetes.io/name: stacks-blockchain-api + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: cloud.google.com/gke-preemptible + operator: DoesNotExist + containers: + - name: stacks-blockchain-api + envFrom: + - configMapRef: + name: stacks-blockchain-api + optional: false + image: hirosystems/stacks-blockchain-api + imagePullPolicy: IfNotPresent + ports: + - containerPort: 3999 + name: api + protocol: TCP + - containerPort: 3700 + name: eventport + protocol: TCP + resources: + requests: + cpu: 250m + memory: 750Mi # todo: revisit allocation + limits: + memory: 750Mi # todo: revisit allocation + - name: postgres + envFrom: + - configMapRef: + name: stacks-blockchain-api-pg + optional: false + image: postgres:15 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 5432 + name: postgres + protocol: TCP + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: pg + subPath: postgres + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + memory: 512Mi + volumeClaimTemplates: + - metadata: + name: pg + spec: + accessModes: + - ReadWriteOnce + storageClassName: premium-rwo + resources: + requests: + storage: 1Gi \ No newline at end of file