From da34fad0be056107b4e882ce138e22787d4fa03a Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Thu, 15 Jun 2023 22:13:21 +0530 Subject: [PATCH] Add starship integration running vanila stride and simple e2e tests --- .github/workflows/starship.yaml | 43 +++++++ package.json | 26 +++- starship/Makefile | 82 +++++++++++++ starship/README.md | 75 ++++++++++++ starship/__tests__/gov.test.js | 185 +++++++++++++++++++++++++++++ starship/__tests__/setup.test.js | 24 ++++ starship/__tests__/staking.test.js | 125 +++++++++++++++++++ starship/__tests__/token.test.js | 74 ++++++++++++ starship/configs/config.yaml | 34 ++++++ starship/configs/local-config.yaml | 94 +++++++++++++++ starship/scripts/dev-setup.sh | 65 ++++++++++ starship/scripts/port-forward.sh | 84 +++++++++++++ starship/src/config.js | 116 ++++++++++++++++++ starship/src/index.js | 2 + starship/src/utils.js | 29 +++++ 15 files changed, 1055 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/starship.yaml create mode 100644 starship/Makefile create mode 100644 starship/README.md create mode 100644 starship/__tests__/gov.test.js create mode 100644 starship/__tests__/setup.test.js create mode 100644 starship/__tests__/staking.test.js create mode 100644 starship/__tests__/token.test.js create mode 100644 starship/configs/config.yaml create mode 100644 starship/configs/local-config.yaml create mode 100644 starship/scripts/dev-setup.sh create mode 100644 starship/scripts/port-forward.sh create mode 100644 starship/src/config.js create mode 100644 starship/src/index.js create mode 100644 starship/src/utils.js diff --git a/.github/workflows/starship.yaml b/.github/workflows/starship.yaml new file mode 100644 index 00000000..dcf1e78f --- /dev/null +++ b/.github/workflows/starship.yaml @@ -0,0 +1,43 @@ +name: Starship E2E tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + + workflow_dispatch: + +jobs: + e2e-tests: + runs-on: ubuntu-latest + steps: + - name: checkout 🛎️ + uses: actions/checkout@v2.3.1 + - name: node + uses: actions/setup-node@v3 + with: + node-version: 18.4.0 + - name: deps + run: yarn + - name: bootstrap + run: yarn bootstrap + - name: build + run: yarn build + # Starship Infra setup + # - Create a local kind cluster + # - Creates a new namespace based on the name + # - Spins up the infra with the given config file + # - Waits till all nodes are running (timeout 30m) + # - Port forward all ports to localhost for next steps to connect + - name: infra + id: starship-action + uses: cosmology-tech/starship-action@0.2.12 + with: + values: starship/configs/local-config.yaml + port-forward: true + version: 0.1.34 + - name: e2e + run: yarn run e2e:test diff --git a/package.json b/package.json index f927ec70..e9093599 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,17 @@ "dev": "cross-env NODE_ENV=development babel-node src/index --extensions \".tsx,.ts,.js\"", "watch": "cross-env NODE_ENV=development babel-watch src/index --extensions \".tsx,.ts,.js\"", "lint": "eslint src --fix", - "test": "jest", - "test:watch": "jest --watch", - "test:debug": "node --inspect node_modules/.bin/jest --runInBand" + "test": "jest --testPathIgnorePatterns=starship/", + "test:watch": "jest --watch --testPathIgnorePatterns=starship/", + "test:debug": "node --inspect node_modules/.bin/jest --runInBand --testPathIgnorePatterns=starship/", + "e2e": "npm run e2e:test", + "e2e:deps": "cd tests && make setup", + "e2e:kind": "cd tests && make setup-kind", + "e2e:start": "cd tests && make start", + "e2e:test": "jest --testPathPattern=starship/ --verbose --bail", + "e2e:debug": "jest --testPathPattern=starship/ --runInBand --verbose --bail", + "e2e:stop": "cd tests && make stop", + "e2e:clean": "cd tests && make stop clean" }, "publishConfig": { "access": "public" @@ -59,23 +67,35 @@ "@babel/plugin-transform-runtime": "7.18.10", "@babel/preset-env": "7.18.10", "@babel/preset-typescript": "^7.18.6", + "@chain-registry/client": "1.11.0", + "@confio/relayer": "0.7.0", + "@cosmjs/cosmwasm-stargate": "0.29.4", + "@cosmjs/crypto": "0.29.4", "@osmonauts/telescope": "^0.35.0", "@protobufs/cosmos": "^0.0.11", + "@protobufs/cosmos_proto": "^0.0.10", + "@protobufs/cosmwasm": "^0.0.11", "@protobufs/ibc": "^0.0.11", "@types/jest": "^28.1.6", "babel-core": "7.0.0-bridge.0", "babel-jest": "28.1.3", "babel-watch": "^7.0.0", + "bignumber.js": "9.1.0", + "cosmjs-types": "0.5.2", "cross-env": "^7.0.2", "eslint": "8.21.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "jest": "^28.1.3", "jest-in-case": "^1.0.2", + "js-yaml": "4.1.0", "long": "^5.2.0", + "node-fetch": "^2.6.9", + "path": "^0.12.7", "prettier": "^2.7.1", "regenerator-runtime": "^0.13.7", "rimraf": "^3.0.2", + "sinon": "14.0.2", "ts-jest": "^28.0.7", "typescript": "^4.7.4" }, diff --git a/starship/Makefile b/starship/Makefile new file mode 100644 index 00000000..09b7bcdd --- /dev/null +++ b/starship/Makefile @@ -0,0 +1,82 @@ +HELM_NAME = stridejs +HELM_FILE = configs/config.yaml +HELM_REPO = starship +HELM_CHART = devnet +HELM_VERSION = v0.1.34 + +############################################################################### +### All commands ### +############################################################################### + +.PHONY: setup +setup: check setup-helm + +.PHONY: start +start: install port-forward + +.PHONY: test +test: + jest --testPathPattern=../starship --runInBand --verbose --bail + +.PHONY: stop +stop: stop-forward delete + +.PHONY: clean +clean: stop clean-kind + +############################################################################### +### Helm commands ### +############################################################################### + +.PHONY: setup-helm +setup-helm: + helm repo add $(HELM_REPO) https://cosmology-tech.github.io/starship/ + helm repo update + helm search repo $(HELM_REPO)/$(HELM_CHART) --version $(HELM_VERSION) + +.PHONY: install +install: + @echo "Installing the helm chart. This is going to take a while....." + @echo "You can check the status with \"kubectl get pods\", run in another terminal please" + helm install -f $(HELM_FILE) $(HELM_NAME) $(HELM_REPO)/$(HELM_CHART) --version $(HELM_VERSION) --wait --timeout 20m + +.PHONY: upgrade +upgrade: + helm upgrade --debug -f $(HELM_FILE) $(HELM_NAME) $(HELM_REPO)/$(HELM_CHART) --version $(HELM_VERSION) + +.PHONY: debug +debug: + helm install --dry-run --debug -f $(HELM_FILE) $(HELM_NAME) $(HELM_REPO)/$(HELM_CHART) + +.PHONY: delete +delete: + -helm delete $(HELM_NAME) + +############################################################################### +### Port forward ### +############################################################################### + +.PHONY: port-forward +port-forward: + bash $(CURDIR)/scripts/port-forward.sh --config=$(HELM_FILE) + +.PHONY: stop-forward +stop-forward: + -pkill -f "port-forward" + +############################################################################### +### Local Kind Setup ### +############################################################################### +KIND_CLUSTER=starship + +.PHONY: check +check: + bash $(CURDIR)/scripts/dev-setup.sh + +.PHONY: setup-kind +setup-kind: + kind create cluster --name $(KIND_CLUSTER) + +.PHONY: clean-kind +clean-kind: + kind delete cluster --name $(KIND_CLUSTER) diff --git a/starship/README.md b/starship/README.md new file mode 100644 index 00000000..1999bda6 --- /dev/null +++ b/starship/README.md @@ -0,0 +1,75 @@ +## 1. Installation +Inorder to get started with starship, one needs to install the following +* `kubectl`: https://kubernetes.io/docs/tasks/tools/ +* `kind`: https://kind.sigs.k8s.io/docs/user/quick-start/#installation +* `helm`: https://helm.sh/docs/intro/install/ +* `yq`: https://github.com/mikefarah/yq/#install + +Note: To make the process easy we have a simple command that will try and install dependencies +so that you dont have to. + +```bash +npm run e2e:deps +``` +This command will +* check (and install) if your system has all the dependencies needed to run the e2e tests wtih Starship +* fetch the helm charts for Starship + +## 2. Connect to a kubernetes cluster +Inorder to set up the infrastructure, for Starship, we need access to a kubernetes cluster. +One can either perform connect to a +* remote cluster in a managed kubernetes service +* use kubernetes desktop to spin up a cluster +* use kind to create a local cluster on local machine + +To make this easier we have a handy command which will create a local kind cluster and give you access +to a kubernetes cluster locally. + +NOTE: Resources constraint on local machine will affect the performance of Starship spinup time + +```bash +npm run e2e:kind +``` + +Run the following command to check connection to a k8s cluster +```bash +kubectl get pods +``` + +## 3. Start Starship +Now with the dependencies and a kubernetes cluster in handy, we can proceed with creating the mini-cosmos ecosystem + +Run +```bash +npm run e2e:start +``` + +We use the config file `configs/config.yaml` as the genesis file to define the topology of the e2e test infra. Change it as required + +Note: Spinup will take some time, while you wait for the system, can check the progress in another tab with `kubectl get pods` + +## 4. Run the tests +We have everything we need, our desired infrastructure is now running as intended, now we can run +our end-to-end tests. + +Run +```bash +npm run e2e:test +``` + +## 5. Stop the infra +The tests should be ideompotent, so the tests can be run multiple times (which is recommeded), since the time to spinup is still high (around 5 to 10 mins). + +Once the state of the mini-cosmos is corrupted, you can stop the deployments with +```bash +npm run e2e:stop +``` +Which will +* Stop port-forwarding the traffic to your local +* Delete all the helm charts deployed + +## 6. Cleanup kind (optional) +If you are using kind for your kubernetes cluster, you can delete it with +```bash +npm run e2e:clean +``` diff --git a/starship/__tests__/gov.test.js b/starship/__tests__/gov.test.js new file mode 100644 index 00000000..a1974d25 --- /dev/null +++ b/starship/__tests__/gov.test.js @@ -0,0 +1,185 @@ +import { generateMnemonic } from '@confio/relayer/build/lib/helpers'; +import { assertIsDeliverTxSuccess } from '@cosmjs/stargate'; +import Long from 'long'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; + +import { cosmos, getSigningStrideClient } from '../../src/codegen'; +import { useChain, waitUntil } from '../src'; +import './setup.test'; + +describe('Governance tests for stride', () => { + let wallet, denom, address; + let chainInfo, + getCoin, + getStargateClient, + getGenesisMnemonic, + getRpcEndpoint, + creditFromFaucet; + + // Variables used accross testcases + let queryClient; + let proposalId; + let genesisAddress; + + beforeAll(async () => { + ({ + chainInfo, + getCoin, + getStargateClient, + getGenesisMnemonic, + getRpcEndpoint, + creditFromFaucet + } = useChain('stride')); + denom = getCoin().base; + + // Initialize wallet + wallet = await DirectSecp256k1HdWallet.fromMnemonic(generateMnemonic(), { + prefix: chainInfo.chain.bech32_prefix + }); + address = (await wallet.getAccounts())[0].address; + + // Create custom cosmos interchain client + queryClient = await cosmos.ClientFactory.createRPCQueryClient({ + rpcEndpoint: getRpcEndpoint() + }); + + // Transfer stride to address + await creditFromFaucet(address); + }, 200000); + + it('check address has tokens', async () => { + const { balance } = await queryClient.cosmos.bank.v1beta1.balance({ + address, + denom + }); + + expect(balance.amount).toEqual('10000000000'); + }, 10000); + + it('submit a txt proposal', async () => { + const signingClient = await getSigningStrideClient({ + rpcEndpoint: getRpcEndpoint(), + signer: wallet + }); + + const contentMsg = cosmos.gov.v1beta1.TextProposal.fromPartial({ + title: 'Test Proposal', + description: 'Test text proposal for the e2e testing' + }); + + // Stake half of the tokens + const msg = cosmos.gov.v1beta1.MessageComposer.withTypeUrl.submitProposal({ + proposer: address, + initialDeposit: [ + { + amount: '1000000', + denom: denom + } + ], + content: { + typeUrl: '/cosmos.gov.v1beta1.TextProposal', + value: cosmos.gov.v1beta1.TextProposal.encode(contentMsg).finish() + } + }); + + const fee = { + amount: [ + { + denom, + amount: '100000' + } + ], + gas: '550000' + }; + + const result = await signingClient.signAndBroadcast(address, [msg], fee); + assertIsDeliverTxSuccess(result); + + // Get proposal id from log events + const proposalIdEvent = result.events.find( + (event) => event.type === 'submit_proposal' + ); + proposalId = proposalIdEvent.attributes.find( + (attr) => attr.key === 'proposal_id' + ).value; + + // eslint-disable-next-line no-undef + expect(BigInt(proposalId)).toBeGreaterThan(BigInt(0)); + }, 200000); + + it('query proposal', async () => { + const result = await queryClient.cosmos.gov.v1beta1.proposal({ + proposalId: Long.fromString(proposalId) + }); + + expect(result.proposal.proposalId.toString()).toEqual(proposalId); + }, 10000); + + it('vote on proposal from genesis address', async () => { + // create genesis address signing client + const mnemonic = await getGenesisMnemonic(); + const genesisWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { + prefix: chainInfo.chain.bech32_prefix + }); + genesisAddress = (await genesisWallet.getAccounts())[0].address; + + const genesisSigningClient = await getSigningStrideClient({ + rpcEndpoint: getRpcEndpoint(), + signer: genesisWallet + }); + + // Vote on proposal from genesis mnemonic address + const msg = cosmos.gov.v1beta1.MessageComposer.withTypeUrl.vote({ + proposalId: Long.fromString(proposalId), + voter: genesisAddress, + option: cosmos.gov.v1beta1.VoteOption.VOTE_OPTION_YES + }); + + const fee = { + amount: [ + { + denom, + amount: '100000' + } + ], + gas: '550000' + }; + + const result = await genesisSigningClient.signAndBroadcast( + genesisAddress, + [msg], + fee + ); + assertIsDeliverTxSuccess(result); + }, 10000); + + it('verify vote', async () => { + const { vote } = await queryClient.cosmos.gov.v1beta1.vote({ + proposalId: Long.fromString(proposalId), + voter: genesisAddress + }); + + expect(vote.proposalId.toString()).toEqual(proposalId); + expect(vote.voter).toEqual(genesisAddress); + expect(vote.options[0].option).toEqual(cosmos.gov.v1beta1.VoteOption.VOTE_OPTION_YES); + }, 10000); + + it('wait for voting period to end', async () => { + // wait for the voting period to end + const { proposal } = await queryClient.cosmos.gov.v1beta1.proposal({ + proposalId: Long.fromString(proposalId) + }); + + await expect(waitUntil(proposal.votingEndTime)).resolves.not.toThrow(); + }, 200000); + + it('verify proposal passed', async () => { + const { proposal } = await queryClient.cosmos.gov.v1beta1.proposal({ + proposalId: Long.fromString(proposalId) + }); + + expect(proposal.status).toEqual( + cosmos.gov.v1beta1.ProposalStatus.PROPOSAL_STATUS_PASSED + ); + }, 10000); +}); diff --git a/starship/__tests__/setup.test.js b/starship/__tests__/setup.test.js new file mode 100644 index 00000000..9f108746 --- /dev/null +++ b/starship/__tests__/setup.test.js @@ -0,0 +1,24 @@ +import path from 'path'; + +import { Config, useChain, useRegistry } from '../src'; + +beforeAll(async () => { + const configFile = path.join(__dirname, '..', 'configs', 'config.yaml'); + Config.setConfigFile = configFile; + Config.setRegistry = await useRegistry(configFile); +}); + +describe('Test clients', () => { + let client; + + beforeAll(async () => { + const { getStargateClient } = useChain('stride'); + client = await getStargateClient(); + }); + + it('check chain height', async () => { + const height = await client.getHeight(); + + expect(height).toBeGreaterThan(0); + }); +}); diff --git a/starship/__tests__/staking.test.js b/starship/__tests__/staking.test.js new file mode 100644 index 00000000..5416f295 --- /dev/null +++ b/starship/__tests__/staking.test.js @@ -0,0 +1,125 @@ +import { generateMnemonic } from '@confio/relayer/build/lib/helpers'; +import { assertIsDeliverTxSuccess } from '@cosmjs/stargate'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import BigNumber from 'bignumber.js'; + +import { cosmos, getSigningStrideClient } from '../../src/codegen'; +import { useChain } from '../src'; +import './setup.test'; + +describe('Staking tokens testing', () => { + let wallet, denom, address; + let chainInfo, getCoin, getStargateClient, getRpcEndpoint, creditFromFaucet; + + // Variables used accross testcases + let queryClient; + let validatorAddress; + let delegationAmount; + + beforeAll(async () => { + ({ + chainInfo, + getCoin, + getStargateClient, + getRpcEndpoint, + creditFromFaucet + } = useChain('stride')); + denom = getCoin().base; + + // Initialize wallet + wallet = await DirectSecp256k1HdWallet.fromMnemonic(generateMnemonic(), { + prefix: chainInfo.chain.bech32_prefix + }); + address = (await wallet.getAccounts())[0].address; + + // Create custom cosmos interchain client + queryClient = await cosmos.ClientFactory.createRPCQueryClient({ + rpcEndpoint: getRpcEndpoint() + }); + + // Transfer stride and ibc tokens to address, send only osmo to address + await creditFromFaucet(address); + }, 200000); + + it('check address has tokens', async () => { + const { balance } = await queryClient.cosmos.bank.v1beta1.balance({ + address, + denom + }); + + expect(balance.amount).toEqual('10000000000'); + }, 10000); + + it('query validator address', async () => { + const { validators } = await queryClient.cosmos.staking.v1beta1.validators({ + status: cosmos.staking.v1beta1.bondStatusToJSON( + cosmos.staking.v1beta1.BondStatus.BOND_STATUS_BONDED + ) + }); + let allValidators = validators; + if (validators.length > 1) { + allValidators = validators.sort((a, b) => + new BigNumber(b.tokens).minus(new BigNumber(a.tokens)).toNumber() + ); + } + + expect(allValidators.length).toBeGreaterThan(0); + + // set validator address to the first one + validatorAddress = allValidators[0].operatorAddress; + }); + + it('stake tokens to genesis validator', async () => { + const signingClient = await getSigningStrideClient({ + rpcEndpoint: getRpcEndpoint(), + signer: wallet + }); + + const { balance } = await queryClient.cosmos.bank.v1beta1.balance({ + address, + denom + }); + + // Stake half of the tokens + // eslint-disable-next-line no-undef + delegationAmount = (BigInt(balance.amount) / BigInt(2)).toString(); + const msg = cosmos.staking.v1beta1.MessageComposer.fromPartial.delegate({ + delegatorAddress: address, + validatorAddress: validatorAddress, + amount: { + amount: delegationAmount, + denom: balance.denom + } + }); + + const fee = { + amount: [ + { + denom, + amount: '100000' + } + ], + gas: '550000' + }; + + const result = await signingClient.signAndBroadcast(address, [msg], fee); + assertIsDeliverTxSuccess(result); + }); + + it('query delegation', async () => { + const { delegationResponse } = + await queryClient.cosmos.staking.v1beta1.delegation({ + delegatorAddr: address, + validatorAddr: validatorAddress + }); + + // Assert that the delegation amount is the set delegation amount + // eslint-disable-next-line no-undef + expect(BigInt(delegationResponse.balance.amount)).toBeGreaterThan( + // eslint-disable-next-line no-undef + BigInt(0) + ); + expect(delegationResponse.balance.amount).toEqual(delegationAmount); + expect(delegationResponse.balance.denom).toEqual(denom); + }); +}); diff --git a/starship/__tests__/token.test.js b/starship/__tests__/token.test.js new file mode 100644 index 00000000..7c5b4079 --- /dev/null +++ b/starship/__tests__/token.test.js @@ -0,0 +1,74 @@ +import { generateMnemonic } from '@confio/relayer/build/lib/helpers'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import { assertIsDeliverTxSuccess } from '@cosmjs/stargate'; + +import { getSigningStrideClient } from '../../src/codegen'; +import { useChain } from '../src'; +import './setup.test'; + +describe('Token transfers', () => { + let wallet, denom, address; + let chainInfo, getCoin, getStargateClient, getRpcEndpoint, creditFromFaucet; + + beforeAll(async () => { + ({ + chainInfo, + getCoin, + getStargateClient, + getRpcEndpoint, + creditFromFaucet + } = useChain('stride')); + denom = getCoin().base; + + // Initialize wallet + wallet = await DirectSecp256k1HdWallet.fromMnemonic(generateMnemonic(), { + prefix: chainInfo.chain.bech32_prefix + }); + address = (await wallet.getAccounts())[0].address; + + await creditFromFaucet(address); + }); + + it('send stride token to address', async () => { + // Initialize wallet + const wallet2 = await DirectSecp256k1HdWallet.fromMnemonic( + generateMnemonic(), + { prefix: chainInfo.chain.bech32_prefix } + ); + const address2 = (await wallet2.getAccounts())[0].address; + + const signingClient = await getSigningStrideClient({ + rpcEndpoint: getRpcEndpoint(), + signer: wallet + }); + + const fee = { + amount: [ + { + denom, + amount: '100000' + } + ], + gas: '550000' + }; + + const token = { + amount: '10000000', + denom + }; + + // Transfer uosmo tokens from faceut + await signingClient.sendTokens( + address, + address2, + [token], + fee, + 'send tokens test' + ); + + const balance = await signingClient.getBalance(address2, denom); + + expect(balance.amount).toEqual(token.amount); + expect(balance.denom).toEqual(denom); + }, 10000); +}); diff --git a/starship/configs/config.yaml b/starship/configs/config.yaml new file mode 100644 index 00000000..019b8275 --- /dev/null +++ b/starship/configs/config.yaml @@ -0,0 +1,34 @@ +chains: + - name: stride-1 + type: stride + numValidators: 1 + ports: + rest: 1317 + rpc: 26657 + faucet: 8007 + - name: cosmos-2 + type: cosmos + numValidators: 1 + ports: + rest: 1313 + rpc: 26653 + faucet: 8003 + +relayers: + - name: stride-cosmos + type: hermes + replicas: 1 + chains: + - stride-1 + - cosmos-2 + +explorer: + enabled: true + ports: + rest: 8080 + +registry: + enabled: true + ports: + rest: 8081 + grpc: 9091 diff --git a/starship/configs/local-config.yaml b/starship/configs/local-config.yaml new file mode 100644 index 00000000..b09d1d2c --- /dev/null +++ b/starship/configs/local-config.yaml @@ -0,0 +1,94 @@ +chains: + - name: stride-1 + type: stride + numValidators: 1 + ports: + rest: 1317 + rpc: 26657 + faucet: 8007 + resources: + limits: + cpu: "0.2" + memory: "200M" + requests: + cpu: "0.2" + memory: "200M" + - name: cosmos-2 + type: cosmos + numValidators: 1 + ports: + rest: 1313 + rpc: 26653 + faucet: 8003 + resources: + limits: + cpu: "0.2" + memory: "200M" + requests: + cpu: "0.2" + memory: "200M" + +relayers: + - name: stride-cosmos + type: hermes + replicas: 1 + chains: + - stride-1 + - cosmos-2 + resources: + limits: + cpu: "0.1" + memory: "100M" + requests: + cpu: "0.1" + memory: "100M" + +explorer: + enabled: false + +registry: + enabled: true + ports: + rest: 8081 + grpc: 9091 + resources: + limits: + cpu: "0.1" + memory: "100M" + requests: + cpu: "0.1" + memory: "100M" + +exposer: + resources: + limits: + cpu: "0.1" + memory: "100M" + requests: + cpu: "0.1" + memory: "100M" + +faucet: + resources: + limits: + cpu: "0.1" + memory: "100M" + requests: + cpu: "0.1" + memory: "100M" + +resources: + node: + limits: + cpu: "0.2" + memory: "200M" + requests: + cpu: "0.2" + memory: "200M" + wait: + limits: + cpu: "0.1" + memory: "100M" + requests: + cpu: "0.1" + memory: "100M" diff --git a/starship/scripts/dev-setup.sh b/starship/scripts/dev-setup.sh new file mode 100644 index 00000000..72beb97f --- /dev/null +++ b/starship/scripts/dev-setup.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +set -euo pipefail + +function color() { + local color=$1 + shift + local black=30 red=31 green=32 yellow=33 blue=34 magenta=35 cyan=36 white=37 + local color_code=${!color:-$green} + printf "\033[%sm%s\033[0m\n" "$color_code" "$*" +} + +# Define a function to install a binary on macOS +install_macos() { + case $1 in + kubectl) brew install kubectl ;; + helm) brew install helm ;; + yq) brew install yq ;; + kind) brew install kind ;; + esac +} + +# Define a function to install a binary on Linux +install_linux() { + color green "Installing $1 at ~/.local/bin, please add it to PATH" + mkdir -p ~/.local/bin + case $1 in + kubectl) curl -Lks "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" > ~/.local/bin/kubectl && chmod +x ~/.local/bin/kubectl ;; + helm) curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash ;; + yq) curl -Lks "https://github.com/mikefarah/yq/releases/download/v4.33.3/yq_linux_amd64" > ~/.local/bin/yq && chmod +x ~/.local/bin/yq ;; + kind) curl -Lks https://kind.sigs.k8s.io/dl/v0.18.0/kind-linux-amd64 > ~/.local/bin/kind && chmod +x ~/.local/bin/kind ;; + esac +} + +# Define a function to install a binary +install_binary() { + if [[ $(uname -s) == "Darwin" ]]; then + install_macos $1 + else + install_linux $1 + fi +} + +# Define a function to check for the presence of a binary +check_binary() { + if ! command -v $1 &> /dev/null + then + echo "$1 is not installed" + install_binary $1 + if ! command -v $1 &> /dev/null + then + color red "Installation of $1 failed, exiting..." + color red "Please install $1 manually, then run me again to verify the installation" + exit 1 + fi + fi +} + +# Check the binaries +check_binary kubectl +check_binary helm +check_binary yq +check_binary kind + +color green "All binaries are installed" diff --git a/starship/scripts/port-forward.sh b/starship/scripts/port-forward.sh new file mode 100644 index 00000000..1dc1eafb --- /dev/null +++ b/starship/scripts/port-forward.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +set -euo pipefail + +function color() { + local color=$1 + shift + local black=30 red=31 green=32 yellow=33 blue=34 magenta=35 cyan=36 white=37 + local color_code=${!color:-$green} + printf "\033[%sm%s\033[0m\n" "$color_code" "$*" +} + +function stop_port_forward() { + color green "Trying to stop all port-forward, if any...." + PIDS=$(ps -ef | grep -i -e 'kubectl port-forward' | grep -v 'grep' | cat | awk '{print $2}') || true + for p in $PIDS; do + kill -15 $p + done + sleep 2 +} + +# Default values +CHAIN_RPC_PORT=26657 +CHAIN_LCD_PORT=1317 +CHAIN_EXPOSER_PORT=8081 +CHAIN_FAUCET_PORT=8000 +EXPLORER_LCD_PORT=8080 +REGISTRY_LCD_PORT=8080 +REGISTRY_GRPC_PORT=9090 + +for i in "$@"; do + case $i in + -c=*|--config=*) + CONFIGFILE="${i#*=}" + shift # past argument=value + ;; + -*|--*) + echo "Unknown option $i" + exit 1 + ;; + *) + ;; + esac +done + +stop_port_forward + +echo "Port forwarding for config ${CONFIGFILE}" +echo "Port forwarding all chains" +num_chains=$(yq -r ".chains | length - 1" ${CONFIGFILE}) +if [[ $num_chains -lt 0 ]]; then + echo "No chains to port-forward: num: $num_chains" + exit 1 +fi +for i in $(seq 0 $num_chains); do + chain=$(yq -r ".chains[$i].name" ${CONFIGFILE} ) + localrpc=$(yq -r ".chains[$i].ports.rpc" ${CONFIGFILE} ) + locallcd=$(yq -r ".chains[$i].ports.rest" ${CONFIGFILE} ) + localexp=$(yq -r ".chains[$i].ports.exposer" ${CONFIGFILE}) + localfaucet=$(yq -r ".chains[$i].ports.faucet" ${CONFIGFILE}) + [[ "$localrpc" != "null" ]] && kubectl port-forward pods/$chain-genesis-0 $localrpc:$CHAIN_RPC_PORT > /dev/null 2>&1 & + [[ "$locallcd" != "null" ]] && kubectl port-forward pods/$chain-genesis-0 $locallcd:$CHAIN_LCD_PORT > /dev/null 2>&1 & + [[ "$localexp" != "null" ]] && kubectl port-forward pods/$chain-genesis-0 $localexp:$CHAIN_EXPOSER_PORT > /dev/null 2>&1 & + [[ "$localfaucet" != "null" ]] && kubectl port-forward pods/$chain-genesis-0 $localfaucet:$CHAIN_FAUCET_PORT > /dev/null 2>&1 & + sleep 1 + color yellow "chains: forwarded $chain lcd to http://localhost:$locallcd, rpc to http://localhost:$localrpc, faucet to http://localhost:$localfaucet" +done + +echo "Port forward services" + +if [[ $(yq -r ".registry.enabled" $CONFIGFILE) == "true" ]]; +then + kubectl port-forward service/registry 8081:$REGISTRY_LCD_PORT > /dev/null 2>&1 & + kubectl port-forward service/registry 9091:$REGISTRY_GRPC_PORT > /dev/null 2>&1 & + sleep 1 + color yellow "registry: forwarded registry lcd to grpc http://localhost:8081, to http://localhost:9091" +fi + +if [[ $(yq -r ".explorer.enabled" $CONFIGFILE) == "true" ]]; +then + kubectl port-forward service/explorer 8080:$EXPLORER_LCD_PORT > /dev/null 2>&1 & + sleep 1 + color green "Open the explorer to get started.... http://localhost:8080" +fi diff --git a/starship/src/config.js b/starship/src/config.js new file mode 100644 index 00000000..cdb5f079 --- /dev/null +++ b/starship/src/config.js @@ -0,0 +1,116 @@ +import { ChainRegistryFetcher } from '@chain-registry/client'; +import { StargateClient } from '@cosmjs/stargate'; +import fs from 'fs'; +import yaml from 'js-yaml'; +import fetch from 'node-fetch'; +import { getSigningCosmosClientOptions } from '../../src/codegen'; + +export const Config = { + registry: undefined, + configFile: undefined, + + set setConfigFile(configFile) { + this.configFile = configFile; + }, + set setRegistry(registry) { + this.registry = registry; + } +}; + +export const useRegistry = async (configFile) => { + const config = yaml.load(fs.readFileSync(configFile, 'utf8')); + const registryUrl = `http://localhost:${config.registry.ports.rest}`; + + const urls = []; + config.chains.forEach((chain) => { + urls.push( + `${registryUrl}/chains/${chain.name}`, + `${registryUrl}/chains/${chain.name}/assets` + ); + }); + config.relayers.forEach((relayer) => { + urls.push( + `${registryUrl}/ibc/${relayer.chains[0]}/${relayer.chains[1]}`, + `${registryUrl}/ibc/${relayer.chains[1]}/${relayer.chains[0]}` + ); + }); + + const options = { + urls: urls + }; + const registry = new ChainRegistryFetcher(options); + await registry.fetchUrls(); + + return registry; +}; + +export const useChain = (chainName, signingOptions = null) => { + const registry = Config.registry; + const configFile = Config.configFile; + const config = yaml.load(fs.readFileSync(configFile, 'utf8')); + + const chain = registry.getChain(chainName); + const chainInfo = registry.getChainInfo(chainName); + const chainID = chainInfo.chain.chain_id; + + const getRpcEndpoint = () => { + return `http://localhost:${ + config.chains.find((chain) => chain.name === chainID).ports.rpc + }`; + }; + const getRestEndpoint = () => { + return `http://localhost:${ + config.chains.find((chain) => chain.name === chainID).ports.rest + }`; + }; + + const getGenesisMnemonic = async () => { + const url = `http://localhost:${config.registry.ports.rest}/chains/${chainID}/keys`; + const response = await fetch(url, {}); + const data = await response.json(); + return data['genesis'][0]['mnemonic']; + }; + + if (!signingOptions) { + signingOptions = getSigningCosmosClientOptions(); + } + + const getStargateClient = () => { + const rpcEndpoint = getRpcEndpoint(); + return StargateClient.connect(rpcEndpoint, signingOptions); + }; + + const getCoin = () => { + return chainInfo.fetcher.getChainAssetList(chainName).assets[0]; + }; + + const creditFromFaucet = async (address, denom = null) => { + const faucetEndpoint = `http://localhost:${ + config.chains.find((chain) => chain.name === chainID).ports.faucet + }/credit`; + if (!denom) { + denom = getCoin().base; + } + await fetch(faucetEndpoint, { + method: 'POST', + body: JSON.stringify({ + address, + denom + }), + headers: { + 'Content-type': 'application/json' + } + }); + }; + + return { + chain, + chainInfo, + getCoin, + getRpcEndpoint, + getRestEndpoint, + getGenesisMnemonic, + getStargateClient, + creditFromFaucet + }; +}; diff --git a/starship/src/index.js b/starship/src/index.js new file mode 100644 index 00000000..1d6a9e83 --- /dev/null +++ b/starship/src/index.js @@ -0,0 +1,2 @@ +export * from './config'; +export * from './utils'; diff --git a/starship/src/utils.js b/starship/src/utils.js new file mode 100644 index 00000000..6611247e --- /dev/null +++ b/starship/src/utils.js @@ -0,0 +1,29 @@ +import BigNumber from 'bignumber.js'; + +export const calcShareOutAmount = (poolInfo, coinsNeeded) => { + return poolInfo.poolAssets + .map(({ token }, i) => { + const tokenInAmount = new BigNumber(coinsNeeded[i].amount); + const totalShare = new BigNumber(poolInfo.totalShares.amount); + const totalShareExp = totalShare.shiftedBy(-18); + const poolAssetAmount = new BigNumber(token.amount); + + return tokenInAmount + .multipliedBy(totalShareExp) + .dividedBy(poolAssetAmount) + .shiftedBy(18) + .decimalPlaces(0, BigNumber.ROUND_HALF_UP) + .toString(); + }) + .sort((a, b) => (new BigNumber(a).lt(b) ? -1 : 1))[0]; +}; + +export const waitUntil = (date, timeout = 90000) => { + return new Promise((resolve) => { + const delay = date.getTime() - Date.now(); + if (delay > timeout) { + throw new Error('Timeout to wait until date'); + } + setTimeout(resolve, delay + 3000); + }); +};