diff --git a/.circleci/config.yml b/.circleci/config.yml index 1dc80239..f0d1ba0c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,6 +41,7 @@ workflows: - coverage: requires: - build + - faucet-docker jobs: build: @@ -144,9 +145,13 @@ jobs: - run: name: Start socket server command: ./scripts/socketserver/start.sh + - run: + name: Start http server + command: ./scripts/httpserver/start.sh - run: name: Run tests environment: + HTTPSERVER_ENABLED: 1 TENDERMINT_ENABLED: 1 SOCKETSERVER_ENABLED: 1 SKIP_BUILD: 1 @@ -165,6 +170,7 @@ jobs: name: Run CLI examples working_directory: packages/cli environment: + HTTPSERVER_ENABLED: 1 TENDERMINT_ENABLED: 1 SOCKETSERVER_ENABLED: 1 SKIP_BUILD: 1 @@ -176,6 +182,7 @@ jobs: - run: name: Stop chains command: | + ./scripts/httpserver/stop.sh ./scripts/socketserver/stop.sh ./scripts/tendermint/all_stop.sh ./scripts/<< parameters.simapp >>/stop.sh @@ -264,8 +271,12 @@ jobs: - run: name: Start socket server command: ./scripts/socketserver/start.sh + - run: + name: Start http server + command: ./scripts/httpserver/start.sh - run: environment: + HTTPSERVER_ENABLED: 1 SIMAPP42_ENABLED: 1 SLOW_SIMAPP42_ENABLED: 1 TENDERMINT_ENABLED: 1 @@ -284,6 +295,7 @@ jobs: name: Run CLI examples working_directory: packages/cli environment: + HTTPSERVER_ENABLED: 1 SIMAPP42_ENABLED: 1 SLOW_SIMAPP42_ENABLED: 1 TENDERMINT_ENABLED: 1 @@ -294,6 +306,7 @@ jobs: - run: name: Stop chains command: | + ./scripts/httpserver/stop.sh ./scripts/socketserver/stop.sh ./scripts/tendermint/all_stop.sh ./scripts/simapp42/stop.sh @@ -375,8 +388,12 @@ jobs: - run: name: Start socket server command: ./scripts/socketserver/start.sh + - run: + name: Start http server + command: ./scripts/httpserver/start.sh - run: environment: + HTTPSERVER_ENABLED: 1 SIMAPP42_ENABLED: 1 SLOW_SIMAPP42_ENABLED: 1 TENDERMINT_ENABLED: 1 @@ -387,6 +404,7 @@ jobs: - run: name: Stop chains command: | + ./scripts/httpserver/stop.sh ./scripts/socketserver/stop.sh ./scripts/tendermint/all_stop.sh ./scripts/simapp42/stop.sh @@ -467,8 +485,12 @@ jobs: - run: name: Start socket server command: ./scripts/socketserver/start.sh + - run: + name: Start http server + command: ./scripts/httpserver/start.sh - run: environment: + HTTPSERVER_ENABLED: 1 SIMAPP42_ENABLED: 1 SLOW_SIMAPP42_ENABLED: 1 TENDERMINT_ENABLED: 1 @@ -482,6 +504,7 @@ jobs: - run: name: Stop chains command: | + ./scripts/httpserver/stop.sh ./scripts/socketserver/stop.sh ./scripts/tendermint/all_stop.sh ./scripts/simapp42/stop.sh @@ -573,3 +596,32 @@ jobs: - run: name: Format shell scripts command: yarn format-shell + faucet-docker: + docker: + - image: cimg/go:1.17.8 + steps: + - run: + name: Install Git Large File Storage (LFS) + command: | + sudo apt update + sudo apt install git-lfs + - checkout + - setup_remote_docker: + # >= v20.10 https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.14.0#faccessat2 + version: 20.10.11 + - run: + name: Build faucet Docker image + # Use ${CIRCLE_TAG} + command: | + docker build --pull -t "confio/faucet:$CIRCLE_SHA1" -f packages/faucet/Dockerfile . + - run: + name: Check docker image can be executed + command: | + docker run --rm "confio/faucet:$CIRCLE_SHA1" version + docker run --rm "confio/faucet:$CIRCLE_SHA1" generate + # - run: + # name: Push image to Docker Hub + # command: | + # docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" + # docker push "confio/faucet:$CIRCLE_SHA1" + # docker logout diff --git a/.pnp.cjs b/.pnp.cjs index ffcda06e..41706e65 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3445,7 +3445,9 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["ses", "npm:0.11.1"], ["source-map-support", "npm:0.5.19"], ["ts-node", "virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:8.10.2"], - ["typescript", "patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=ddd1e8"] + ["typescript", "patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=ddd1e8"], + ["webpack", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:5.37.1"], + ["webpack-cli", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:4.7.0"] ], "linkType": "SOFT", }] @@ -5226,6 +5228,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], + ["virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.0.3", { + "packageLocation": "./.yarn/__virtual__/@webpack-cli-configtest-virtual-af67a62b82/0/cache/@webpack-cli-configtest-npm-1.0.3-b6e357f778-4efcca159e.zip/node_modules/@webpack-cli/configtest/", + "packageDependencies": [ + ["@webpack-cli/configtest", "virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.0.3"], + ["@types/webpack", null], + ["@types/webpack-cli", null], + ["webpack", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:5.37.1"], + ["webpack-cli", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:4.7.0"] + ], + "packagePeers": [ + "@types/webpack-cli", + "@types/webpack", + "webpack-cli", + "webpack" + ], + "linkType": "HARD", + }], ["virtual:3f2095d1b1ae86c3528b28bd8410f4d8bf5f6047b5402e111275cdabf96cfed69de5749998e5371b82993388087e5eee4fbc64b5a88b9a1f7cccaf495cab5b18#npm:1.0.3", { "packageLocation": "./.yarn/__virtual__/@webpack-cli-configtest-virtual-a2633f5f35/0/cache/@webpack-cli-configtest-npm-1.0.3-b6e357f778-4efcca159e.zip/node_modules/@webpack-cli/configtest/", "packageDependencies": [ @@ -5464,6 +5483,20 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], + ["virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.2.4", { + "packageLocation": "./.yarn/__virtual__/@webpack-cli-info-virtual-6c0587bf6e/0/cache/@webpack-cli-info-npm-1.2.4-e4a2135f37-4e27ccd04c.zip/node_modules/@webpack-cli/info/", + "packageDependencies": [ + ["@webpack-cli/info", "virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.2.4"], + ["@types/webpack-cli", null], + ["envinfo", "npm:7.8.1"], + ["webpack-cli", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:4.7.0"] + ], + "packagePeers": [ + "@types/webpack-cli", + "webpack-cli" + ], + "linkType": "HARD", + }], ["virtual:3f2095d1b1ae86c3528b28bd8410f4d8bf5f6047b5402e111275cdabf96cfed69de5749998e5371b82993388087e5eee4fbc64b5a88b9a1f7cccaf495cab5b18#npm:1.2.4", { "packageLocation": "./.yarn/__virtual__/@webpack-cli-info-virtual-eea32a6276/0/cache/@webpack-cli-info-npm-1.2.4-e4a2135f37-4e27ccd04c.zip/node_modules/@webpack-cli/info/", "packageDependencies": [ @@ -5678,6 +5711,23 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], + ["virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.4.0", { + "packageLocation": "./.yarn/__virtual__/@webpack-cli-serve-virtual-0fc3f02bbe/0/cache/@webpack-cli-serve-npm-1.4.0-1f566be693-0b063bed4c.zip/node_modules/@webpack-cli/serve/", + "packageDependencies": [ + ["@webpack-cli/serve", "virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.4.0"], + ["@types/webpack-cli", null], + ["@types/webpack-dev-server", null], + ["webpack-cli", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:4.7.0"], + ["webpack-dev-server", null] + ], + "packagePeers": [ + "@types/webpack-cli", + "@types/webpack-dev-server", + "webpack-cli", + "webpack-dev-server" + ], + "linkType": "HARD", + }], ["virtual:3f2095d1b1ae86c3528b28bd8410f4d8bf5f6047b5402e111275cdabf96cfed69de5749998e5371b82993388087e5eee4fbc64b5a88b9a1f7cccaf495cab5b18#npm:1.4.0", { "packageLocation": "./.yarn/__virtual__/@webpack-cli-serve-virtual-cea108c2e7/0/cache/@webpack-cli-serve-npm-1.4.0-1f566be693-0b063bed4c.zip/node_modules/@webpack-cli/serve/", "packageDependencies": [ @@ -11703,6 +11753,25 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], + ["virtual:536c20e6941e891928359308cc16b72093d0e0da0f443beba2054a9e2598b09efad7e1c132d847c30f74367ac51b448379359520939be6472541db64ca7607cb#npm:5.1.2", { + "packageLocation": "./.yarn/__virtual__/terser-webpack-plugin-virtual-ecdde3d019/0/cache/terser-webpack-plugin-npm-5.1.2-59f409825a-0eb0e81f52.zip/node_modules/terser-webpack-plugin/", + "packageDependencies": [ + ["terser-webpack-plugin", "virtual:536c20e6941e891928359308cc16b72093d0e0da0f443beba2054a9e2598b09efad7e1c132d847c30f74367ac51b448379359520939be6472541db64ca7607cb#npm:5.1.2"], + ["@types/webpack", null], + ["jest-worker", "npm:26.6.2"], + ["p-limit", "npm:3.1.0"], + ["schema-utils", "npm:3.0.0"], + ["serialize-javascript", "npm:5.0.1"], + ["source-map", "npm:0.6.1"], + ["terser", "npm:5.7.0"], + ["webpack", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:5.37.1"] + ], + "packagePeers": [ + "@types/webpack", + "webpack" + ], + "linkType": "HARD", + }], ["virtual:5e82fc811d8fd02f05149cd66fb79c02f16f05bfaf043b01e108813bc4e39a600f5a9f93ce3a86bd2c21b92c8082c2e5b50384c76a2fc4e252d186976e3055e2#npm:5.1.2", { "packageLocation": "./.yarn/__virtual__/terser-webpack-plugin-virtual-1be69a9933/0/cache/terser-webpack-plugin-npm-5.1.2-59f409825a-0eb0e81f52.zip/node_modules/terser-webpack-plugin/", "packageDependencies": [ @@ -12441,6 +12510,42 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], + ["virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:5.37.1", { + "packageLocation": "./.yarn/__virtual__/webpack-virtual-536c20e694/0/cache/webpack-npm-5.37.1-1e75a59f6f-572d3cd617.zip/node_modules/webpack/", + "packageDependencies": [ + ["webpack", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:5.37.1"], + ["@types/eslint-scope", "npm:3.7.0"], + ["@types/estree", "npm:0.0.47"], + ["@types/webpack-cli", null], + ["@webassemblyjs/ast", "npm:1.11.0"], + ["@webassemblyjs/wasm-edit", "npm:1.11.0"], + ["@webassemblyjs/wasm-parser", "npm:1.11.0"], + ["acorn", "npm:8.2.4"], + ["browserslist", "npm:4.16.6"], + ["chrome-trace-event", "npm:1.0.3"], + ["enhanced-resolve", "npm:5.8.2"], + ["es-module-lexer", "npm:0.4.1"], + ["eslint-scope", "npm:5.1.1"], + ["events", "npm:3.3.0"], + ["glob-to-regexp", "npm:0.4.1"], + ["graceful-fs", "npm:4.2.6"], + ["json-parse-better-errors", "npm:1.0.2"], + ["loader-runner", "npm:4.2.0"], + ["mime-types", "npm:2.1.30"], + ["neo-async", "npm:2.6.2"], + ["schema-utils", "npm:3.0.0"], + ["tapable", "npm:2.2.0"], + ["terser-webpack-plugin", "virtual:536c20e6941e891928359308cc16b72093d0e0da0f443beba2054a9e2598b09efad7e1c132d847c30f74367ac51b448379359520939be6472541db64ca7607cb#npm:5.1.2"], + ["watchpack", "npm:2.2.0"], + ["webpack-cli", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:4.7.0"], + ["webpack-sources", "npm:2.2.0"] + ], + "packagePeers": [ + "@types/webpack-cli", + "webpack-cli" + ], + "linkType": "HARD", + }], ["virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:5.37.1", { "packageLocation": "./.yarn/__virtual__/webpack-virtual-830f2cc1c2/0/cache/webpack-npm-5.37.1-1e75a59f6f-572d3cd617.zip/node_modules/webpack/", "packageDependencies": [ @@ -12972,6 +13077,48 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], + ["virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:4.7.0", { + "packageLocation": "./.yarn/__virtual__/webpack-cli-virtual-3ea0c3d01a/0/cache/webpack-cli-npm-4.7.0-cb3d7c34ff-cecfb321b9.zip/node_modules/webpack-cli/", + "packageDependencies": [ + ["webpack-cli", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:4.7.0"], + ["@discoveryjs/json-ext", "npm:0.5.3"], + ["@types/webpack", null], + ["@types/webpack-bundle-analyzer", null], + ["@types/webpack-cli__generators", null], + ["@types/webpack-cli__migrate", null], + ["@types/webpack-dev-server", null], + ["@webpack-cli/configtest", "virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.0.3"], + ["@webpack-cli/generators", null], + ["@webpack-cli/info", "virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.2.4"], + ["@webpack-cli/migrate", null], + ["@webpack-cli/serve", "virtual:3ea0c3d01a91f33f2b24b3c62295ba50dc5ef053f064cdf07af43c620c6676ffb7047384e94d8051c46c3fcde0c51e60e90f847a877a41fe8c439f2527308476#npm:1.4.0"], + ["colorette", "npm:1.2.2"], + ["commander", "npm:7.2.0"], + ["execa", "npm:5.0.0"], + ["fastest-levenshtein", "npm:1.0.12"], + ["import-local", "npm:3.0.2"], + ["interpret", "npm:2.2.0"], + ["rechoir", "npm:0.7.0"], + ["v8-compile-cache", "npm:2.3.0"], + ["webpack", "virtual:45dc8d177c5463b02ae2b62c45461f6704449bac45b0b4bf10ceca81013a617a6fa5aaf2547e43076d50ac57cad5c9979a6da6e8adf35b42d844e73e8c014613#npm:5.37.1"], + ["webpack-bundle-analyzer", null], + ["webpack-dev-server", null], + ["webpack-merge", "npm:5.7.3"] + ], + "packagePeers": [ + "@types/webpack-bundle-analyzer", + "@types/webpack-cli__generators", + "@types/webpack-cli__migrate", + "@types/webpack-dev-server", + "@types/webpack", + "@webpack-cli/generators", + "@webpack-cli/migrate", + "webpack-bundle-analyzer", + "webpack-dev-server", + "webpack" + ], + "linkType": "HARD", + }], ["virtual:4f1584ad4aba8733a24be7c8aebbffafef25607f2d00f4b314cf96717145c692763628a31c2b85d4686fbb091ff21ebffa3cc337399c042c19a32b9bdb786464#npm:4.7.0", { "packageLocation": "./.yarn/__virtual__/webpack-cli-virtual-0dffb89908/0/cache/webpack-cli-npm-4.7.0-cb3d7c34ff-cecfb321b9.zip/node_modules/webpack-cli/", "packageDependencies": [ diff --git a/.yarn/sdks/typescript/lib/tsserver.js b/.yarn/sdks/typescript/lib/tsserver.js index 71e35cf6..830ad9f2 100644 --- a/.yarn/sdks/typescript/lib/tsserver.js +++ b/.yarn/sdks/typescript/lib/tsserver.js @@ -18,6 +18,7 @@ const moduleWrapper = tsserver => { const pnpApi = require(`pnpapi`); const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { @@ -44,7 +45,7 @@ const moduleWrapper = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { str = resolved; } } @@ -60,18 +61,26 @@ const moduleWrapper = tsserver => { // // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 // - // Update Oct 8 2021: VSCode changed their format in 1.61. + // Update 2021-10-08: VSCode changed their format in 1.61. // Before | ^zip:/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // + // Update 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // case `vscode <1.61`: { str = `^zip:${str}`; } break; - case `vscode`: { + case `vscode <1.66`: { str = `^/zip/${str}`; } break; + case `vscode`: { + str = `^/zip${str}`; + } break; + // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) @@ -85,7 +94,7 @@ const moduleWrapper = tsserver => { // everything else is up to neovim case `neovim`: { str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile:${str}`; + str = `zipfile://${str}`; } break; default: { @@ -100,8 +109,7 @@ const moduleWrapper = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: - case `neovim`: { + case `coc-nvim`: { str = str.replace(/\.zip::/, `.zip/`); // The path for coc-nvim is in format of //zipfile://.yarn/... // So in order to convert it back, we use .* to match all the thing @@ -111,6 +119,12 @@ const moduleWrapper = tsserver => { : str.replace(/^.*zipfile:/, ``); } break; + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + case `vscode`: default: { return process.platform === `win32` @@ -143,8 +157,9 @@ const moduleWrapper = tsserver => { let hostInfo = `unknown`; Object.assign(Session.prototype, { - onMessage(/** @type {string} */ message) { - const parsedMessage = JSON.parse(message) + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( parsedMessage != null && @@ -153,14 +168,23 @@ const moduleWrapper = tsserver => { typeof parsedMessage.arguments.hostInfo === `string` ) { hostInfo = parsedMessage.arguments.hostInfo; - if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK && process.env.VSCODE_IPC_HOOK.match(/Code\/1\.([1-5][0-9]|60)\./)) { - hostInfo += ` <1.61`; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + if (/(\/|-)1\.([1-5][0-9]|60)\./.test(process.env.VSCODE_IPC_HOOK)) { + hostInfo += ` <1.61`; + } else if (/(\/|-)1\.(6[1-5])\./.test(process.env.VSCODE_IPC_HOOK)) { + hostInfo += ` <1.66`; + } } } - return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => { - return typeof value === `string` ? fromEditorPath(value) : value; - })); + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); }, send(/** @type {any} */ msg) { diff --git a/.yarn/sdks/typescript/lib/tsserverlibrary.js b/.yarn/sdks/typescript/lib/tsserverlibrary.js index 7a2d65ea..0f7084cd 100644 --- a/.yarn/sdks/typescript/lib/tsserverlibrary.js +++ b/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -18,6 +18,7 @@ const moduleWrapper = tsserver => { const pnpApi = require(`pnpapi`); const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const isPortal = str => str.startsWith("portal:/"); const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { @@ -44,7 +45,7 @@ const moduleWrapper = tsserver => { const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); - if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { + if (locator && (dependencyTreeRoots.has(`${locator.name}@${locator.reference}`) || isPortal(locator.reference))) { str = resolved; } } @@ -60,18 +61,26 @@ const moduleWrapper = tsserver => { // // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 // - // Update Oct 8 2021: VSCode changed their format in 1.61. + // Update 2021-10-08: VSCode changed their format in 1.61. // Before | ^zip:/c:/foo/bar.zip/package.json // After | ^/zip//c:/foo/bar.zip/package.json // + // Update 2022-04-06: VSCode changed the format in 1.66. + // Before | ^/zip//c:/foo/bar.zip/package.json + // After | ^/zip/c:/foo/bar.zip/package.json + // case `vscode <1.61`: { str = `^zip:${str}`; } break; - case `vscode`: { + case `vscode <1.66`: { str = `^/zip/${str}`; } break; + case `vscode`: { + str = `^/zip${str}`; + } break; + // To make "go to definition" work, // We have to resolve the actual file system path from virtual path // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) @@ -85,7 +94,7 @@ const moduleWrapper = tsserver => { // everything else is up to neovim case `neovim`: { str = normalize(resolved).replace(/\.zip\//, `.zip::`); - str = `zipfile:${str}`; + str = `zipfile://${str}`; } break; default: { @@ -100,8 +109,7 @@ const moduleWrapper = tsserver => { function fromEditorPath(str) { switch (hostInfo) { - case `coc-nvim`: - case `neovim`: { + case `coc-nvim`: { str = str.replace(/\.zip::/, `.zip/`); // The path for coc-nvim is in format of //zipfile://.yarn/... // So in order to convert it back, we use .* to match all the thing @@ -111,6 +119,12 @@ const moduleWrapper = tsserver => { : str.replace(/^.*zipfile:/, ``); } break; + case `neovim`: { + str = str.replace(/\.zip::/, `.zip/`); + // The path for neovim is in format of zipfile:////.yarn/... + return str.replace(/^zipfile:\/\//, ``); + } break; + case `vscode`: default: { return process.platform === `win32` @@ -143,8 +157,9 @@ const moduleWrapper = tsserver => { let hostInfo = `unknown`; Object.assign(Session.prototype, { - onMessage(/** @type {string} */ message) { - const parsedMessage = JSON.parse(message) + onMessage(/** @type {string | object} */ message) { + const isStringMessage = typeof message === 'string'; + const parsedMessage = isStringMessage ? JSON.parse(message) : message; if ( parsedMessage != null && @@ -153,14 +168,23 @@ const moduleWrapper = tsserver => { typeof parsedMessage.arguments.hostInfo === `string` ) { hostInfo = parsedMessage.arguments.hostInfo; - if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK && process.env.VSCODE_IPC_HOOK.match(/Code\/1\.([1-5][0-9]|60)\./)) { - hostInfo += ` <1.61`; + if (hostInfo === `vscode` && process.env.VSCODE_IPC_HOOK) { + if (/(\/|-)1\.([1-5][0-9]|60)\./.test(process.env.VSCODE_IPC_HOOK)) { + hostInfo += ` <1.61`; + } else if (/(\/|-)1\.(6[1-5])\./.test(process.env.VSCODE_IPC_HOOK)) { + hostInfo += ` <1.66`; + } } } - return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => { - return typeof value === `string` ? fromEditorPath(value) : value; - })); + const processedMessageJSON = JSON.stringify(parsedMessage, (key, value) => { + return typeof value === 'string' ? fromEditorPath(value) : value; + }); + + return originalOnMessage.call( + this, + isStringMessage ? processedMessageJSON : JSON.parse(processedMessageJSON) + ); }, send(/** @type {any} */ msg) { diff --git a/CHANGELOG.md b/CHANGELOG.md index aa2e5262..2bc18d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,28 @@ and this project adheres to ## [Unreleased] +### Added + +- @cosmjs/math: Add `Decimal.zero` and `Decimal.one` ([#1110]). + +### Changed + +- @cosmjs/faucet: Docker build image is 90 % smaller now (from 500 MB to 50 MB) + due to build system optimizations ([#1120], [#1121]). +- @cosmjs/cosmwasm-stargate: `CosmWasmClient.connect` and + `SigningCosmWasmClient.connectWithSigner` now accept custom HTTP headers + ([#1007]) +- @cosmjs/stargate: `StargateClient.connect` and + `SigningStargateClient.connectWithSigner` now accept custom HTTP headers + ([#1007]) +- @cosmjs/tendermint-rpc: `Tendermint34Client.connect` now accepts custom HTTP + headers ([#1007]). + +[#1007]: https://github.com/cosmos/cosmjs/issues/1007 +[#1110]: https://github.com/cosmos/cosmjs/issues/1110 +[#1120]: https://github.com/cosmos/cosmjs/pull/1120 +[#1121]: https://github.com/cosmos/cosmjs/pull/1121 + ## [0.28.3] - 2022-04-11 ### Added diff --git a/HACKING.md b/HACKING.md index a8f96965..8080f2d8 100644 --- a/HACKING.md +++ b/HACKING.md @@ -28,7 +28,7 @@ To verify everything worked as expected, check if the testing contracts are correctly checked out: ```sh -cd scripts/launchpad/contracts +cd scripts/wasmd/contracts sha256sum -c checksums.sha256 ``` @@ -64,13 +64,19 @@ export TENDERMINT_ENABLED=1 ./scripts/socketserver/start.sh export SOCKETSERVER_ENABLED=1 +# Start Http server +./scripts/httpserver/start.sh +export HTTPSERVER_ENABLED=1 + # now more tests are running that were marked as "pending" before yarn test # And at the end of the day +unset HTTPSERVER_ENABLED unset SOCKETSERVER_ENABLED unset TENDERMINT_ENABLED unset LAUNCHPAD_ENABLED +./scripts/httpserver/stop.sh ./scripts/socketserver/stop.sh ./scripts/tendermint/all_stop.sh ./scripts/launchpad/stop.sh @@ -100,6 +106,7 @@ order to avoid conflicts. Here is an overview of the ports used: | 1319 | wasmd LCD API | Manual Stargate debugging | | 4444 | socketserver | @cosmjs/sockets tests | | 4445 | socketserver slow | @cosmjs/sockets tests | +| 5555 | httpserver | @cosmjs/tendermint-rpc tests | | 9090 | simapp gRPC | Manual Stargate debugging | | 11134 | Tendermint 0.34 RPC | @cosmjs/tendermint-rpc tests | | 26658 | simapp Tendermint RPC | Stargate client tests | diff --git a/packages/cli/examples/figment.ts b/packages/cli/examples/figment.ts new file mode 100644 index 00000000..8a7592ef --- /dev/null +++ b/packages/cli/examples/figment.ts @@ -0,0 +1,21 @@ +import { StargateClient } from "@cosmjs/stargate"; + +// Network config +const rpcEndpoint = { + // Note: we removed the /status patch from the examples because we use the HTTP POST API + url: "https://cosmoshub-4--rpc--full.datahub.figment.io/", + headers: { + "Authorization": "5195ebb0bfb7f0fe5c43409240c8b2c4", + } +}; + +// Setup client +const client = await StargateClient.connect(rpcEndpoint); + +// Get some data +const chainId = await client.getChainId(); +console.log("Chain ID:", chainId); +const balance = await client.getAllBalances("cosmos1ey69r37gfxvxg62sh4r0ktpuc46pzjrmz29g45"); +console.log("Balances:", balance); + +client.disconnect(); diff --git a/packages/cli/run_examples.sh b/packages/cli/run_examples.sh index 73dbafb2..5e301bca 100755 --- a/packages/cli/run_examples.sh +++ b/packages/cli/run_examples.sh @@ -17,3 +17,6 @@ if [ -n "${SIMAPP42_ENABLED:-}" ]; then yarn node ./bin/cosmjs-cli --init examples/stargate.ts --code "process.exit(0)" yarn node ./bin/cosmjs-cli --init examples/simulate.ts --code "process.exit(0)" fi + +# Disabled as this requires internet access +# yarn node ./bin/cosmjs-cli --init examples/figment.ts --code "process.exit(0)" diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.ts index e09c76e7..c308c598 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.ts @@ -23,7 +23,7 @@ import { TimeoutError, TxExtension, } from "@cosmjs/stargate"; -import { Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; import { assert, sleep } from "@cosmjs/utils"; import { CodeInfoResponse, @@ -89,7 +89,7 @@ export class CosmWasmClient { private readonly codesCache = new Map(); private chainId: string | undefined; - public static async connect(endpoint: string): Promise { + public static async connect(endpoint: string | HttpEndpoint): Promise { const tmClient = await Tendermint34Client.connect(endpoint); return new CosmWasmClient(tmClient); } diff --git a/packages/cosmwasm-stargate/src/index.ts b/packages/cosmwasm-stargate/src/index.ts index 91ab2f30..49908b5d 100644 --- a/packages/cosmwasm-stargate/src/index.ts +++ b/packages/cosmwasm-stargate/src/index.ts @@ -29,3 +29,6 @@ export { SigningCosmWasmClientOptions, UploadResult, } from "./signingcosmwasmclient"; + +// Re-exported because this is part of the CosmWasmClient/SigningCosmWasmClient APIs +export { HttpEndpoint } from "@cosmjs/tendermint-rpc"; diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 1e940d88..69e2d71d 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -30,7 +30,7 @@ import { SignerData, StdFee, } from "@cosmjs/stargate"; -import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { assert, assertDefined } from "@cosmjs/utils"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; import { MsgDelegate, MsgUndelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; @@ -172,7 +172,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( - endpoint: string, + endpoint: string | HttpEndpoint, signer: OfflineSigner, options: SigningCosmWasmClientOptions = {}, ): Promise { diff --git a/packages/faucet/Dockerfile b/packages/faucet/Dockerfile index 5087c9e5..17962f2b 100644 --- a/packages/faucet/Dockerfile +++ b/packages/faucet/Dockerfile @@ -1,24 +1,32 @@ # Confio hosts a faucet build at https://hub.docker.com/r/confio/faucet # # Build: -# docker build -t confio/faucet:manual -f packages/faucet/Dockerfile . +# docker build --pull -t confio/faucet:manual -f packages/faucet/Dockerfile . # Run: # docker run --rm confio/faucet:manual version +# +# During the build step the working directory must be the repo root such that the +# app has access to all installed dependencies. FROM node:14-alpine as build-env ADD . /app WORKDIR /app -RUN apk add --update --no-cache alpine-sdk linux-headers build-base gcc libusb-dev python3 eudev-dev && ln -sf python3 /usr/bin/python -RUN python3 -m ensurepip -RUN pip3 install --no-cache --upgrade pip setuptools +RUN apk add --update --no-cache alpine-sdk linux-headers build-base gcc libusb-dev python3 py3-pip eudev-dev +RUN ln -sf python3 /usr/bin/python RUN yarn install && yarn run build +RUN (cd packages/faucet && SKIP_BUILD=1 yarn pack-node) -FROM node:14-alpine -COPY --from=build-env /app /app +# Use Alpine and install Node.js which is 50% smaller than the -alpine version of the node +# image (53 MB including the faucet app). +FROM alpine:3.15 +# Installs Node.js 16 (https://pkgs.alpinelinux.org/packages?name=nodejs&branch=v3.15) +RUN apk add --update nodejs + +COPY --from=build-env /app/packages/faucet /app/packages/faucet WORKDIR /app EXPOSE 8000 -ENTRYPOINT ["yarn", "node", "/app/packages/faucet/bin/cosmos-faucet"] +ENTRYPOINT ["/app/packages/faucet/bin/cosmos-faucet-dist"] diff --git a/packages/faucet/bin/cosmos-faucet-dist b/packages/faucet/bin/cosmos-faucet-dist new file mode 100755 index 00000000..6ec8dca2 --- /dev/null +++ b/packages/faucet/bin/cosmos-faucet-dist @@ -0,0 +1,6 @@ +#!/usr/bin/env node +const path = require("path"); + +// attempt to call in main file.... +const faucet = require(path.join(__dirname, "..", "dist", "node", "cli.js")); +faucet.main(process.argv.slice(2)); diff --git a/packages/faucet/package.json b/packages/faucet/package.json index b7ffe50c..2007b809 100644 --- a/packages/faucet/package.json +++ b/packages/faucet/package.json @@ -36,7 +36,8 @@ "test": "yarn build-or-skip && yarn test-node", "coverage": "nyc --reporter=text --reporter=lcov yarn test --quiet", "start-dev": "FAUCET_ADDRESS_PREFIX=wasm FAUCET_CREDIT_AMOUNT_UCOSM=10000000 FAUCET_CREDIT_AMOUNT_USTAKE=100000 FAUCET_CONCURRENCY=3 FAUCET_MNEMONIC=\"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone\" ./bin/cosmos-faucet start \"http://localhost:26659\"", - "start-coralnet": "FAUCET_ADDRESS_PREFIX=coral FAUCET_TOKENS=\"ushell,ureef\" FAUCET_CREDIT_AMOUNT_USHELL=10000000 FAUCET_CREDIT_AMOUNT_UREEF=2000000 FAUCET_CONCURRENCY=3 FAUCET_MNEMONIC=\"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone\" ./bin/cosmos-faucet start \"https://lcd.coralnet.cosmwasm.com\"" + "start-coralnet": "FAUCET_ADDRESS_PREFIX=coral FAUCET_TOKENS=\"ushell,ureef\" FAUCET_CREDIT_AMOUNT_USHELL=10000000 FAUCET_CREDIT_AMOUNT_UREEF=2000000 FAUCET_CONCURRENCY=3 FAUCET_MNEMONIC=\"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone\" ./bin/cosmos-faucet start \"https://lcd.coralnet.cosmwasm.com\"", + "pack-node": "yarn build-or-skip && webpack --mode production --config webpack.node.config.js" }, "dependencies": { "@cosmjs/crypto": "workspace:packages/crypto", @@ -72,6 +73,8 @@ "ses": "^0.11.0", "source-map-support": "^0.5.19", "ts-node": "^8", - "typescript": "~4.4" + "typescript": "~4.4", + "webpack": "^5.32.0", + "webpack-cli": "^4.6.0" } } diff --git a/packages/faucet/webpack.node.config.js b/packages/faucet/webpack.node.config.js new file mode 100644 index 00000000..94b875c4 --- /dev/null +++ b/packages/faucet/webpack.node.config.js @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +const path = require("path"); + +const target = "node"; +const distdir = path.join(__dirname, "dist", target); + +module.exports = [ + { + target: target, + entry: "./build/cli.js", + output: { + path: distdir, + filename: "cli.js", + library: { + type: "commonjs", + }, + }, + plugins: [], + resolve: {}, + }, +]; diff --git a/packages/math/src/decimal.spec.ts b/packages/math/src/decimal.spec.ts index e12d1f96..1526c926 100644 --- a/packages/math/src/decimal.spec.ts +++ b/packages/math/src/decimal.spec.ts @@ -133,6 +133,50 @@ describe("Decimal", () => { }); }); + describe("zero", () => { + it("works", () => { + expect(Decimal.zero(0).toString()).toEqual("0"); + expect(Decimal.zero(1).toString()).toEqual("0"); + expect(Decimal.zero(2).toString()).toEqual("0"); + expect(Decimal.zero(30).toString()).toEqual("0"); + + expect(Decimal.zero(0).fractionalDigits).toEqual(0); + expect(Decimal.zero(1).fractionalDigits).toEqual(1); + expect(Decimal.zero(2).fractionalDigits).toEqual(2); + expect(Decimal.zero(30).fractionalDigits).toEqual(30); + + expect(Decimal.zero(0).atomics).toEqual("0"); + expect(Decimal.zero(1).atomics).toEqual("0"); + expect(Decimal.zero(2).atomics).toEqual("0"); + expect(Decimal.zero(30).atomics).toEqual("0"); + + expect(() => Decimal.zero(NaN)).toThrowError(/Fractional digits is not an integer/i); + expect(() => Decimal.zero(1.2)).toThrowError(/Fractional digits is not an integer/i); + }); + }); + + describe("one", () => { + it("works", () => { + expect(Decimal.one(0).toString()).toEqual("1"); + expect(Decimal.one(1).toString()).toEqual("1"); + expect(Decimal.one(2).toString()).toEqual("1"); + expect(Decimal.one(30).toString()).toEqual("1"); + + expect(Decimal.one(0).fractionalDigits).toEqual(0); + expect(Decimal.one(1).fractionalDigits).toEqual(1); + expect(Decimal.one(2).fractionalDigits).toEqual(2); + expect(Decimal.one(30).fractionalDigits).toEqual(30); + + expect(Decimal.one(0).atomics).toEqual("1"); + expect(Decimal.one(1).atomics).toEqual("10"); + expect(Decimal.one(2).atomics).toEqual("100"); + expect(Decimal.one(30).atomics).toEqual("1000000000000000000000000000000"); + + expect(() => Decimal.one(NaN)).toThrowError(/Fractional digits is not an integer/i); + expect(() => Decimal.one(1.2)).toThrowError(/Fractional digits is not an integer/i); + }); + }); + describe("toString", () => { it("displays no decimal point for full numbers", () => { expect(Decimal.fromUserInput("44", 0).toString()).toEqual("44"); diff --git a/packages/math/src/decimal.ts b/packages/math/src/decimal.ts index 3f548e5b..6bb8a1f6 100644 --- a/packages/math/src/decimal.ts +++ b/packages/math/src/decimal.ts @@ -58,6 +58,28 @@ export class Decimal { return new Decimal(atomics, fractionalDigits); } + /** + * Creates a Decimal with value 0.0 and the given number of fractial digits. + * + * Fractional digits are not relevant for the value but needed to be able + * to perform arithmetic operations with other decimals. + */ + public static zero(fractionalDigits: number): Decimal { + Decimal.verifyFractionalDigits(fractionalDigits); + return new Decimal("0", fractionalDigits); + } + + /** + * Creates a Decimal with value 1.0 and the given number of fractial digits. + * + * Fractional digits are not relevant for the value but needed to be able + * to perform arithmetic operations with other decimals. + */ + public static one(fractionalDigits: number): Decimal { + Decimal.verifyFractionalDigits(fractionalDigits); + return new Decimal("1" + "0".repeat(fractionalDigits), fractionalDigits); + } + private static verifyFractionalDigits(fractionalDigits: number): void { if (!Number.isInteger(fractionalDigits)) throw new Error("Fractional digits is not an integer"); if (fractionalDigits < 0) throw new Error("Fractional digits must not be negative"); diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index 6af58edc..f3cd17f1 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -122,3 +122,6 @@ export { } from "./stargateclient"; export { StdFee } from "@cosmjs/amino"; export { Coin, coin, coins, makeCosmoshubPath, parseCoins } from "@cosmjs/proto-signing"; + +// Re-exported because this is part of the StargateClient/SigningStargateClient APIs +export { HttpEndpoint } from "@cosmjs/tendermint-rpc"; diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index e0e13c5a..9dec3293 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -12,7 +12,7 @@ import { Registry, TxBodyEncodeObject, } from "@cosmjs/proto-signing"; -import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client } from "@cosmjs/tendermint-rpc"; import { assert, assertDefined } from "@cosmjs/utils"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx"; @@ -116,7 +116,7 @@ export class SigningStargateClient extends StargateClient { private readonly gasPrice: GasPrice | undefined; public static async connectWithSigner( - endpoint: string, + endpoint: string | HttpEndpoint, signer: OfflineSigner, options: SigningStargateClientOptions = {}, ): Promise { diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 46474ed0..af3da369 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -2,7 +2,7 @@ import { toHex } from "@cosmjs/encoding"; import { Uint53 } from "@cosmjs/math"; import { Decimal } from "@cosmjs/math"; -import { Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; +import { HttpEndpoint, Tendermint34Client, toRfc3339WithNanoseconds } from "@cosmjs/tendermint-rpc"; import { assert, sleep } from "@cosmjs/utils"; import { MsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; @@ -152,7 +152,7 @@ export class StargateClient { private readonly accountParser: AccountParser; public static async connect( - endpoint: string, + endpoint: string | HttpEndpoint, options: StargateClientOptions = {}, ): Promise { const tmClient = await Tendermint34Client.connect(endpoint); diff --git a/packages/tendermint-rpc/src/index.ts b/packages/tendermint-rpc/src/index.ts index e56a3e5c..98515b4d 100644 --- a/packages/tendermint-rpc/src/index.ts +++ b/packages/tendermint-rpc/src/index.ts @@ -12,6 +12,10 @@ export { toRfc3339WithNanoseconds, toSeconds, } from "./dates"; +export { + // This type is part of the Tendermint34Client.connect API + HttpEndpoint, +} from "./rpcclients"; export { HttpClient, WebsocketClient } from "./rpcclients"; // TODO: Why do we export those outside of this package? export { AbciInfoRequest, diff --git a/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts index add473e9..1c326508 100644 --- a/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts +++ b/packages/tendermint-rpc/src/rpcclients/httpclient.spec.ts @@ -1,27 +1,71 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import { createJsonRpcRequest } from "../jsonrpc"; import { defaultInstance } from "../testutil.spec"; import { http, HttpClient } from "./httpclient"; function pendingWithoutTendermint(): void { if (!process.env.TENDERMINT_ENABLED) { - pending("Set TENDERMINT_ENABLED to enable tendermint rpc tests"); + pending("Set TENDERMINT_ENABLED to enable Tendermint RPC tests"); + } +} + +function pendingWithoutHttpServer(): void { + if (!process.env.HTTPSERVER_ENABLED) { + pending("Set HTTPSERVER_ENABLED to enable HTTP tests"); } } const tendermintUrl = defaultInstance.url; +const echoUrl = "http://localhost:5555/echo_headers"; describe("http", () => { it("can send a health request", async () => { pendingWithoutTendermint(); - const response = await http("POST", `http://${tendermintUrl}`, createJsonRpcRequest("health")); + const response = await http("POST", `http://${tendermintUrl}`, undefined, createJsonRpcRequest("health")); expect(response).toEqual(jasmine.objectContaining({ jsonrpc: "2.0" })); }); it("errors for non-open port", async () => { await expectAsync( - http("POST", `http://localhost:56745`, createJsonRpcRequest("health")), + http("POST", `http://localhost:56745`, undefined, createJsonRpcRequest("health")), ).toBeRejectedWithError(/(ECONNREFUSED|Failed to fetch)/i); }); + + it("can send custom headers", async () => { + pendingWithoutHttpServer(); + // Without custom headers + const response1 = await http("POST", echoUrl, undefined, createJsonRpcRequest("health")); + expect(response1).toEqual({ + request_headers: jasmine.objectContaining({ + // Basic headers from http client + Accept: jasmine.any(String), + "Content-Length": jasmine.any(String), + "Content-Type": "application/json", + Host: jasmine.any(String), + "User-Agent": jasmine.any(String), + }), + }); + + // With custom headers + const response2 = await http( + "POST", + echoUrl, + { foo: "bar123", Authorization: "Basic Z3Vlc3Q6bm9QYXNzMTIz" }, + createJsonRpcRequest("health"), + ); + expect(response2).toEqual({ + request_headers: jasmine.objectContaining({ + // Basic headers from http client + "Content-Length": jasmine.any(String), + "Content-Type": "application/json", + Host: jasmine.any(String), + "User-Agent": jasmine.any(String), + // Custom headers + foo: "bar123", + Authorization: "Basic Z3Vlc3Q6bm9QYXNzMTIz", + }), + }); + }); }); describe("HttpClient", () => { diff --git a/packages/tendermint-rpc/src/rpcclients/httpclient.ts b/packages/tendermint-rpc/src/rpcclients/httpclient.ts index 293842df..0b7dedac 100644 --- a/packages/tendermint-rpc/src/rpcclients/httpclient.ts +++ b/packages/tendermint-rpc/src/rpcclients/httpclient.ts @@ -25,23 +25,58 @@ function filterBadStatus(res: any): any { * For some reason, fetch does not complain about missing server-side CORS support. */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export async function http(method: "POST", url: string, request?: any): Promise { +export async function http( + method: "POST", + url: string, + headers: Record | undefined, + request?: any, +): Promise { if (typeof fetch !== "undefined") { - const body = request ? JSON.stringify(request) : undefined; - return fetch(url, { method: method, body: body }) + const settings = { + method: method, + body: request ? JSON.stringify(request) : undefined, + headers: { + // eslint-disable-next-line @typescript-eslint/naming-convention + "Content-Type": "application/json", + ...headers, + }, + }; + return fetch(url, settings) .then(filterBadStatus) .then((res: any) => res.json()); } else { - return axios.request({ url: url, method: method, data: request }).then((res) => res.data); + return axios + .request({ url: url, method: method, data: request, headers: headers }) + .then((res) => res.data); } } +export interface HttpEndpoint { + /** + * The URL of the HTTP endpoint. + * + * For POST APIs like Tendermint RPC in CosmJS, + * this is without the method specific paths (e.g. https://cosmoshub-4--rpc--full.datahub.figment.io/) + */ + readonly url: string; + /** + * HTTP headers that are sent with every request, such as authorization information. + */ + readonly headers: Record; +} + export class HttpClient implements RpcClient { protected readonly url: string; + protected readonly headers: Record | undefined; - public constructor(url: string) { - // accept host.name:port and assume http protocol - this.url = hasProtocol(url) ? url : "http://" + url; + public constructor(endpoint: string | HttpEndpoint) { + if (typeof endpoint === "string") { + // accept host.name:port and assume http protocol + this.url = hasProtocol(endpoint) ? endpoint : "http://" + endpoint; + } else { + this.url = endpoint.url; + this.headers = endpoint.headers; + } } public disconnect(): void { @@ -49,7 +84,7 @@ export class HttpClient implements RpcClient { } public async execute(request: JsonRpcRequest): Promise { - const response = parseJsonRpcResponse(await http("POST", this.url, request)); + const response = parseJsonRpcResponse(await http("POST", this.url, this.headers, request)); if (isJsonRpcErrorResponse(response)) { throw new Error(JSON.stringify(response.error)); } diff --git a/packages/tendermint-rpc/src/rpcclients/index.ts b/packages/tendermint-rpc/src/rpcclients/index.ts index 31cff4b0..bff29531 100644 --- a/packages/tendermint-rpc/src/rpcclients/index.ts +++ b/packages/tendermint-rpc/src/rpcclients/index.ts @@ -1,5 +1,5 @@ // This folder contains Tendermint-specific RPC clients -export { HttpClient } from "./httpclient"; +export { HttpClient, HttpEndpoint } from "./httpclient"; export { instanceOfRpcStreamingClient, RpcClient, RpcStreamingClient, SubscriptionEvent } from "./rpcclient"; export { WebsocketClient } from "./websocketclient"; diff --git a/packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts index c55cd751..a5fb0310 100644 --- a/packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts +++ b/packages/tendermint-rpc/src/rpcclients/rpcclient.spec.ts @@ -6,7 +6,7 @@ import { WebsocketClient } from "./websocketclient"; function pendingWithoutTendermint(): void { if (!process.env.TENDERMINT_ENABLED) { - pending("Set TENDERMINT_ENABLED to enable tendermint rpc tests"); + pending("Set TENDERMINT_ENABLED to enable Tendermint RPC tests"); } } diff --git a/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts b/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts index d8496af4..8c88a450 100644 --- a/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts +++ b/packages/tendermint-rpc/src/rpcclients/websocketclient.spec.ts @@ -9,7 +9,7 @@ import { WebsocketClient } from "./websocketclient"; function pendingWithoutTendermint(): void { if (!process.env.TENDERMINT_ENABLED) { - pending("Set TENDERMINT_ENABLED to enable tendermint rpc tests"); + pending("Set TENDERMINT_ENABLED to enable Tendermint RPC tests"); } } diff --git a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts index 05176d60..a0220eb4 100644 --- a/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts +++ b/packages/tendermint-rpc/src/tendermint34/tendermint34client.ts @@ -4,6 +4,7 @@ import { Stream } from "xstream"; import { createJsonRpcRequest } from "../jsonrpc"; import { HttpClient, + HttpEndpoint, instanceOfRpcStreamingClient, RpcClient, SubscriptionEvent, @@ -19,10 +20,14 @@ export class Tendermint34Client { * * Uses HTTP when the URL schema is http or https. Uses WebSockets otherwise. */ - public static async connect(url: string): Promise { - const useHttp = url.startsWith("http://") || url.startsWith("https://"); - const rpcClient = useHttp ? new HttpClient(url) : new WebsocketClient(url); - return Tendermint34Client.create(rpcClient); + public static async connect(endpoint: string | HttpEndpoint): Promise { + if (typeof endpoint === "object") { + return Tendermint34Client.create(new HttpClient(endpoint)); + } else { + const useHttp = endpoint.startsWith("http://") || endpoint.startsWith("https://"); + const rpcClient = useHttp ? new HttpClient(endpoint) : new WebsocketClient(endpoint); + return Tendermint34Client.create(rpcClient); + } } /** diff --git a/packages/tendermint-rpc/webpack.web.config.js b/packages/tendermint-rpc/webpack.web.config.js index 260f3154..d72c8f89 100644 --- a/packages/tendermint-rpc/webpack.web.config.js +++ b/packages/tendermint-rpc/webpack.web.config.js @@ -16,7 +16,10 @@ module.exports = [ filename: "tests.js", }, plugins: [ - new webpack.EnvironmentPlugin({ TENDERMINT_ENABLED: "" }), + new webpack.EnvironmentPlugin({ + HTTPSERVER_ENABLED: "", + TENDERMINT_ENABLED: "", + }), new webpack.ProvidePlugin({ Buffer: ["buffer", "Buffer"], }), diff --git a/scripts/httpserver/Dockerfile b/scripts/httpserver/Dockerfile new file mode 100644 index 00000000..15971432 --- /dev/null +++ b/scripts/httpserver/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.9-alpine + +WORKDIR /usr/src/app + +COPY echo.py ./ + +ENTRYPOINT ["python", "./echo.py"] diff --git a/scripts/httpserver/echo.py b/scripts/httpserver/echo.py new file mode 100755 index 00000000..4582d552 --- /dev/null +++ b/scripts/httpserver/echo.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +#pylint:disable=missing-docstring,invalid-name + +import argparse +from http.server import HTTPServer, BaseHTTPRequestHandler +import json +import sys + +HOST = "0.0.0.0" + +def log(data): + print(data, flush=True) + +class CORSRequestHandler(BaseHTTPRequestHandler): + def end_headers(self): + self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Headers", "*") + BaseHTTPRequestHandler.end_headers(self) + + def do_OPTIONS(self): + self.send_response(200) + self.end_headers() + + def do_GET(self): + """Respond to a GET request.""" + if self.path == "/echo_headers": + self.send_response(200) + self.send_header("Content-type", "text/plain") + self.send_header('Content-type', 'application/json') + self.end_headers() + body = { + "request_headers": dict(self.headers) + } + self.wfile.write(json.dumps(body, sort_keys=True).encode()) + else: + self.send_response(404) + self.wfile.write("404. Try /echo_headers".encode()) + + def do_POST(self): + self.do_GET() + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("--port", + help="Port to listen on", + type=int, + default=5555) + args = parser.parse_args() + httpd = HTTPServer((HOST, args.port), CORSRequestHandler) + log("Starting server at {}:{}".format(HOST, args.port)) + httpd.serve_forever() + log("Running now.") diff --git a/scripts/httpserver/start.sh b/scripts/httpserver/start.sh new file mode 100755 index 00000000..95546d9b --- /dev/null +++ b/scripts/httpserver/start.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +# Please keep this in sync with the Ports overview in HACKING.md +DEFAULT_PORT_GUEST="5555" +DEFAULT_PORT_HOST="5555" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +HTTPSERVER_DIR=$(mktemp -d "${TMPDIR:-/tmp}/httpserver.XXXXXXXXX") +export HTTPSERVER_DIR +echo "HTTPSERVER_DIR = $HTTPSERVER_DIR" + +IMAGE_NAME="httpserver:local" +CONTAINER_NAME="httpserver" + +LOGFILE_DEFAULT="${HTTPSERVER_DIR}/httpserver_$DEFAULT_PORT_HOST.log" + +docker build -t "$IMAGE_NAME" "$SCRIPT_DIR" + +docker run --rm \ + --user="$UID" \ + --name "$CONTAINER_NAME" \ + -p "$DEFAULT_PORT_HOST:$DEFAULT_PORT_GUEST" \ + "$IMAGE_NAME" \ + >"$LOGFILE_DEFAULT" & + +# Debug start +sleep 3 +cat "$LOGFILE_DEFAULT" diff --git a/scripts/httpserver/stop.sh b/scripts/httpserver/stop.sh new file mode 100755 index 00000000..74d04dcb --- /dev/null +++ b/scripts/httpserver/stop.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -o errexit -o nounset -o pipefail +command -v shellcheck >/dev/null && shellcheck "$0" + +CONTAINER_NAME="httpserver" + +echo "Killing socketserver containers ..." +docker container kill "$CONTAINER_NAME" diff --git a/yarn.lock b/yarn.lock index ffd407c2..012c6101 100644 --- a/yarn.lock +++ b/yarn.lock @@ -617,6 +617,8 @@ __metadata: source-map-support: ^0.5.19 ts-node: ^8 typescript: ~4.4 + webpack: ^5.32.0 + webpack-cli: ^4.6.0 bin: cosmos-faucet: bin/cosmos-faucet languageName: unknown