From 2ea473f686aa77364e49087476eeda3616d8f3d6 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 13:54:00 -0300 Subject: [PATCH 01/16] all: Update package.json and yarn.lock --- package.json | 4 +- packages/indexer-agent/package.json | 17 +- packages/indexer-cli/package.json | 4 +- packages/indexer-common/package.json | 21 +- packages/indexer-native/package.json | 2 +- packages/indexer-service/package.json | 6 +- yarn.lock | 1321 +++++++++++++------------ 7 files changed, 750 insertions(+), 625 deletions(-) diff --git a/package.json b/package.json index 223e00446..b04cb5498 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ }, "resolutions": { "ethers": "5.7.0", - "sequelize": "6.19.0", + "sequelize": "6.31.1", "@ethersproject/bignumber": "5.7.0", "@ethersproject/providers": "5.7.0", "@urql/core": "2.4.4", @@ -34,7 +34,7 @@ }, "overrides": { "ethers": "5.7.0", - "sequelize": "6.19.0", + "sequelize": "6.31.1", "@ethersproject/bignumber": "5.7.0", "@ethersproject/providers": "5.7.0", "@urql/core": "2.4.4", diff --git a/packages/indexer-agent/package.json b/packages/indexer-agent/package.json index ecaa09c6f..cacb7d79b 100644 --- a/packages/indexer-agent/package.json +++ b/packages/indexer-agent/package.json @@ -14,10 +14,10 @@ "scripts": { "format": "prettier --write 'src/**/*.ts'", "lint": "eslint . --ext .ts,.tsx --fix", - "compile": "tsc", + "compile": "tsc --build", "prepare": "yarn format && yarn lint && yarn compile", "start": "node ./dist/index.js start", - "test": "jest --colors --verbose", + "test": "jest --colors --verbose --detectOpenHandles", "test:ci": "jest --verbose --ci", "clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo", "migrator:pending": "node src/db/cli/migrator pending", @@ -43,6 +43,9 @@ "graphql-tag": "2.12.6", "isomorphic-fetch": "3.0.0", "jayson": "3.6.6", + "lodash.isequal": "^4.5.0", + "lodash.mapvalues": "^4.6.0", + "lodash.zip": "^4.2.0", "ngeohash": "0.6.3", "p-filter": "2.1.0", "p-map": "4.0.0", @@ -51,12 +54,18 @@ "p-retry": "4.6.1", "umzug": "3.0.0", "yaml": "^2.0.0-10", - "yargs": "17.4.1" + "yargs": "17.4.1", + "zod": "^3.21.4", + "zod-validation-error": "^1.3.0" }, "devDependencies": { "@types/bs58": "4.0.1", "@types/isomorphic-fetch": "0.0.36", "@types/jest": "27.4.1", + "@types/lodash.countby": "^4.6.7", + "@types/lodash.isequal": "^4.5.6", + "@types/lodash.mapvalues": "^4.6.7", + "@types/lodash.zip": "^4.2.7", "@types/ngeohash": "0.6.4", "@types/node": "17.0.23", "@types/yargs": "17.0.10", @@ -73,7 +82,7 @@ }, "resolutions": { "ethers": "5.7.0", - "sequelize": "6.19.0", + "sequelize": "6.31.1", "@ethersproject/bignumber": "5.7.0", "@ethersproject/providers": "5.7.0" }, diff --git a/packages/indexer-cli/package.json b/packages/indexer-cli/package.json index 3e71c9886..2c51aa356 100644 --- a/packages/indexer-cli/package.json +++ b/packages/indexer-cli/package.json @@ -22,7 +22,7 @@ "clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo", "test": "jest --colors --verbose --runInBand --detectOpenHandles", "test:ci": "LOG_LEVEL=error jest --verbose --runInBand --ci", - "test:debug": "LOG_LEVEL=debug jest --runInBand --detectOpenHandles --verbose --forceExit", + "test:debug": "LOG_LEVEL=debug jest --runInBand --detectOpenHandles --verbose", "test:watch": "jest --watch --detectOpenHandles --verbose" }, "dependencies": { @@ -52,7 +52,7 @@ }, "resolutions": { "ethers": "5.7.0", - "sequelize": "6.19.0" + "sequelize": "6.31.1" }, "gitHead": "972ab96774007b2aee15b1da169d2ff4be9f9d27" } diff --git a/packages/indexer-common/package.json b/packages/indexer-common/package.json index 55ee854b4..404900038 100644 --- a/packages/indexer-common/package.json +++ b/packages/indexer-common/package.json @@ -15,9 +15,9 @@ "lint": "eslint . --ext .ts,.tsx --fix", "compile": "tsc", "prepare": "yarn format && yarn lint && yarn compile", - "test": "LOG_LEVEL=info jest --colors --verbose --forceExit --runInBand", - "test:ci": "LOG_LEVEL=error jest --verbose --runInBand --forceExit --ci", - "test:debug": "LOG_LEVEL=debug jest --runInBand --detectOpenHandles --verbose --forceExit", + "test": "LOG_LEVEL=info jest --colors --verbose --runInBand --detectOpenHandles", + "test:ci": "LOG_LEVEL=info jest --verbose --maxWorkers=1 --ci", + "test:debug": "LOG_LEVEL=debug jest --runInBand --detectOpenHandles --verbose", "test:watch": "jest --runInBand --detectOpenHandles --watch --passWithNoTests --verbose", "clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo" }, @@ -25,6 +25,7 @@ "@graphprotocol/common-ts": "2.0.1", "@graphprotocol/cost-model": "0.1.16", "@thi.ng/heaps": "1.2.38", + "@types/lodash.xor": "^4.5.7", "@urql/core": "2.4.4", "@urql/exchange-execute": "1.2.2", "axios": "0.26.1", @@ -37,6 +38,9 @@ "graphql": "16.3.0", "graphql-tag": "2.12.6", "jayson": "3.6.6", + "lodash.groupby": "^4.6.0", + "lodash.isequal": "^4.5.0", + "lodash.xor": "^4.5.0", "morgan": "1.10.0", "ngeohash": "0.6.3", "p-filter": "2.1.0", @@ -44,16 +48,21 @@ "p-reduce": "2.1.0", "p-retry": "4.6.1", "p-timeout": "4.1.0", - "sequelize": "6.19.0", - "ts-custom-error": "^3.2.0" + "parsimmon": "^1.18.1", + "sequelize": "6.31.1", + "ts-custom-error": "^3.2.0", + "zod": "^3.21.4" }, "devDependencies": { "@types/cors": "2.8.12", "@types/express": "4.17.13", "@types/jest": "27.4.1", + "@types/lodash.groupby": "^4.6.7", + "@types/lodash.isequal": "^4.5.6", "@types/morgan": "1.9.2", "@types/ngeohash": "0.6.4", "@types/node": "17.0.23", + "@types/parsimmon": "^1.10.6", "@typescript-eslint/eslint-plugin": "5.22.0", "@typescript-eslint/parser": "5.22.0", "eslint": "8.14.0", @@ -65,7 +74,7 @@ }, "resolutions": { "ethers": "5.7.0", - "sequelize": "6.19.0", + "sequelize": "6.31.1", "@ethersproject/bignumber": "5.7.0", "@urql/exchange-execute/@urql/core": "2.4.4" }, diff --git a/packages/indexer-native/package.json b/packages/indexer-native/package.json index 394b9a995..3c0eeced2 100644 --- a/packages/indexer-native/package.json +++ b/packages/indexer-native/package.json @@ -41,7 +41,7 @@ "lint": "eslint .", "prepare": "yarn format && yarn lint", "install": "yarn pull-or-build", - "test": "jest --colors --verbose --forceExit", + "test": "jest --colors --verbose", "test:ci": "jest --verbose --ci", "clean": "rm -rf ./node_modules ./binary ./build ./coverage ./native/target ./native/artifacts.json ./native/index.node" }, diff --git a/packages/indexer-service/package.json b/packages/indexer-service/package.json index f268f98ea..35c3d9505 100644 --- a/packages/indexer-service/package.json +++ b/packages/indexer-service/package.json @@ -14,11 +14,11 @@ "scripts": { "format": "prettier --write 'src/**/*.ts'", "lint": "eslint . --ext .ts,.tsx --fix", - "compile": "tsc", + "compile": "tsc --build", "prepare": "yarn format && yarn lint && yarn compile", "start": "node ./dist/index.js start", - "test": "jest --colors --verbose --forceExit", - "test:ci": "jest --verbose --forceExit --ci", + "test": "jest --colors --verbose --detectOpenHandles", + "test:ci": "jest --verbose --ci", "test:watch": "jest --watch --passWithNoTests --detectOpenHandles --verbose", "clean": "rm -rf ./node_modules ./dist ./tsconfig.tsbuildinfo" }, diff --git a/yarn.lock b/yarn.lock index 3f0851367..cec112ecd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,7 +4,7 @@ "@ampproject/remapping@^2.1.0": version "2.2.0" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== dependencies: "@jridgewell/gen-mapping" "^0.1.0" @@ -37,12 +37,12 @@ "@babel/compat-data@^7.17.10": version "7.18.5" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz#acac0c839e317038c73137fbb6ef71a1d6238471" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz" integrity sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg== "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": version "7.18.5" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.5.tgz#c597fa680e58d571c28dda9827669c78cdd7f000" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.18.5.tgz" integrity sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ== dependencies: "@ampproject/remapping" "^2.1.0" @@ -63,7 +63,7 @@ "@babel/generator@^7.18.2", "@babel/generator@^7.7.2": version "7.18.2" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz" integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== dependencies: "@babel/types" "^7.18.2" @@ -72,7 +72,7 @@ "@babel/helper-compilation-targets@^7.18.2": version "7.18.2" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz#67a85a10cbd5fc7f1457fec2e7f45441dc6c754b" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.2.tgz" integrity sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ== dependencies: "@babel/compat-data" "^7.17.10" @@ -82,12 +82,12 @@ "@babel/helper-environment-visitor@^7.16.7", "@babel/helper-environment-visitor@^7.18.2": version "7.18.2" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz#8a6d2dedb53f6bf248e31b4baf38739ee4a637bd" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.2.tgz" integrity sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ== "@babel/helper-function-name@^7.17.9": version "7.17.9" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz" integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== dependencies: "@babel/template" "^7.16.7" @@ -95,21 +95,21 @@ "@babel/helper-hoist-variables@^7.16.7": version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz" integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: "@babel/types" "^7.16.7" "@babel/helper-module-imports@^7.16.7": version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz" integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: "@babel/types" "^7.16.7" "@babel/helper-module-transforms@^7.18.0": version "7.18.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz#baf05dec7a5875fb9235bd34ca18bad4e21221cd" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz" integrity sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA== dependencies: "@babel/helper-environment-visitor" "^7.16.7" @@ -123,19 +123,19 @@ "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.17.12", "@babel/helper-plugin-utils@^7.8.0": version "7.17.12" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96" + resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz" integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA== "@babel/helper-simple-access@^7.17.7": version "7.18.2" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz#4dc473c2169ac3a1c9f4a51cfcd091d1c36fcff9" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.2.tgz" integrity sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ== dependencies: "@babel/types" "^7.18.2" "@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz" integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: "@babel/types" "^7.16.7" @@ -147,12 +147,12 @@ "@babel/helper-validator-option@^7.16.7": version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz" integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== "@babel/helpers@^7.18.2": version "7.18.2" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz#970d74f0deadc3f5a938bfa250738eb4ac889384" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.2.tgz" integrity sha512-j+d+u5xT5utcQSzrh9p+PaJX94h++KN+ng9b9WEJq7pkUPAd61FGqhjuUEdfknb3E/uDBb7ruwEeKkIxNJPIrg== dependencies: "@babel/template" "^7.16.7" @@ -170,103 +170,103 @@ "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7", "@babel/parser@^7.18.5": version "7.18.5" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.5.tgz#337062363436a893a2d22faa60be5bb37091c83c" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.5.tgz" integrity sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": version "7.17.12" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz#b54fc3be6de734a56b87508f99d6428b5b605a7b" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.12.tgz" integrity sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw== dependencies: "@babel/helper-plugin-utils" "^7.17.12" "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz" integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== dependencies: "@babel/code-frame" "^7.16.7" @@ -275,7 +275,7 @@ "@babel/traverse@^7.18.0", "@babel/traverse@^7.18.2", "@babel/traverse@^7.18.5", "@babel/traverse@^7.7.2": version "7.18.5" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz#94a8195ad9642801837988ab77f36e992d9a20cd" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz" integrity sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA== dependencies: "@babel/code-frame" "^7.16.7" @@ -291,7 +291,7 @@ "@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.17.0", "@babel/types@^7.18.0", "@babel/types@^7.18.2", "@babel/types@^7.18.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3": version "7.18.4" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz" integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== dependencies: "@babel/helper-validator-identifier" "^7.16.7" @@ -299,24 +299,24 @@ "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@cspotcode/source-map-consumer@0.8.0": version "0.8.0" - resolved "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + resolved "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz" integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== "@cspotcode/source-map-support@0.7.0": version "0.7.0" - resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz" integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== dependencies: "@cspotcode/source-map-consumer" "0.8.0" "@eslint/eslintrc@^1.2.1", "@eslint/eslintrc@^1.2.2": version "1.3.0" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz" integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" @@ -331,7 +331,7 @@ "@ethersproject/abi@5.7.0", "@ethersproject/abi@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + resolved "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz" integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== dependencies: "@ethersproject/address" "^5.7.0" @@ -346,7 +346,7 @@ "@ethersproject/abstract-provider@5.7.0", "@ethersproject/abstract-provider@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + resolved "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz" integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== dependencies: "@ethersproject/bignumber" "^5.7.0" @@ -359,7 +359,7 @@ "@ethersproject/abstract-signer@5.7.0", "@ethersproject/abstract-signer@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + resolved "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz" integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== dependencies: "@ethersproject/abstract-provider" "^5.7.0" @@ -370,7 +370,7 @@ "@ethersproject/address@5.7.0", "@ethersproject/address@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + resolved "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz" integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== dependencies: "@ethersproject/bignumber" "^5.7.0" @@ -381,14 +381,14 @@ "@ethersproject/base64@5.7.0", "@ethersproject/base64@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + resolved "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz" integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== dependencies: "@ethersproject/bytes" "^5.7.0" "@ethersproject/basex@5.7.0", "@ethersproject/basex@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + resolved "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz" integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -396,7 +396,7 @@ "@ethersproject/bignumber@5.7.0", "@ethersproject/bignumber@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + resolved "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz" integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -405,21 +405,21 @@ "@ethersproject/bytes@5.7.0", "@ethersproject/bytes@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + resolved "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz" integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== dependencies: "@ethersproject/logger" "^5.7.0" "@ethersproject/constants@5.7.0", "@ethersproject/constants@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + resolved "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz" integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== dependencies: "@ethersproject/bignumber" "^5.7.0" "@ethersproject/contracts@5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + resolved "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz" integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== dependencies: "@ethersproject/abi" "^5.7.0" @@ -435,7 +435,7 @@ "@ethersproject/hash@5.7.0", "@ethersproject/hash@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + resolved "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz" integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== dependencies: "@ethersproject/abstract-signer" "^5.7.0" @@ -450,7 +450,7 @@ "@ethersproject/hdnode@5.7.0", "@ethersproject/hdnode@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + resolved "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz" integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== dependencies: "@ethersproject/abstract-signer" "^5.7.0" @@ -468,7 +468,7 @@ "@ethersproject/json-wallets@5.7.0", "@ethersproject/json-wallets@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + resolved "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz" integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== dependencies: "@ethersproject/abstract-signer" "^5.7.0" @@ -487,7 +487,7 @@ "@ethersproject/keccak256@5.7.0", "@ethersproject/keccak256@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + resolved "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz" integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -495,26 +495,26 @@ "@ethersproject/logger@5.7.0", "@ethersproject/logger@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + resolved "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz" integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== "@ethersproject/networks@5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.0.tgz#df72a392f1a63a57f87210515695a31a245845ad" + resolved "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.0.tgz" integrity sha512-MG6oHSQHd4ebvJrleEQQ4HhVu8Ichr0RDYEfHzsVAVjHNM+w36x9wp9r+hf1JstMXtseXDtkiVoARAG6M959AA== dependencies: "@ethersproject/logger" "^5.7.0" "@ethersproject/networks@^5.7.0": version "5.7.1" - resolved "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + resolved "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz" integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== dependencies: "@ethersproject/logger" "^5.7.0" "@ethersproject/pbkdf2@5.7.0", "@ethersproject/pbkdf2@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + resolved "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz" integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -522,14 +522,14 @@ "@ethersproject/properties@5.7.0", "@ethersproject/properties@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + resolved "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz" integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== dependencies: "@ethersproject/logger" "^5.7.0" "@ethersproject/providers@5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.0.tgz#a885cfc7650a64385e7b03ac86fe9c2d4a9c2c63" + resolved "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.0.tgz" integrity sha512-+TTrrINMzZ0aXtlwO/95uhAggKm4USLm1PbeCBR/3XZ7+Oey+3pMyddzZEyRhizHpy1HXV0FRWRMI1O3EGYibA== dependencies: "@ethersproject/abstract-provider" "^5.7.0" @@ -555,7 +555,7 @@ "@ethersproject/random@5.7.0", "@ethersproject/random@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + resolved "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz" integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -563,7 +563,7 @@ "@ethersproject/rlp@5.7.0", "@ethersproject/rlp@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + resolved "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz" integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -571,7 +571,7 @@ "@ethersproject/sha2@5.7.0", "@ethersproject/sha2@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + resolved "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz" integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -580,7 +580,7 @@ "@ethersproject/signing-key@5.7.0", "@ethersproject/signing-key@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + resolved "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz" integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -592,7 +592,7 @@ "@ethersproject/solidity@5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz#5e9c911d8a2acce2a5ebb48a5e2e0af20b631cb8" + resolved "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz" integrity sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA== dependencies: "@ethersproject/bignumber" "^5.7.0" @@ -604,7 +604,7 @@ "@ethersproject/strings@5.7.0", "@ethersproject/strings@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + resolved "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz" integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -613,7 +613,7 @@ "@ethersproject/transactions@5.7.0", "@ethersproject/transactions@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + resolved "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz" integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== dependencies: "@ethersproject/address" "^5.7.0" @@ -628,7 +628,7 @@ "@ethersproject/units@5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + resolved "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz" integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== dependencies: "@ethersproject/bignumber" "^5.7.0" @@ -637,7 +637,7 @@ "@ethersproject/wallet@5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + resolved "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz" integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== dependencies: "@ethersproject/abstract-provider" "^5.7.0" @@ -658,7 +658,7 @@ "@ethersproject/web@5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.0.tgz#40850c05260edad8b54827923bbad23d96aac0bc" + resolved "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.0.tgz" integrity sha512-ApHcbbj+muRASVDSCl/tgxaH2LBkRMEYfLOLVa0COipx0+nlu0QKet7U2lEg0vdkh8XRSLf2nd1f1Uk9SrVSGA== dependencies: "@ethersproject/base64" "^5.7.0" @@ -669,7 +669,7 @@ "@ethersproject/web@^5.7.0": version "5.7.1" - resolved "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + resolved "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz" integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== dependencies: "@ethersproject/base64" "^5.7.0" @@ -680,7 +680,7 @@ "@ethersproject/wordlists@5.7.0", "@ethersproject/wordlists@^5.7.0": version "5.7.0" - resolved "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + resolved "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz" integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== dependencies: "@ethersproject/bytes" "^5.7.0" @@ -698,7 +698,7 @@ "@gar/promisify@^1.1.3": version "1.1.3" - resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== "@google-cloud/common@^3.0.0", "@google-cloud/common@^3.4.1": @@ -777,7 +777,7 @@ "@graphprotocol/common-ts@2.0.1": version "2.0.1" - resolved "https://registry.npmjs.org/@graphprotocol/common-ts/-/common-ts-2.0.1.tgz#2f3518fe257b262f6194a35075eeafa6ad9faa2e" + resolved "https://registry.npmjs.org/@graphprotocol/common-ts/-/common-ts-2.0.1.tgz" integrity sha512-dmcTWVOS4HEvglMtH0wZTMeqlzniLAo+k04kZ7/cY+QgLXAYB7X1Sz3sHqkGmNbn89kBQ3Z/nPDLLSlln69hYg== dependencies: "@graphprotocol/contracts" "2.1.0" @@ -804,7 +804,7 @@ "@graphprotocol/contracts@2.1.0": version "2.1.0" - resolved "https://registry.npmjs.org/@graphprotocol/contracts/-/contracts-2.1.0.tgz#c2e9131973af231e3f6f84ada31baebccc77ed48" + resolved "https://registry.npmjs.org/@graphprotocol/contracts/-/contracts-2.1.0.tgz" integrity sha512-SeymJCUxBp488K/KNi77EKvrkPT0t/7rERGp8zFkmf5KByerjihhE9Ty9oXJAU9RzbUpxsU0JkPBSmiw9/2DYQ== dependencies: console-table-printer "^2.11.1" @@ -812,7 +812,7 @@ "@graphprotocol/cost-model@0.1.16": version "0.1.16" - resolved "https://registry.npmjs.org/@graphprotocol/cost-model/-/cost-model-0.1.16.tgz#42e6889a23934f293f119db9ac23159be019df0c" + resolved "https://registry.npmjs.org/@graphprotocol/cost-model/-/cost-model-0.1.16.tgz" integrity sha512-oDBhXH4sdKLduw0PgcK6rWPrzqJ4eFH9BTWz81c2iMIdTrm3n92eixCrhTwB3Nd1JpM1y0Bz9sMIvRGvC6htoA== dependencies: "@mapbox/node-pre-gyp" "1.0.9" @@ -947,7 +947,7 @@ "@humanwhocodes/config-array@^0.9.2": version "0.9.5" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz" integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== dependencies: "@humanwhocodes/object-schema" "^1.2.1" @@ -956,7 +956,7 @@ "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" - resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@hutson/parse-repository-url@^3.0.0": @@ -971,12 +971,12 @@ "@isaacs/string-locale-compare@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz#291c227e93fd407a96ecd59879a35809120e432b" + resolved "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz" integrity sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -987,12 +987,12 @@ "@istanbuljs/schema@^0.1.2": version "0.1.3" - resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== "@jest/console@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" + resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz" integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg== dependencies: "@jest/types" "^27.5.1" @@ -1004,7 +1004,7 @@ "@jest/core@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" + resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz" integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ== dependencies: "@jest/console" "^27.5.1" @@ -1038,7 +1038,7 @@ "@jest/environment@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz" integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA== dependencies: "@jest/fake-timers" "^27.5.1" @@ -1048,7 +1048,7 @@ "@jest/fake-timers@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz" integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ== dependencies: "@jest/types" "^27.5.1" @@ -1060,7 +1060,7 @@ "@jest/globals@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz" integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q== dependencies: "@jest/environment" "^27.5.1" @@ -1069,7 +1069,7 @@ "@jest/reporters@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz" integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw== dependencies: "@bcoe/v8-coverage" "^0.2.3" @@ -1100,7 +1100,7 @@ "@jest/source-map@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz" integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg== dependencies: callsites "^3.0.0" @@ -1109,7 +1109,7 @@ "@jest/test-result@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz" integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag== dependencies: "@jest/console" "^27.5.1" @@ -1119,7 +1119,7 @@ "@jest/test-sequencer@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz" integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ== dependencies: "@jest/test-result" "^27.5.1" @@ -1129,7 +1129,7 @@ "@jest/transform@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz" integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw== dependencies: "@babel/core" "^7.1.0" @@ -1150,7 +1150,7 @@ "@jest/types@^27.5.1": version "27.5.1" - resolved "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + resolved "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz" integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" @@ -1161,7 +1161,7 @@ "@jridgewell/gen-mapping@^0.1.0": version "0.1.1" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== dependencies: "@jridgewell/set-array" "^1.0.0" @@ -1169,7 +1169,7 @@ "@jridgewell/gen-mapping@^0.3.0": version "0.3.1" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz" integrity sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg== dependencies: "@jridgewell/set-array" "^1.0.0" @@ -1178,22 +1178,22 @@ "@jridgewell/resolve-uri@^3.0.3": version "3.0.7" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz" integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== "@jridgewell/set-array@^1.0.0": version "1.1.1" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.1.tgz" integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ== "@jridgewell/sourcemap-codec@^1.4.10": version "1.4.13" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== "@jridgewell/trace-mapping@^0.3.9": version "0.3.13" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz" integrity sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w== dependencies: "@jridgewell/resolve-uri" "^3.0.3" @@ -1201,7 +1201,7 @@ "@lerna/add@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/add/-/add-6.1.0.tgz#0f09495c5e1af4c4f316344af34b6d1a91b15b19" + resolved "https://registry.npmjs.org/@lerna/add/-/add-6.1.0.tgz" integrity sha512-f2cAeS1mE/p7QvSRn5TCgdUXw6QVbu8PeRxaTOxTThhTdJIWdXZfY00QjAsU6jw1PdYXK1qGUSwWOPkdR16mBg== dependencies: "@lerna/bootstrap" "6.1.0" @@ -1217,7 +1217,7 @@ "@lerna/bootstrap@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-6.1.0.tgz#81738f32cd431814c9943dfffe28752587d90830" + resolved "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-6.1.0.tgz" integrity sha512-aDxKqgxexVj/Z0B1aPu7P1iPbPqhk1FPkl/iayCmPlkAh90pYEH0uVytGzi1hFB5iXEfG7Pa6azGQywUodx/1g== dependencies: "@lerna/command" "6.1.0" @@ -1245,7 +1245,7 @@ "@lerna/changed@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/changed/-/changed-6.1.0.tgz#4fa480cbb0e7106ea9dad30d315e953975118d06" + resolved "https://registry.npmjs.org/@lerna/changed/-/changed-6.1.0.tgz" integrity sha512-p7C2tf1scmvoUC1Osck/XIKVKXAQ8m8neL8/rfgKSYsvUVjsOB1LbF5HH1VUZntE6S4OxkRxUQGkAHVf5xrGqw== dependencies: "@lerna/collect-updates" "6.1.0" @@ -1255,7 +1255,7 @@ "@lerna/check-working-tree@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-6.1.0.tgz#b8970fd27a26449b12456d5d0ece60477aa54e15" + resolved "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-6.1.0.tgz" integrity sha512-hSciDmRqsNPevMhAD+SYbnhjatdb7UUu9W8vTyGtUXkrq2xtRZU0vAOgqovV8meirRkbC41pZePYKqyQtF0y3w== dependencies: "@lerna/collect-uncommitted" "6.1.0" @@ -1264,7 +1264,7 @@ "@lerna/child-process@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/child-process/-/child-process-6.1.0.tgz#6361f7945cd5b36e983f819de3cd91c315707302" + resolved "https://registry.npmjs.org/@lerna/child-process/-/child-process-6.1.0.tgz" integrity sha512-jhr3sCFeps6Y15SCrWEPvqE64i+QLOTSh+OzxlziCBf7ZEUu7sF0yA4n5bAqw8j43yCKhhjkf/ZLYxZe+pnl3Q== dependencies: chalk "^4.1.0" @@ -1273,7 +1273,7 @@ "@lerna/clean@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/clean/-/clean-6.1.0.tgz#1114fd90ad82438123726e2493d3550e73abebbc" + resolved "https://registry.npmjs.org/@lerna/clean/-/clean-6.1.0.tgz" integrity sha512-LRK2hiNUiBhPe5tmJiefOVpkaX2Yob0rp15IFNIbuteRWUJg0oERFQo62WvnxwElfzKSOhr8OGuEq/vN4bMrRA== dependencies: "@lerna/command" "6.1.0" @@ -1287,7 +1287,7 @@ "@lerna/cli@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/cli/-/cli-6.1.0.tgz#41214331fa4c1ea5f41125befdd81b009fe12640" + resolved "https://registry.npmjs.org/@lerna/cli/-/cli-6.1.0.tgz" integrity sha512-p4G/OSPIrHiNkEl8bXrQdFOh4ORAZp2+ljvbXmAxpdf2qmopaUdr+bZYtIAxd+Z42SxRnDNz9IEyR0kOsARRQQ== dependencies: "@lerna/global-options" "6.1.0" @@ -1297,7 +1297,7 @@ "@lerna/collect-uncommitted@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-6.1.0.tgz#b6ffd7adda24d73b70304210967d3518caa3529d" + resolved "https://registry.npmjs.org/@lerna/collect-uncommitted/-/collect-uncommitted-6.1.0.tgz" integrity sha512-VvWvqDZG+OiF4PwV4Ro695r3+8ty4w+11Bnq8tbsbu5gq8qZiam8Fkc/TQLuNNqP0SPi4qmMPaIzWvSze3SmDg== dependencies: "@lerna/child-process" "6.1.0" @@ -1306,7 +1306,7 @@ "@lerna/collect-updates@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-6.1.0.tgz#75fcc0733b5a9ac318a6484b890aa4061b7859c2" + resolved "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-6.1.0.tgz" integrity sha512-dgH7kgstwCXFctylQ4cxuCmhwSIE6VJZfHdh2bOaLuncs6ATMErKWN/mVuFHuUWEqPDRyy5Ky40Cu9S40nUq5w== dependencies: "@lerna/child-process" "6.1.0" @@ -1317,7 +1317,7 @@ "@lerna/command@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/command/-/command-6.1.0.tgz#bcb12516f2c181822b3b5be46c18eadc9b61e885" + resolved "https://registry.npmjs.org/@lerna/command/-/command-6.1.0.tgz" integrity sha512-OnMqBDaEBY0C8v9CXIWFbGGKgsiUtZrnKVvQRbupMSZDKMpVGWIUd3X98Is9j9MAmk1ynhBMWE9Fwai5ML/mcA== dependencies: "@lerna/child-process" "6.1.0" @@ -1333,7 +1333,7 @@ "@lerna/conventional-commits@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-6.1.0.tgz#1157bb66d84d48880dc5c5026d743cedf0f47094" + resolved "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-6.1.0.tgz" integrity sha512-Tipo3cVr8mNVca4btzrCIzct59ZJWERT8/ZCZ/TQWuI4huUJZs6LRofLtB0xsGJAVZ7Vz2WRXAeH4XYgeUxutQ== dependencies: "@lerna/validation-error" "6.1.0" @@ -1349,7 +1349,7 @@ "@lerna/create-symlink@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-6.1.0.tgz#d4260831f5d10abc0c70f0a8f39bea91db87e640" + resolved "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-6.1.0.tgz" integrity sha512-ulMa5OUJEwEWBHSgCUNGxrcsJllq1YMYWqhufvIigmMPJ0Zv3TV1Hha5i2MsqLJAakxtW0pNuwdutkUTtUdgxQ== dependencies: cmd-shim "^5.0.0" @@ -1358,7 +1358,7 @@ "@lerna/create@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/create/-/create-6.1.0.tgz#cde219da46a7c5062c558366b4ffce2134f13845" + resolved "https://registry.npmjs.org/@lerna/create/-/create-6.1.0.tgz" integrity sha512-ZqlknXu0L29cV5mcfNgBLl+1RbKTWmNk8mj545zgXc7qQDgmrY+EVvrs8Cirey8C7bBpVkzP7Brzze0MSoB4rQ== dependencies: "@lerna/child-process" "6.1.0" @@ -1380,7 +1380,7 @@ "@lerna/describe-ref@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-6.1.0.tgz#60f0b8297b912aa5fe5e6ab8ef6c4127813681a7" + resolved "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-6.1.0.tgz" integrity sha512-0RQAYnxBaMz1SrEb/rhfR+8VeZx5tvCNYKRee5oXIDZdQ2c6/EPyrKCp3WcqiuOWY50SfGOVfxJEcxpK8Y3FNA== dependencies: "@lerna/child-process" "6.1.0" @@ -1388,7 +1388,7 @@ "@lerna/diff@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/diff/-/diff-6.1.0.tgz#bfa9bc35894d88a33fa0a3a5787082dea45d8cb2" + resolved "https://registry.npmjs.org/@lerna/diff/-/diff-6.1.0.tgz" integrity sha512-GhP+jPDbcp9QcAMSAjFn4lzM8MKpLR1yt5jll+zUD831U1sL0I5t8HUosFroe5MoRNffEL/jHuI3SbC3jjqWjQ== dependencies: "@lerna/child-process" "6.1.0" @@ -1398,7 +1398,7 @@ "@lerna/exec@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/exec/-/exec-6.1.0.tgz#a2d165576471ff61e33c49952d40a5dbc36fc78f" + resolved "https://registry.npmjs.org/@lerna/exec/-/exec-6.1.0.tgz" integrity sha512-Ej6WlPHXLF6hZHsfD+J/dxeuTrnc0HIfIXR1DU//msHW5RNCdi9+I7StwreCAQH/dLEsdBjPg5chNmuj2JLQRg== dependencies: "@lerna/child-process" "6.1.0" @@ -1411,7 +1411,7 @@ "@lerna/filter-options@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-6.1.0.tgz#f4ee65d0db0273ce490ce6c72c9dbb1d23268ca6" + resolved "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-6.1.0.tgz" integrity sha512-kPf92Z7uLsR6MUiXnyXWebaUWArLa15wLfpfTwIp5H3MNk1lTbuG7QnrxE7OxQj+ozFmBvXeV9fuwfLsYTfmOw== dependencies: "@lerna/collect-updates" "6.1.0" @@ -1421,7 +1421,7 @@ "@lerna/filter-packages@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-6.1.0.tgz#1ddac63a6ffdf5f058d206be5adfb39ad7aaf4f9" + resolved "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-6.1.0.tgz" integrity sha512-zW2avsZHs/ITE/37AEMhegGVHjiD0rgNk9bguNDfz6zaPa90UaW6PWDH6Tf4ThPRlbkl2Go48N3bFYHYSJKbcw== dependencies: "@lerna/validation-error" "6.1.0" @@ -1430,14 +1430,14 @@ "@lerna/get-npm-exec-opts@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-6.1.0.tgz#22351e2ebc4adbef21ca4b86187278e15e4cb38a" + resolved "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-6.1.0.tgz" integrity sha512-10Pdf+W0z7RT34o0SWlf+WVzz2/WbnTIJ1tQqXvXx6soj2L/xGLhOPvhJiKNtl4WlvUiO/zQ91yb83ESP4TZaA== dependencies: npmlog "^6.0.2" "@lerna/get-packed@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-6.1.0.tgz#b6d1c1dd1e068212e784b8dfc2e5fe64741ea8db" + resolved "https://registry.npmjs.org/@lerna/get-packed/-/get-packed-6.1.0.tgz" integrity sha512-lg0wPpV0wPekcD0mebJp619hMxsOgbZDOH5AkL/bCR217391eha0iPhQ0dU/G0Smd2vv6Cg443+J5QdI4LGRTg== dependencies: fs-extra "^9.1.0" @@ -1446,7 +1446,7 @@ "@lerna/github-client@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/github-client/-/github-client-6.1.0.tgz#cd33743e4529a0b822ae6716cb4b981e1d8ffe8f" + resolved "https://registry.npmjs.org/@lerna/github-client/-/github-client-6.1.0.tgz" integrity sha512-+/4PtDgsjt0VRRZtOCN2Piyu0asU/16gSZZy/opVb8dlT44lTrH/ZghrJLE4tSL8Nuv688kx0kSgbUG8BY54jQ== dependencies: "@lerna/child-process" "6.1.0" @@ -1457,7 +1457,7 @@ "@lerna/gitlab-client@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/gitlab-client/-/gitlab-client-6.1.0.tgz#bbcbf80d937e5980798ac1e0edd1f769101057d8" + resolved "https://registry.npmjs.org/@lerna/gitlab-client/-/gitlab-client-6.1.0.tgz" integrity sha512-fUI/ppXzxJafN9ceSl+FDgsYvu3iTsO6UW0WTD63pS32CfM+PiCryLQHzuc4RkyVW8WQH3aCR/GbaKCqbu52bw== dependencies: node-fetch "^2.6.1" @@ -1465,12 +1465,12 @@ "@lerna/global-options@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/global-options/-/global-options-6.1.0.tgz#268e1de924369102e47babd9288086764ec6f9e6" + resolved "https://registry.npmjs.org/@lerna/global-options/-/global-options-6.1.0.tgz" integrity sha512-1OyJ/N1XJh3ZAy8S20c6th9C4yBm/k3bRIdC+z0XxpDaHwfNt8mT9kUIDt6AIFCUvVKjSwnIsMHwhzXqBnwYSA== "@lerna/has-npm-version@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-6.1.0.tgz#a5d960213d1a7ca5374eb3c551a17b322b9a9e62" + resolved "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-6.1.0.tgz" integrity sha512-up5PVuP6BmKQ5/UgH/t2c5B1q4HhjwW3/bqbNayX6V0qNz8OijnMYvEUbxFk8fOdeN41qVnhAk0Tb5kbdtYh2A== dependencies: "@lerna/child-process" "6.1.0" @@ -1478,7 +1478,7 @@ "@lerna/import@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/import/-/import-6.1.0.tgz#1c64281e3431c43c9cd140b66a6a51427afe7095" + resolved "https://registry.npmjs.org/@lerna/import/-/import-6.1.0.tgz" integrity sha512-xsBhiKLUavATR32dAFL+WFY0yuab0hsM1eztKtRKk4wy7lSyxRfA5EIUcNCsLXx2xaDOKoMncCTXgNcpeYuqcQ== dependencies: "@lerna/child-process" "6.1.0" @@ -1492,7 +1492,7 @@ "@lerna/info@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/info/-/info-6.1.0.tgz#a5d66a9c1f18398dc020a6f6073c399013081587" + resolved "https://registry.npmjs.org/@lerna/info/-/info-6.1.0.tgz" integrity sha512-CsrWdW/Wyb4kcvHSnrsm7KYWFvjUNItu+ryeyWBZJtWYQOv45jNmWix6j2L4/w1+mMlWMjsfLmBscg82UBrF5w== dependencies: "@lerna/command" "6.1.0" @@ -1501,7 +1501,7 @@ "@lerna/init@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/init/-/init-6.1.0.tgz#b178775693b9c38c0f3fe3300eeb574cf76e0297" + resolved "https://registry.npmjs.org/@lerna/init/-/init-6.1.0.tgz" integrity sha512-z8oUeVjn+FQYAtepAw6G47cGodLyBAyNoEjO3IsJjQLWE1yH3r83L2sjyD/EckgR3o2VTEzrKo4ArhxLp2mNmg== dependencies: "@lerna/child-process" "6.1.0" @@ -1513,7 +1513,7 @@ "@lerna/link@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/link/-/link-6.1.0.tgz#f6f0cfd0b02aecdeb304ce614e4e4e89fe0a3ad5" + resolved "https://registry.npmjs.org/@lerna/link/-/link-6.1.0.tgz" integrity sha512-7OD2lYNQHl6Kl1KYmplt8KoWjVHdiaqpYqwD38AwcB09YN58nGmo4aJgC12Fdx8DSNjkumgM0ROg/JOjMCTIzQ== dependencies: "@lerna/command" "6.1.0" @@ -1525,7 +1525,7 @@ "@lerna/list@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/list/-/list-6.1.0.tgz#a7625bceb5224c4bf1154e715c07ea29f9698bac" + resolved "https://registry.npmjs.org/@lerna/list/-/list-6.1.0.tgz" integrity sha512-7/g2hjizkvVnBGpVm+qC7lUFGhZ/0GIMUbGQwnE6yXDGm8yP9aEcNVkU4JGrDWW+uIklf9oodnMHaLXd/FJe6Q== dependencies: "@lerna/command" "6.1.0" @@ -1535,7 +1535,7 @@ "@lerna/listable@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/listable/-/listable-6.1.0.tgz#2510045fde7bc568b18172a5d24372a719bb5c4c" + resolved "https://registry.npmjs.org/@lerna/listable/-/listable-6.1.0.tgz" integrity sha512-3KZ9lQ9AtNfGNH/mYJYaMKCiF2EQvLLBGYkWHeIzIs6foegcZNXe0Cyv3LNXuo5WslMNr5RT4wIgy3BOoAxdtg== dependencies: "@lerna/query-graph" "6.1.0" @@ -1544,7 +1544,7 @@ "@lerna/log-packed@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-6.1.0.tgz#18ae946e8b7881f2fc5b973cc6682cc599b1759b" + resolved "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-6.1.0.tgz" integrity sha512-Sq2HZJAcPuoNeEHeIutcPYQCyWBxLyVGvEhgsP3xTe6XkBGQCG8piCp9wX+sc2zT+idPdpI6qLqdh85yYIMMhA== dependencies: byte-size "^7.0.0" @@ -1554,7 +1554,7 @@ "@lerna/npm-conf@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-6.1.0.tgz#79697260c9d14ffb9d892927f37fcde75b89ec58" + resolved "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-6.1.0.tgz" integrity sha512-+RD3mmJe9XSQj7Diibs0+UafAHPcrFCd29ODpDI+tzYl4MmYZblfrlL6mbSCiVYCZQneQ8Uku3P0r+DlbYBaFw== dependencies: config-chain "^1.1.12" @@ -1562,7 +1562,7 @@ "@lerna/npm-dist-tag@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-6.1.0.tgz#29f843aa628687a29dc3a9b905dd3002db7a3820" + resolved "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-6.1.0.tgz" integrity sha512-1zo+Yww/lvWJWZnEXpke9dZSb5poDzhUM/pQNqAQYSlbZ96o18SuCR6TEi5isMPiw63Aq1MMzbUqttQfJ11EOA== dependencies: "@lerna/otplease" "6.1.0" @@ -1572,7 +1572,7 @@ "@lerna/npm-install@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-6.1.0.tgz#b75d1f152540a144bd6c81586a9f6010ed7f3046" + resolved "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-6.1.0.tgz" integrity sha512-1SHmOHZA1YJuUctLQBRjA2+yMp+UNYdOBsFb3xUVT7MjWnd1Zl0toT3jxGu96RNErD9JKkk/cGo/Aq+DU3s9pg== dependencies: "@lerna/child-process" "6.1.0" @@ -1585,7 +1585,7 @@ "@lerna/npm-publish@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-6.1.0.tgz#8fe561e639e6a06380354271aeca7cbc39acf7dd" + resolved "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-6.1.0.tgz" integrity sha512-N0LdR1ImZQw1r4cYaKtVbBhBPtj4Zu9NbvygzizEP5HuTfxZmE1Ans3w93Kks9VTXZXob8twNbXnzBwzTyEpEA== dependencies: "@lerna/otplease" "6.1.0" @@ -1599,7 +1599,7 @@ "@lerna/npm-run-script@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-6.1.0.tgz#bc5bd414ee9696168d88d8ce78f8e8b715967100" + resolved "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-6.1.0.tgz" integrity sha512-7p13mvdxdY5+VqWvvtMsMDeyCRs0PrrTmSHRO+FKuLQuGhBvUo05vevcMEOQNDvEvl/tXPrOVbeGCiGubYTCLg== dependencies: "@lerna/child-process" "6.1.0" @@ -1608,21 +1608,21 @@ "@lerna/otplease@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/otplease/-/otplease-6.1.0.tgz#d25dbe2d867215b69f06de12ab4ff559d83d1d01" + resolved "https://registry.npmjs.org/@lerna/otplease/-/otplease-6.1.0.tgz" integrity sha512-gqSE6IbaD4IeNJePkaDLaFLoGp0Ceu35sn7z0AHAOoHiQGGorOmvM+h1Md3xZZRSXQmY9LyJVhG5eRa38SoG4g== dependencies: "@lerna/prompt" "6.1.0" "@lerna/output@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/output/-/output-6.1.0.tgz#d470146c6ee8ee063fd416081c1ca64fb132c4d8" + resolved "https://registry.npmjs.org/@lerna/output/-/output-6.1.0.tgz" integrity sha512-mgCIzLKIuroytXuxjTB689ERtpfgyNXW0rMv9WHOa6ufQc+QJPjh3L4jVsOA0l+/OxZyi97PUXotduNj+0cbnA== dependencies: npmlog "^6.0.2" "@lerna/pack-directory@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-6.1.0.tgz#3252ba7250d826b9922238c775abf5004e7580c4" + resolved "https://registry.npmjs.org/@lerna/pack-directory/-/pack-directory-6.1.0.tgz" integrity sha512-Xsixqm2nkGXs9hvq08ClbGpRlCYnlBV4TwSrLttIDL712RlyXoPe2maJzTUqo9OXBbOumFSahUEInCMT2OS05g== dependencies: "@lerna/get-packed" "6.1.0" @@ -1635,7 +1635,7 @@ "@lerna/package-graph@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-6.1.0.tgz#2373617605f48f53b5fa9d13188838b6c09022b0" + resolved "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-6.1.0.tgz" integrity sha512-yGyxd/eHTDjkpnBbDhTV0hwKF+i01qZc+6/ko65wOsh8xtgqpQeE6mtdgbvsLKcuMcIQ7PDy1ntyIv9phg14gQ== dependencies: "@lerna/prerelease-id-from-version" "6.1.0" @@ -1646,7 +1646,7 @@ "@lerna/package@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/package/-/package-6.1.0.tgz#e9e33876c0509a86c1b676045b19fd3f7f1c77e2" + resolved "https://registry.npmjs.org/@lerna/package/-/package-6.1.0.tgz" integrity sha512-PyNFtdH2IcLasp/nyMDshmeXotriOSlhbeFIxhdl1XuGj5v1so3utMSOrJMO5kzZJQg5zyx8qQoxL+WH/hkrVQ== dependencies: load-json-file "^6.2.0" @@ -1655,14 +1655,14 @@ "@lerna/prerelease-id-from-version@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-6.1.0.tgz#4ee5beeef4e81d77001e94ec5613c140b6615616" + resolved "https://registry.npmjs.org/@lerna/prerelease-id-from-version/-/prerelease-id-from-version-6.1.0.tgz" integrity sha512-ngC4I6evvZztB6aOaSDEnhUgRTlqX3TyBXwWwLGTOXCPaCQBTPaLNokhmRdJ+ZVdZ4iHFbzEDSL07ubZrYUcmQ== dependencies: semver "^7.3.4" "@lerna/profiler@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/profiler/-/profiler-6.1.0.tgz#aae2249f1a39c79db72a548ce50bf32f86a0f3a5" + resolved "https://registry.npmjs.org/@lerna/profiler/-/profiler-6.1.0.tgz" integrity sha512-WFDQNpuqPqMJLg8llvrBHF8Ib5Asgp23lMeNUe89T62NUX6gkjVBTYdjsduxM0tZH6Pa0GAGaQcha97P6fxfdQ== dependencies: fs-extra "^9.1.0" @@ -1671,7 +1671,7 @@ "@lerna/project@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/project/-/project-6.1.0.tgz#605afe28fb15d8b8b890fafe0ec1da2700964056" + resolved "https://registry.npmjs.org/@lerna/project/-/project-6.1.0.tgz" integrity sha512-EOkfjjrTM16c3GUxGqcfYD2stV35p9mBEmkF41NPmyjfbzjol/irDF1r6Q7BsQSRsdClMJRCeZ168xdSxC2X0A== dependencies: "@lerna/package" "6.1.0" @@ -1690,7 +1690,7 @@ "@lerna/prompt@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/prompt/-/prompt-6.1.0.tgz#98e228220428d33620822f77e39f592ce29c776c" + resolved "https://registry.npmjs.org/@lerna/prompt/-/prompt-6.1.0.tgz" integrity sha512-981J/C53TZ2l2mFVlWJN7zynSzf5GEHKvKQa12Td9iknhASZOuwTAWb6eq46246Ant6W5tWwb0NSPu3I5qtcrA== dependencies: inquirer "^8.2.4" @@ -1698,7 +1698,7 @@ "@lerna/publish@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/publish/-/publish-6.1.0.tgz#9d62c327bc3541a0430951d726b39a2fb17b7925" + resolved "https://registry.npmjs.org/@lerna/publish/-/publish-6.1.0.tgz" integrity sha512-XtvuydtU0IptbAapLRgoN1AZj/WJR+e3UKnx9BQ1Dwc+Fpg2oqPxR/vi+6hxAsr95pdQ5CnWBdgS+dg2wEUJ7Q== dependencies: "@lerna/check-working-tree" "6.1.0" @@ -1732,21 +1732,21 @@ "@lerna/pulse-till-done@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/pulse-till-done/-/pulse-till-done-6.1.0.tgz#df0112a9a5b8547b53d18742ce21104eb360d731" + resolved "https://registry.npmjs.org/@lerna/pulse-till-done/-/pulse-till-done-6.1.0.tgz" integrity sha512-a2RVT82E4R9nVXtehzp2TQL6iXp0QfEM3bu8tBAR/SfI1A9ggZWQhuuUqtRyhhVCajdQDOo7rS0UG7R5JzK58w== dependencies: npmlog "^6.0.2" "@lerna/query-graph@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-6.1.0.tgz#e78c47c78d4691231fc379570e036bc2753cf6fa" + resolved "https://registry.npmjs.org/@lerna/query-graph/-/query-graph-6.1.0.tgz" integrity sha512-YkyCc+6aR7GlCOcZXEKPcl5o5L2v+0YUNs59JrfAS0mctFosZ/2tP7pkdu2SI4qXIi5D0PMNsh/0fRni56znsQ== dependencies: "@lerna/package-graph" "6.1.0" "@lerna/resolve-symlink@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-6.1.0.tgz#5a8686b99c838bc6e869930e5b5fd582607ebbe7" + resolved "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-6.1.0.tgz" integrity sha512-8ILO+h5fsE0q8MSLfdL+MT1GEsNhAB1fDyMkSsYgLRCsssN/cViZbffpclZyT/EfAhpyKfBCHZ0CmT1ZGofU1A== dependencies: fs-extra "^9.1.0" @@ -1755,7 +1755,7 @@ "@lerna/rimraf-dir@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-6.1.0.tgz#75559585d5921563eff0e206bb9ec8ab0cc967c6" + resolved "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-6.1.0.tgz" integrity sha512-J9YeGHkCCeAIzsnKURYeGECBexiIii6HA+Bbd+rAgoKPsNCOj6ql4+qJE8Jbd7fQEFNDPQeBCYvM7JcdMc0WSA== dependencies: "@lerna/child-process" "6.1.0" @@ -1765,7 +1765,7 @@ "@lerna/run-lifecycle@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-6.1.0.tgz#e1fa6cd300842ef1d688af77648fed05ec2d5345" + resolved "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-6.1.0.tgz" integrity sha512-GbTdKxL+hWHEPgyBEKtqY9Nf+jFlt6YLtP5VjEVc5SdLkm+FeRquar9/YcZVUbzr3c+NJwWNgVjHuePfowdpUA== dependencies: "@lerna/npm-conf" "6.1.0" @@ -1775,7 +1775,7 @@ "@lerna/run-topologically@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-6.1.0.tgz#8f1a428b5d4b800bced178edabfa2262b328572f" + resolved "https://registry.npmjs.org/@lerna/run-topologically/-/run-topologically-6.1.0.tgz" integrity sha512-kpTaSBKdKjtf61be8Z1e7TIaMt/aksfxswQtpFxEuKDsPsdHfR8htSkADO4d/3SZFtmcAHIHNCQj9CaNj4O4Xw== dependencies: "@lerna/query-graph" "6.1.0" @@ -1783,7 +1783,7 @@ "@lerna/run@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/run/-/run-6.1.0.tgz#efaea1acc78cb7fc73b4906be70002e118628d64" + resolved "https://registry.npmjs.org/@lerna/run/-/run-6.1.0.tgz" integrity sha512-vlEEKPcTloiob6EK7gxrjEdB6fQQ/LNfWhSJCGxJlvNVbrMpoWIu0Kpp20b0nE+lzX7rRJ4seWr7Wdo/Fjub4Q== dependencies: "@lerna/command" "6.1.0" @@ -1799,7 +1799,7 @@ "@lerna/symlink-binary@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-6.1.0.tgz#7d476499b86ae5fcb853c510603cff9a27acf105" + resolved "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-6.1.0.tgz" integrity sha512-DaiRNZk/dvomNxgEaTW145PyL7vIGP7rvnfXV2FO+rjX8UUSNUOjmVmHlYfs64gV9Eqx/dLfQClIbKcwYMD83A== dependencies: "@lerna/create-symlink" "6.1.0" @@ -1809,7 +1809,7 @@ "@lerna/symlink-dependencies@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-6.1.0.tgz#f44d33e043fed21a366c4ced2cbde8fa8be0c5fc" + resolved "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-6.1.0.tgz" integrity sha512-hrTvtY1Ek+fLA4JjXsKsvwPjuJD0rwB/+K4WY57t00owj//BpCsJ37w3kkkS7f/PcW/5uRjCuHcY67LOEwsRxw== dependencies: "@lerna/create-symlink" "6.1.0" @@ -1821,7 +1821,7 @@ "@lerna/temp-write@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/temp-write/-/temp-write-6.1.0.tgz#a5d532090dd7b2d4f8965fbb475376aae06b9242" + resolved "https://registry.npmjs.org/@lerna/temp-write/-/temp-write-6.1.0.tgz" integrity sha512-ZcQl88H9HbQ/TeWUOVt+vDYwptm7kwprGvj9KkZXr9S5Bn6SiKRQOeydCCfCrQT+9Q3dm7QZXV6rWzLsACcAlQ== dependencies: graceful-fs "^4.1.15" @@ -1832,19 +1832,19 @@ "@lerna/timer@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/timer/-/timer-6.1.0.tgz#245b02c05b2dec6d2aed2da8a0962cf0343d83d5" + resolved "https://registry.npmjs.org/@lerna/timer/-/timer-6.1.0.tgz" integrity sha512-du+NQ9q7uO4d2nVU4AD2DSPuAZqUapA/bZKuVpFVxvY9Qhzb8dQKLsFISe4A9TjyoNAk8ZeWK0aBc/6N+Qer9A== "@lerna/validation-error@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-6.1.0.tgz#03bd46f6219b6db7c4420528d5aaf047f92693e3" + resolved "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-6.1.0.tgz" integrity sha512-q0c3XCi5OpyTr8AcfbisS6e3svZaJF/riCvBDqRMaQUT4A8QOPzB4fVF3/+J2u54nidBuTlIk0JZu9aOdWTUkQ== dependencies: npmlog "^6.0.2" "@lerna/version@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/version/-/version-6.1.0.tgz#44d8649e978df9d6a14d97c9d7631a7dcd4a9cbf" + resolved "https://registry.npmjs.org/@lerna/version/-/version-6.1.0.tgz" integrity sha512-RUxVFdzHt0739lRNMrAbo6HWcFrcyG7atM1pn+Eo61fUoA5R/9N4bCk4m9xUGkJ/mOcROjuwAGe+wT1uOs58Bg== dependencies: "@lerna/check-working-tree" "6.1.0" @@ -1877,7 +1877,7 @@ "@lerna/write-log-file@6.1.0": version "6.1.0" - resolved "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-6.1.0.tgz#b811cffd2ea2b3be6239a756c64dac9a3795707a" + resolved "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-6.1.0.tgz" integrity sha512-09omu2w4NCt8mJH/X9ZMuToQQ3xu/KpC7EU4yDl2Qy8nxKf8HiG8Oe+YYNprngmkdsq60F5eUZvoiFDZ5JeGIg== dependencies: npmlog "^6.0.2" @@ -1885,7 +1885,7 @@ "@mapbox/node-pre-gyp@1.0.10": version "1.0.10" - resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c" + resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz" integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA== dependencies: detect-libc "^2.0.0" @@ -1941,7 +1941,7 @@ "@npmcli/arborist@5.3.0": version "5.3.0" - resolved "https://registry.npmjs.org/@npmcli/arborist/-/arborist-5.3.0.tgz#321d9424677bfc08569e98a5ac445ee781f32053" + resolved "https://registry.npmjs.org/@npmcli/arborist/-/arborist-5.3.0.tgz" integrity sha512-+rZ9zgL1lnbl8Xbb1NQdMjveOMwj4lIYfcDtyJHHi5x4X8jtR6m8SXooJMZy5vmFVZ8w7A2Bnd/oX9eTuU8w5A== dependencies: "@isaacs/string-locale-compare" "^1.1.0" @@ -1981,7 +1981,7 @@ "@npmcli/fs@^2.1.0": version "2.1.2" - resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" + resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz" integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== dependencies: "@gar/promisify" "^1.1.3" @@ -1989,7 +1989,7 @@ "@npmcli/git@^3.0.0": version "3.0.2" - resolved "https://registry.npmjs.org/@npmcli/git/-/git-3.0.2.tgz#5c5de6b4d70474cf2d09af149ce42e4e1dacb931" + resolved "https://registry.npmjs.org/@npmcli/git/-/git-3.0.2.tgz" integrity sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w== dependencies: "@npmcli/promise-spawn" "^3.0.0" @@ -2004,7 +2004,7 @@ "@npmcli/installed-package-contents@^1.0.7": version "1.0.7" - resolved "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz#ab7408c6147911b970a8abe261ce512232a3f4fa" + resolved "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz" integrity sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw== dependencies: npm-bundled "^1.1.1" @@ -2012,7 +2012,7 @@ "@npmcli/map-workspaces@^2.0.3": version "2.0.4" - resolved "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.4.tgz#9e5e8ab655215a262aefabf139782b894e0504fc" + resolved "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.4.tgz" integrity sha512-bMo0aAfwhVwqoVM5UzX1DJnlvVvzDCHae821jv48L1EsrYwfOZChlqWYXEtto/+BkBXetPbEWgau++/brh4oVg== dependencies: "@npmcli/name-from-folder" "^1.0.1" @@ -2022,7 +2022,7 @@ "@npmcli/metavuln-calculator@^3.0.1": version "3.1.1" - resolved "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-3.1.1.tgz#9359bd72b400f8353f6a28a25c8457b562602622" + resolved "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-3.1.1.tgz" integrity sha512-n69ygIaqAedecLeVH3KnO39M6ZHiJ2dEv5A7DGvcqCB8q17BGUgW8QaanIkbWUo2aYGZqJaOORTLAlIvKjNDKA== dependencies: cacache "^16.0.0" @@ -2032,7 +2032,7 @@ "@npmcli/move-file@^2.0.0": version "2.0.1" - resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" + resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz" integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== dependencies: mkdirp "^1.0.4" @@ -2040,31 +2040,31 @@ "@npmcli/name-from-folder@^1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz#77ecd0a4fcb772ba6fe927e2e2e155fbec2e6b1a" + resolved "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz" integrity sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA== "@npmcli/node-gyp@^2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz#8c20e53e34e9078d18815c1d2dda6f2420d75e35" + resolved "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-2.0.0.tgz" integrity sha512-doNI35wIe3bBaEgrlPfdJPaCpUR89pJWep4Hq3aRdh6gKazIVWfs0jHttvSSoq47ZXgC7h73kDsUl8AoIQUB+A== "@npmcli/package-json@^2.0.0": version "2.0.0" - resolved "https://registry.npmjs.org/@npmcli/package-json/-/package-json-2.0.0.tgz#3bbcf4677e21055adbe673d9f08c9f9cde942e4a" + resolved "https://registry.npmjs.org/@npmcli/package-json/-/package-json-2.0.0.tgz" integrity sha512-42jnZ6yl16GzjWSH7vtrmWyJDGVa/LXPdpN2rcUWolFjc9ON2N3uz0qdBbQACfmhuJZ2lbKYtmK5qx68ZPLHMA== dependencies: json-parse-even-better-errors "^2.3.1" "@npmcli/promise-spawn@^3.0.0": version "3.0.0" - resolved "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz#53283b5f18f855c6925f23c24e67c911501ef573" + resolved "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-3.0.0.tgz" integrity sha512-s9SgS+p3a9Eohe68cSI3fi+hpcZUmXq5P7w0kMlAsWVtR7XbK3ptkZqKT2cK1zLDObJ3sR+8P59sJE0w/KTL1g== dependencies: infer-owner "^1.0.4" "@npmcli/run-script@^4.1.0", "@npmcli/run-script@^4.1.3", "@npmcli/run-script@^4.1.7": version "4.2.1" - resolved "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.1.tgz#c07c5c71bc1c70a5f2a06b0d4da976641609b946" + resolved "https://registry.npmjs.org/@npmcli/run-script/-/run-script-4.2.1.tgz" integrity sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg== dependencies: "@npmcli/node-gyp" "^2.0.0" @@ -2075,14 +2075,14 @@ "@nrwl/cli@15.3.0": version "15.3.0" - resolved "https://registry.npmjs.org/@nrwl/cli/-/cli-15.3.0.tgz#61b145d2ba613f9df4dbb9188e631ca50a4e42cb" + resolved "https://registry.npmjs.org/@nrwl/cli/-/cli-15.3.0.tgz" integrity sha512-WAki2+puBp6qel/VAxdQmr/L/sLyw8K6bynYNmMl4eIlR5hjefrUChPzUiJDAS9/CUYQNOyva2VV5wofzdv95w== dependencies: nx "15.3.0" "@nrwl/devkit@>=14.8.6 < 16": version "15.3.0" - resolved "https://registry.npmjs.org/@nrwl/devkit/-/devkit-15.3.0.tgz#4b0fc4c94f0b92413aa3d028f8cc75f586936d27" + resolved "https://registry.npmjs.org/@nrwl/devkit/-/devkit-15.3.0.tgz" integrity sha512-1O9QLB/eYS6ddw4MZnV4yj4CEqLIbpleZZiG/9w1TaiVO/jfNfXVaxc8EA87XSzMpk2W+/4Qggmabt6gAQaabA== dependencies: "@phenomnomnominal/tsquery" "4.1.1" @@ -2093,28 +2093,28 @@ "@nrwl/tao@15.3.0": version "15.3.0" - resolved "https://registry.npmjs.org/@nrwl/tao/-/tao-15.3.0.tgz#20266f1269815cb28e21677b0aa7f913a7e31b17" + resolved "https://registry.npmjs.org/@nrwl/tao/-/tao-15.3.0.tgz" integrity sha512-alyzKKSgfgPwQ/FUozvk43VGOZHyNMiSM6Udl49ZaQwT77GXRFkrOu21odW6dciWPd3iUOUjfJISNqrEJmxvpw== dependencies: nx "15.3.0" "@octokit/auth-token@^2.4.0", "@octokit/auth-token@^2.4.4": version "2.5.0" - resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz" + resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz#27c37ea26c205f28443402477ffd261311f21e36" integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== dependencies: "@octokit/types" "^6.0.3" "@octokit/auth-token@^3.0.0": version "3.0.2" - resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz#a0fc8de149fd15876e1ac78f6525c1c5ab48435f" + resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz" integrity sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q== dependencies: "@octokit/types" "^8.0.0" "@octokit/core@3.2.0": version "3.2.0" - resolved "https://registry.npmjs.org/@octokit/core/-/core-3.2.0.tgz" + resolved "https://registry.npmjs.org/@octokit/core/-/core-3.2.0.tgz#7a872ad4cb8d8d2f417dd7fe1aaff3919c09dc04" integrity sha512-42jzu1GWlCr4KUo52X4hD3if2AwjNJLzsS8mqUs9JkJbsM3vzvSx8AqTnVBQjOM0hQMYBqR7/7SAUTfH7IZqIg== dependencies: "@octokit/auth-token" "^2.4.0" @@ -2139,7 +2139,7 @@ "@octokit/core@^4.1.0": version "4.1.0" - resolved "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz#b6b03a478f1716de92b3f4ec4fd64d05ba5a9251" + resolved "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz" integrity sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ== dependencies: "@octokit/auth-token" "^3.0.0" @@ -2161,7 +2161,7 @@ "@octokit/endpoint@^7.0.0": version "7.0.3" - resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz#0b96035673a9e3bedf8bab8f7335de424a2147ed" + resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz" integrity sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw== dependencies: "@octokit/types" "^8.0.0" @@ -2170,7 +2170,7 @@ "@octokit/graphql@^4.3.1", "@octokit/graphql@^4.5.8": version "4.8.0" - resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz" + resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz#664d9b11c0e12112cbf78e10f49a05959aa22cc3" integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== dependencies: "@octokit/request" "^5.6.0" @@ -2179,7 +2179,7 @@ "@octokit/graphql@^5.0.0": version "5.0.4" - resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz#519dd5c05123868276f3ae4e50ad565ed7dff8c8" + resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz" integrity sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A== dependencies: "@octokit/request" "^6.0.0" @@ -2193,7 +2193,7 @@ "@octokit/openapi-types@^14.0.0": version "14.0.0" - resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz" integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== "@octokit/plugin-enterprise-rest@^6.0.1": @@ -2210,7 +2210,7 @@ "@octokit/plugin-paginate-rest@^5.0.0": version "5.0.1" - resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz#93d7e74f1f69d68ba554fa6b888c2a9cf1f99a83" + resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-5.0.1.tgz" integrity sha512-7A+rEkS70pH36Z6JivSlR7Zqepz3KVucEFVDnSrgHXzG7WLAzYwcHZbKdfTXHwuTHbkT1vKvz7dHl1+HNf6Qyw== dependencies: "@octokit/types" "^8.0.0" @@ -2230,7 +2230,7 @@ "@octokit/plugin-rest-endpoint-methods@^6.7.0": version "6.7.0" - resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz#2f6f17f25b6babbc8b41d2bb0a95a8839672ce7c" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz" integrity sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw== dependencies: "@octokit/types" "^8.0.0" @@ -2247,7 +2247,7 @@ "@octokit/request-error@^3.0.0": version "3.0.2" - resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz#f74c0f163d19463b87528efe877216c41d6deb0a" + resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz" integrity sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg== dependencies: "@octokit/types" "^8.0.0" @@ -2268,7 +2268,7 @@ "@octokit/request@^6.0.0": version "6.2.2" - resolved "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz#a2ba5ac22bddd5dcb3f539b618faa05115c5a255" + resolved "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz" integrity sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw== dependencies: "@octokit/endpoint" "^7.0.0" @@ -2290,7 +2290,7 @@ "@octokit/rest@^19.0.3": version "19.0.5" - resolved "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.5.tgz#4dbde8ae69b27dca04b5f1d8119d282575818f6c" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.5.tgz" integrity sha512-+4qdrUFq2lk7Va+Qff3ofREQWGBeoTKNqlJO+FGjFP35ZahP+nBenhZiGdu8USSgmq4Ky3IJ/i4u0xbLqHaeow== dependencies: "@octokit/core" "^4.1.0" @@ -2300,7 +2300,7 @@ "@octokit/types@^5.0.0": version "5.5.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz" + resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" integrity sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ== dependencies: "@types/node" ">= 8" @@ -2314,14 +2314,14 @@ "@octokit/types@^8.0.0": version "8.0.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz#93f0b865786c4153f0f6924da067fe0bb7426a9f" + resolved "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz" integrity sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg== dependencies: "@octokit/openapi-types" "^14.0.0" "@parcel/watcher@2.0.4": version "2.0.4" - resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz#f300fef4cc38008ff4b8c29d92588eced3ce014b" + resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.0.4.tgz" integrity sha512-cTDi+FUDBIUOBKEtj+nhiJ71AZVlkAsQFuGQTun5tV9mwQBQgZvhCzG+URPQc8myeN32yRVZEfVAPCs1RW+Jvg== dependencies: node-addon-api "^3.2.1" @@ -2329,7 +2329,7 @@ "@phenomnomnominal/tsquery@4.1.1": version "4.1.1" - resolved "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-4.1.1.tgz#42971b83590e9d853d024ddb04a18085a36518df" + resolved "https://registry.npmjs.org/@phenomnomnominal/tsquery/-/tsquery-4.1.1.tgz" integrity sha512-jjMmK1tnZbm1Jq5a7fBliM4gQwjxMU7TFoRNwIyzwlO+eHPRCFv/Nv+H/Gi1jc3WR7QURG8D5d0Tn12YGrUqBQ== dependencies: esquery "^1.0.1" @@ -2467,14 +2467,14 @@ "@sinonjs/commons@^1.7.0": version "1.8.3" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz" integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^8.0.1": version "8.1.0" - resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz" integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg== dependencies: "@sinonjs/commons" "^1.7.0" @@ -2623,22 +2623,22 @@ "@tsconfig/node10@^1.0.7": version "1.0.9" - resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== "@tsconfig/node12@^1.0.7": version "1.0.10" - resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz#10fecee4a3be17357ce99b370bd81874044d8dbd" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.10.tgz" integrity sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA== "@tsconfig/node14@^1.0.0": version "1.0.2" - resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz#b09c08de2eb319ca2acab17a1b8028af110b24b3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.2.tgz" integrity sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg== "@tsconfig/node16@^1.0.2": version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== "@types/argparse@1.0.38": @@ -2648,7 +2648,7 @@ "@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14": version "7.1.19" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz" integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== dependencies: "@babel/parser" "^7.1.0" @@ -2659,14 +2659,14 @@ "@types/babel__generator@*": version "7.6.4" - resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.1" - resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" @@ -2674,7 +2674,7 @@ "@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": version "7.17.1" - resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz#1a0e73e8c28c7e832656db372b779bfd2ef37314" + resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.17.1.tgz" integrity sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA== dependencies: "@babel/types" "^7.3.0" @@ -2688,7 +2688,7 @@ "@types/body-parser@*": version "1.19.2" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz" integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== dependencies: "@types/connect" "*" @@ -2696,7 +2696,7 @@ "@types/body-parser@1.19.0": version "1.19.0" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz" integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ== dependencies: "@types/connect" "*" @@ -2704,7 +2704,7 @@ "@types/bs58@4.0.1": version "4.0.1" - resolved "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz#3d51222aab067786d3bc3740a84a7f5a0effaa37" + resolved "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz" integrity sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA== dependencies: base-x "^3.0.6" @@ -2723,12 +2723,12 @@ "@types/cookiejar@*": version "2.1.2" - resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz#66ad9331f63fe8a3d3d9d8c6e3906dd10f6446e8" + resolved "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz" integrity sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog== "@types/cors@2.8.12": version "2.8.12" - resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" + resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== "@types/debug@^4.1.7": @@ -2740,7 +2740,7 @@ "@types/express-rate-limit@^5.1.3": version "5.1.3" - resolved "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-5.1.3.tgz#79f2ca40d90455a5798da6f8e06d8a3d35f4a1d6" + resolved "https://registry.npmjs.org/@types/express-rate-limit/-/express-rate-limit-5.1.3.tgz" integrity sha512-H+TYy3K53uPU2TqPGFYaiWc2xJV6+bIFkDd/Ma2/h67Pa6ARk9kWE0p/K9OH1Okm0et9Sfm66fmXoAxsH2PHXg== dependencies: "@types/express" "*" @@ -2756,7 +2756,7 @@ "@types/express@*", "@types/express@4.17.13": version "4.17.13" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== dependencies: "@types/body-parser" "*" @@ -2766,7 +2766,7 @@ "@types/express@4.17.8": version "4.17.8" - resolved "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz#3df4293293317e61c60137d273a2e96cd8d5f27a" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.8.tgz" integrity sha512-wLhcKh3PMlyA2cNAB9sjM1BntnhPMiM0JOBwPBqttjHev2428MLEB4AYVN+d8s2iyCVZac+o41Pflm/ZH5vLXQ== dependencies: "@types/body-parser" "*" @@ -2776,43 +2776,43 @@ "@types/graceful-fs@^4.1.2": version "4.1.5" - resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz" integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== dependencies: "@types/node" "*" "@types/isomorphic-fetch@0.0.35": version "0.0.35" - resolved "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.35.tgz#c1c0d402daac324582b6186b91f8905340ea3361" + resolved "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.35.tgz" integrity sha512-DaZNUvLDCAnCTjgwxgiL1eQdxIKEpNLOlTNtAgnZc50bG2copGhRrFN9/PxPBuJe+tZVLCbQ7ls0xveXVRPkvw== "@types/isomorphic-fetch@0.0.36": version "0.0.36" - resolved "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz#3a6d8f63974baf26b10fd1314db910633108a769" + resolved "https://registry.npmjs.org/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.36.tgz" integrity sha512-ulw4d+vW1HKn4oErSmNN2HYEcHGq0N1C5exlrMM0CRqX1UUpFhGb5lwiom5j9KN3LBJJDLRmYIZz1ghm7FIzZw== "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" - resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": version "3.0.0" - resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.1" - resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@27.4.1": version "27.4.1" - resolved "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz#185cbe2926eaaf9662d340cc02e548ce9e11ab6d" + resolved "https://registry.npmjs.org/@types/jest/-/jest-27.4.1.tgz" integrity sha512-23iPJADSmicDVrWk+HT58LMJtzLAnB2AgIzplQuq/bSrGaxCrlvRFjGbXmamnnk/mAmCdLStiGqggu28ocUyiw== dependencies: jest-matcher-utils "^27.0.0" @@ -2820,14 +2820,61 @@ "@types/json-schema@^7.0.9": version "7.0.11" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== "@types/json5@^0.0.29": version "0.0.29" - resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash.countby@^4.6.7": + version "4.6.7" + resolved "https://registry.npmjs.org/@types/lodash.countby/-/lodash.countby-4.6.7.tgz" + integrity sha512-RkkfnOXscBXuRc9Iay+tMHtaztdRCtU7doEwi5yLEV/7UHRyy5yS+ppZCzFl06s6lWt6fcHKAN8F6kyes6js5g== + dependencies: + "@types/lodash" "*" + +"@types/lodash.groupby@^4.6.7": + version "4.6.7" + resolved "https://registry.npmjs.org/@types/lodash.groupby/-/lodash.groupby-4.6.7.tgz" + integrity sha512-dFUR1pqdMgjIBbgPJ/8axJX6M1C7zsL+HF4qdYMQeJ7XOp0Qbf37I3zh9gpXr/ks6tgEYPDRqyZRAnFYvewYHQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash.isequal@^4.5.6": + version "4.5.6" + resolved "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.6.tgz" + integrity sha512-Ww4UGSe3DmtvLLJm2F16hDwEQSv7U0Rr8SujLUA2wHI2D2dm8kPu6Et+/y303LfjTIwSBKXB/YTUcAKpem/XEg== + dependencies: + "@types/lodash" "*" + +"@types/lodash.mapvalues@^4.6.7": + version "4.6.7" + resolved "https://registry.npmjs.org/@types/lodash.mapvalues/-/lodash.mapvalues-4.6.7.tgz#a1459b4353c769696b6735f19246753fd25ffc3a" + integrity sha512-yGTo9zD60Iw1Q+YBvx4Ad7RDj5rA6EpvYTpVPVsfe6pwHcwDcGChSqL1n2sPBMbCeHJ3R5kcaqpkdlrFe/y4Vg== + dependencies: + "@types/lodash" "*" + +"@types/lodash.xor@^4.5.7": + version "4.5.7" + resolved "https://registry.npmjs.org/@types/lodash.xor/-/lodash.xor-4.5.7.tgz#a8f218586005476b5bfe14a824aca33ae3b26161" + integrity sha512-Drr3ZdJWjpo7dNR17QdBwgfQRh7kEhhVVdeTVX0v35bT47zpfGSDYo40Ei0oOqk5DQzzxAYuNuW0s/N3mqBwxw== + dependencies: + "@types/lodash" "*" + +"@types/lodash.zip@^4.2.7": + version "4.2.7" + resolved "https://registry.npmjs.org/@types/lodash.zip/-/lodash.zip-4.2.7.tgz#7f59b70e20d957a0fdbdbb907334ac2c470655f2" + integrity sha512-wRtK2bZ0HYXkJkeldrD35qOquGn5GOmp8+o886N18Aqw2DGFLP7JCTEb00j3xQZ+PCMTyfMS2OMbLUwah+bcyg== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.194" + resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.194.tgz" + integrity sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g== + "@types/lodash@^4.14.159": version "4.14.182" resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz" @@ -2840,7 +2887,7 @@ "@types/mime@^1": version "1.3.2" - resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== "@types/minimatch@^3.0.3": @@ -2855,7 +2902,7 @@ "@types/morgan@1.9.2": version "1.9.2" - resolved "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.2.tgz#450f958a4d3fb0694a3ba012b09c8106f9a2885e" + resolved "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.2.tgz" integrity sha512-edtGMEdit146JwwIeyQeHHg9yID4WSolQPxpEorHmN3KuytuCHyn2ELNr5Uxy8SerniFbbkmgKMrGM933am5BQ== dependencies: "@types/node" "*" @@ -2867,19 +2914,24 @@ "@types/ngeohash@0.6.4": version "0.6.4" - resolved "https://registry.npmjs.org/@types/ngeohash/-/ngeohash-0.6.4.tgz#a1ba2c25c4d1ef71f067de247a61490019ea0757" + resolved "https://registry.npmjs.org/@types/ngeohash/-/ngeohash-0.6.4.tgz" integrity sha512-rr20mmx41OkWx4q5du2dv2sESR/6xH2tzScUQXwO8SiaQWa6PYTuan1nqBtA76FR9qkVfZY7nwQwZNC9StX/Ww== -"@types/node@*", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0": +"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": version "17.0.25" resolved "https://registry.npmjs.org/@types/node/-/node-17.0.25.tgz" integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w== "@types/node@17.0.23": version "17.0.23" - resolved "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" + resolved "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz" integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== +"@types/node@>= 8": + version "20.2.5" + resolved "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb" + integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ== + "@types/node@^12.12.54": version "12.20.48" resolved "https://registry.npmjs.org/@types/node/-/node-12.20.48.tgz" @@ -2895,6 +2947,11 @@ resolved "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/parsimmon@^1.10.6": + version "1.10.6" + resolved "https://registry.npmjs.org/@types/parsimmon/-/parsimmon-1.10.6.tgz" + integrity sha512-FwAQwMRbkhx0J6YELkwIpciVzCcgEqXEbIrIn3a2P5d3kGEHQ3wVhlN3YdVepYP+bZzCYO6OjmD4o9TGOZ40rA== + "@types/pbkdf2@^3.0.0": version "3.1.0" resolved "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz" @@ -2904,7 +2961,7 @@ "@types/prettier@^2.1.1", "@types/prettier@^2.1.5": version "2.6.3" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz#68ada76827b0010d0db071f739314fa429943d0a" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.3.tgz" integrity sha512-ymZk3LEC/fsut+/Q5qejp6R9O1rMxz3XaRHDV6kX8MrGAhOSPqVARbDi+EZvInBpw+BnCX3TD240byVkOfQsHg== "@types/qs@*": @@ -2936,7 +2993,7 @@ "@types/serve-static@*": version "1.13.10" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz" integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== dependencies: "@types/mime" "^1" @@ -2944,12 +3001,12 @@ "@types/stack-utils@^2.0.0": version "2.0.1" - resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/superagent@*": version "4.1.15" - resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.15.tgz#63297de457eba5e2bc502a7609426c4cceab434a" + resolved "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.15.tgz" integrity sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ== dependencies: "@types/cookiejar" "*" @@ -2957,7 +3014,7 @@ "@types/supertest@2.0.12": version "2.0.12" - resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz#ddb4a0568597c9aadff8dbec5b2e8fddbe8692fc" + resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz" integrity sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ== dependencies: "@types/superagent" "*" @@ -2995,26 +3052,26 @@ "@types/yargs-parser@*": version "21.0.0" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@17.0.10": version "17.0.10" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.10.tgz" integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== dependencies: "@types/yargs-parser" "*" "@types/yargs@^16.0.0": version "16.0.4" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz" integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== dependencies: "@types/yargs-parser" "*" "@typescript-eslint/eslint-plugin@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz#9608a4b6d0427104bccf132f058cba629a6553c0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.19.0.tgz" integrity sha512-w59GpFqDYGnWFim9p6TGJz7a3qWeENJuAKCqjGSx+Hq/bwq3RZwXYqy98KIfN85yDqz9mq6QXiY5h0FjGQLyEg== dependencies: "@typescript-eslint/scope-manager" "5.19.0" @@ -3029,7 +3086,7 @@ "@typescript-eslint/eslint-plugin@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.22.0.tgz#7b52a0de2e664044f28b36419210aea4ab619e2a" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.22.0.tgz" integrity sha512-YCiy5PUzpAeOPGQ7VSGDEY2NeYUV1B0swde2e0HzokRsHBYjSdF6DZ51OuRZxVPHx0032lXGLvOMls91D8FXlg== dependencies: "@typescript-eslint/scope-manager" "5.22.0" @@ -3044,7 +3101,7 @@ "@typescript-eslint/parser@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.19.0.tgz#05e587c1492868929b931afa0cb5579b0f728e75" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.19.0.tgz" integrity sha512-yhktJjMCJX8BSBczh1F/uY8wGRYrBeyn84kH6oyqdIJwTGKmzX5Qiq49LRQ0Jh0LXnWijEziSo6BRqny8nqLVQ== dependencies: "@typescript-eslint/scope-manager" "5.19.0" @@ -3054,7 +3111,7 @@ "@typescript-eslint/parser@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.22.0.tgz#7bedf8784ef0d5d60567c5ba4ce162460e70c178" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.22.0.tgz" integrity sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ== dependencies: "@typescript-eslint/scope-manager" "5.22.0" @@ -3064,7 +3121,7 @@ "@typescript-eslint/scope-manager@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.19.0.tgz#97e59b0bcbcb54dbcdfba96fc103b9020bbe9cb4" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.19.0.tgz" integrity sha512-Fz+VrjLmwq5fbQn5W7cIJZ066HxLMKvDEmf4eu1tZ8O956aoX45jAuBB76miAECMTODyUxH61AQM7q4/GOMQ5g== dependencies: "@typescript-eslint/types" "5.19.0" @@ -3072,7 +3129,7 @@ "@typescript-eslint/scope-manager@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.22.0.tgz#590865f244ebe6e46dc3e9cab7976fc2afa8af24" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.22.0.tgz" integrity sha512-yA9G5NJgV5esANJCO0oF15MkBO20mIskbZ8ijfmlKIvQKg0ynVKfHZ15/nhAJN5m8Jn3X5qkwriQCiUntC9AbA== dependencies: "@typescript-eslint/types" "5.22.0" @@ -3080,7 +3137,7 @@ "@typescript-eslint/type-utils@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.19.0.tgz#80f2125b0dfe82494bbae1ea99f1c0186d420282" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.19.0.tgz" integrity sha512-O6XQ4RI4rQcBGshTQAYBUIGsKqrKeuIOz9v8bckXZnSeXjn/1+BDZndHLe10UplQeJLXDNbaZYrAytKNQO2T4Q== dependencies: "@typescript-eslint/utils" "5.19.0" @@ -3089,7 +3146,7 @@ "@typescript-eslint/type-utils@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.22.0.tgz#0c0e93b34210e334fbe1bcb7250c470f4a537c19" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.22.0.tgz" integrity sha512-iqfLZIsZhK2OEJ4cQ01xOq3NaCuG5FQRKyHicA3xhZxMgaxQazLUHbH/B2k9y5i7l3+o+B5ND9Mf1AWETeMISA== dependencies: "@typescript-eslint/utils" "5.22.0" @@ -3098,17 +3155,17 @@ "@typescript-eslint/types@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.19.0.tgz#12d3d600d754259da771806ee8b2c842d3be8d12" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.19.0.tgz" integrity sha512-zR1ithF4Iyq1wLwkDcT+qFnhs8L5VUtjgac212ftiOP/ZZUOCuuF2DeGiZZGQXGoHA50OreZqLH5NjDcDqn34w== "@typescript-eslint/types@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.22.0.tgz#50a4266e457a5d4c4b87ac31903b28b06b2c3ed0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.22.0.tgz" integrity sha512-T7owcXW4l0v7NTijmjGWwWf/1JqdlWiBzPqzAWhobxft0SiEvMJB56QXmeCQjrPuM8zEfGUKyPQr/L8+cFUBLw== "@typescript-eslint/typescript-estree@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.19.0.tgz#fc987b8f62883f9ea6a5b488bdbcd20d33c0025f" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.19.0.tgz" integrity sha512-dRPuD4ocXdaE1BM/dNR21elSEUPKaWgowCA0bqJ6YbYkvtrPVEvZ+zqcX5a8ECYn3q5iBSSUcBBD42ubaOp0Hw== dependencies: "@typescript-eslint/types" "5.19.0" @@ -3121,7 +3178,7 @@ "@typescript-eslint/typescript-estree@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.22.0.tgz#e2116fd644c3e2fda7f4395158cddd38c0c6df97" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.22.0.tgz" integrity sha512-EyBEQxvNjg80yinGE2xdhpDYm41so/1kOItl0qrjIiJ1kX/L/L8WWGmJg8ni6eG3DwqmOzDqOhe6763bF92nOw== dependencies: "@typescript-eslint/types" "5.22.0" @@ -3134,7 +3191,7 @@ "@typescript-eslint/utils@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.19.0.tgz#fe87f1e3003d9973ec361ed10d36b4342f1ded1e" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.19.0.tgz" integrity sha512-ZuEckdupXpXamKvFz/Ql8YnePh2ZWcwz7APICzJL985Rp5C2AYcHO62oJzIqNhAMtMK6XvrlBTZeNG8n7gS3lQ== dependencies: "@types/json-schema" "^7.0.9" @@ -3146,7 +3203,7 @@ "@typescript-eslint/utils@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.22.0.tgz#1f2c4897e2cf7e44443c848a13c60407861babd8" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.22.0.tgz" integrity sha512-HodsGb037iobrWSUMS7QH6Hl1kppikjA1ELiJlNSTYf/UdMEwzgj0WIp+lBNb6WZ3zTwb0tEz51j0Wee3iJ3wQ== dependencies: "@types/json-schema" "^7.0.9" @@ -3158,7 +3215,7 @@ "@typescript-eslint/visitor-keys@5.19.0": version "5.19.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.19.0.tgz#c84ebc7f6c744707a361ca5ec7f7f64cd85b8af6" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.19.0.tgz" integrity sha512-Ym7zZoMDZcAKWsULi2s7UMLREdVQdScPQ/fKWMYefarCztWlHPFVJo8racf8R0Gc8FAEJ2eD4of8As1oFtnQlQ== dependencies: "@typescript-eslint/types" "5.19.0" @@ -3166,7 +3223,7 @@ "@typescript-eslint/visitor-keys@5.22.0": version "5.22.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.22.0.tgz#f49c0ce406944ffa331a1cfabeed451ea4d0909c" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.22.0.tgz" integrity sha512-DbgTqn2Dv5RFWluG88tn0pP6Ex0ROF+dpDO1TNNZdRtLjUr6bdznjA6f/qNqJLjd2PgguAES2Zgxh/JzwzETDg== dependencies: "@typescript-eslint/types" "5.22.0" @@ -3236,12 +3293,12 @@ "@yarnpkg/lockfile@^1.1.0": version "1.1.0" - resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz" integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== "@yarnpkg/parsers@^3.0.0-rc.18": version "3.0.0-rc.32" - resolved "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.32.tgz#0aef0bd1b9e9954173c01a7cbd35f98765e39e7d" + resolved "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.32.tgz" integrity sha512-Sz2g88b3iAu2jpMnhtps2bRX2GAAOvanOxGcVi+o7ybGjLetxK23o2cHskXKypvXxtZTsJegel5pUWSPpYphww== dependencies: js-yaml "^3.10.0" @@ -3249,7 +3306,7 @@ "@zkochan/js-yaml@0.0.6": version "0.0.6" - resolved "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz#975f0b306e705e28b8068a07737fa46d3fc04826" + resolved "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.6.tgz" integrity sha512-nzvgl3VfhcELQ8LyVrYOru+UtAy1nrygk2+AGbTm8a5YcO6o8lSjAT+pfg3vJWxIoZKOUhrK6UU7xW/+00kQrg== dependencies: argparse "^2.0.1" @@ -3264,7 +3321,7 @@ JSONStream@^1.0.4, JSONStream@^1.3.5: abab@^2.0.3, abab@^2.0.5: version "2.0.6" - resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== abbrev@1, abbrev@^1.0.0: @@ -3294,7 +3351,7 @@ accepts@^1.3.7, accepts@~1.3.8: acorn-globals@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz" integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== dependencies: acorn "^7.1.1" @@ -3302,27 +3359,27 @@ acorn-globals@^6.0.0: acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^7.1.1: version "7.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== acorn-walk@^8.1.1: version "8.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== acorn@^7.1.1: version "7.4.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== acorn@^8.2.4, acorn@^8.4.1, acorn@^8.7.1: version "8.7.1" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz" integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== add-stream@^1.0.0: @@ -3344,7 +3401,7 @@ agent-base@6, agent-base@^6.0.2: agentkeepalive@^4.2.1: version "4.2.1" - resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz" integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== dependencies: debug "^4.1.0" @@ -3386,7 +3443,7 @@ ansi-colors@^3.2.1: ansi-colors@^4.1.1: version "4.1.3" - resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-escapes@^4.2.1: @@ -3422,12 +3479,12 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== anymatch@^3.0.3: version "3.1.2" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== dependencies: normalize-path "^3.0.0" @@ -3435,7 +3492,7 @@ anymatch@^3.0.3: anymatch@~3.1.2: version "3.1.3" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -3511,7 +3568,7 @@ are-we-there-yet@^2.0.0: are-we-there-yet@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz" integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== dependencies: delegates "^1.0.0" @@ -3519,7 +3576,7 @@ are-we-there-yet@^3.0.0: arg@^4.1.0: version "4.1.3" - resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== argparse@^1.0.7, argparse@~1.0.9: @@ -3531,17 +3588,17 @@ argparse@^1.0.7, argparse@~1.0.9: argparse@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== array-back@^3.0.1, array-back@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" + resolved "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz" integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== array-back@^4.0.1, array-back@^4.0.2: version "4.0.2" - resolved "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz#8004e999a6274586beeb27342168652fdb89fa1e" + resolved "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== array-differ@^3.0.0: @@ -3591,7 +3648,7 @@ astral-regex@^2.0.0: async@^3.2.3: version "3.2.4" - resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== asynckit@^0.4.0: @@ -3635,7 +3692,7 @@ axios@^0.21.4: axios@^1.0.0: version "1.2.1" - resolved "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz#44cf04a3c9f0c2252ebd85975361c026cb9f864a" + resolved "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz" integrity sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A== dependencies: follow-redirects "^1.15.0" @@ -3644,7 +3701,7 @@ axios@^1.0.0: babel-jest@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz" integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== dependencies: "@jest/transform" "^27.5.1" @@ -3658,7 +3715,7 @@ babel-jest@^27.5.1: babel-plugin-istanbul@^6.1.1: version "6.1.1" - resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -3669,7 +3726,7 @@ babel-plugin-istanbul@^6.1.1: babel-plugin-jest-hoist@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz" integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ== dependencies: "@babel/template" "^7.3.3" @@ -3679,7 +3736,7 @@ babel-plugin-jest-hoist@^27.5.1: babel-preset-current-node-syntax@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -3697,7 +3754,7 @@ babel-preset-current-node-syntax@^1.0.0: babel-preset-jest@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz" integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag== dependencies: babel-plugin-jest-hoist "^27.5.1" @@ -3742,7 +3799,12 @@ bech32@1.1.4: resolved "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== -before-after-hook@^2.1.0, before-after-hook@^2.2.0: +before-after-hook@^2.1.0: + version "2.2.3" + resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + +before-after-hook@^2.2.0: version "2.2.2" resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz" integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== @@ -3759,7 +3821,7 @@ bignumber.js@^9.0.0, bignumber.js@^9.0.1: bin-links@^3.0.0: version "3.0.3" - resolved "https://registry.npmjs.org/bin-links/-/bin-links-3.0.3.tgz#3842711ef3db2cd9f16a5f404a996a12db355a6e" + resolved "https://registry.npmjs.org/bin-links/-/bin-links-3.0.3.tgz" integrity sha512-zKdnMPWEdh4F5INR07/eBrodC7QrF5JKvqskjz/ZZRXg5YSAZIbn8zGhbhUrElzHBZ2fvEQdOU59RHcTG3GiwA== dependencies: cmd-shim "^5.0.0" @@ -3771,7 +3833,7 @@ bin-links@^3.0.0: binary-extensions@^2.0.0: version "2.2.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== bindings@^1.2.1: @@ -3788,7 +3850,7 @@ bintrees@1.0.1: bl@^4.0.3, bl@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" @@ -3817,7 +3879,7 @@ bn.js@^5.1.2, bn.js@^5.2.0: bn.js@^5.2.1: version "5.2.1" - resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + resolved "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== body-parser@1.19.1: @@ -3862,7 +3924,7 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" @@ -3881,7 +3943,7 @@ brorand@^1.1.0: browser-process-hrtime@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== browserify-aes@^1.2.0: @@ -3898,7 +3960,7 @@ browserify-aes@^1.2.0: browserslist@^4.20.2: version "4.20.4" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz#98096c9042af689ee1e0271333dbc564b8ce4477" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz" integrity sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw== dependencies: caniuse-lite "^1.0.30001349" @@ -3909,7 +3971,7 @@ browserslist@^4.20.2: bs-logger@0.x: version "0.2.6" - resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" @@ -3939,7 +4001,7 @@ bs58check@^2.1.2: bser@2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" @@ -3971,7 +4033,7 @@ buffer-xor@^1.0.3: buffer@^5.5.0, buffer@^5.7.0: version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== dependencies: base64-js "^1.3.1" @@ -3984,7 +4046,7 @@ builtins@^1.0.3: builtins@^5.0.0: version "5.0.1" - resolved "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz#87f6db9ab0458be728564fa81d876d8d74552fa9" + resolved "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz" integrity sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ== dependencies: semver "^7.0.0" @@ -4006,7 +4068,7 @@ bytes@3.1.2: cacache@^16.0.0, cacache@^16.0.6, cacache@^16.1.0: version "16.1.3" - resolved "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" + resolved "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz" integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== dependencies: "@npmcli/fs" "^2.1.0" @@ -4057,12 +4119,12 @@ camelcase@^5.0.0, camelcase@^5.3.1: camelcase@^6.2.0: version "6.3.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001349: version "1.0.30001352" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz#cc6f5da3f983979ad1e2cdbae0505dccaa7c6a12" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz" integrity sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA== cargo-cp-artifact@0.1.6: @@ -4072,12 +4134,12 @@ cargo-cp-artifact@0.1.6: cargo-cp-artifact@0.1.7: version "0.1.7" - resolved "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz#1181b9d6e71f00f17c068c05e3cd1b0864783341" + resolved "https://registry.npmjs.org/cargo-cp-artifact/-/cargo-cp-artifact-0.1.7.tgz" integrity sha512-pxEV9p1on8vu3BOKstVisF9TwMyGKCBRvzaVpQHuU2sLULCKrn3MJWx/4XlNzmG6xNCTPf78DJ7WCGgr2mOzjg== chalk@4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== dependencies: ansi-styles "^4.1.0" @@ -4110,7 +4172,7 @@ chalk@^3.0.0: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== chardet@^0.7.0: @@ -4120,7 +4182,7 @@ chardet@^0.7.0: chokidar@^3.5.1: version "3.5.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" @@ -4145,7 +4207,7 @@ ci-info@^2.0.0: ci-info@^3.2.0: version "3.3.1" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz#58331f6f472a25fe3a50a351ae3052936c2c7f32" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.3.1.tgz" integrity sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: @@ -4158,7 +4220,7 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: cjs-module-lexer@^1.0.0: version "1.2.2" - resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== clean-stack@^2.0.0: @@ -4175,12 +4237,12 @@ cli-cursor@3.1.0, cli-cursor@^3.1.0: cli-spinners@2.6.1, cli-spinners@^2.2.0: version "2.6.1" - resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz" integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== cli-spinners@^2.5.0: version "2.7.0" - resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz#f815fd30b5f9eaac02db604c7a231ed7cb2f797a" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz" integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw== cli-table3@~0.5.0: @@ -4209,7 +4271,7 @@ cliui@^7.0.2: cliui@^8.0.1: version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" @@ -4232,19 +4294,19 @@ clone@^1.0.2: cmd-shim@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/cmd-shim/-/cmd-shim-5.0.0.tgz#8d0aaa1a6b0708630694c4dbde070ed94c707724" + resolved "https://registry.npmjs.org/cmd-shim/-/cmd-shim-5.0.0.tgz" integrity sha512-qkCtZ59BidfEwHltnJwkyVZn+XQojdAySM1D1gSeh11Z4pW1Kpolkyo53L5noc0nrxmIvyFwTmJRo4xs7FFLPw== dependencies: mkdirp-infer-owner "^2.0.0" co@^4.6.0: version "4.6.0" - resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== collect-v8-coverage@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== color-convert@^1.9.0: @@ -4273,7 +4335,7 @@ color-name@~1.1.4: color-support@^1.1.2, color-support@^1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== colors@^1.1.2, colors@^1.3.3: @@ -4288,7 +4350,7 @@ colors@~1.2.1: columnify@^1.6.0: version "1.6.0" - resolved "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3" + resolved "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz" integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q== dependencies: strip-ansi "^6.0.1" @@ -4303,7 +4365,7 @@ combined-stream@^1.0.8: command-line-args@^5.1.1: version "5.2.1" - resolved "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz#c44c32e437a57d7c51157696893c5909e9cec42e" + resolved "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz" integrity sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg== dependencies: array-back "^3.1.0" @@ -4313,7 +4375,7 @@ command-line-args@^5.1.1: command-line-usage@^6.1.0: version "6.1.3" - resolved "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz#428fa5acde6a838779dfa30e44686f4b6761d957" + resolved "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz" integrity sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw== dependencies: array-back "^4.0.2" @@ -4323,7 +4385,7 @@ command-line-usage@^6.1.0: commander@7.2.0: version "7.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== commander@^2.20.3: @@ -4333,7 +4395,7 @@ commander@^2.20.3: common-ancestor-path@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" + resolved "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz" integrity sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w== compare-func@^2.0.0: @@ -4346,7 +4408,7 @@ compare-func@^2.0.0: component-emitter@^1.3.0: version "1.3.0" - resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== concat-map@0.0.1: @@ -4384,7 +4446,7 @@ console-log-level@^1.4.0: console-table-printer@^2.11.1: version "2.11.1" - resolved "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.11.1.tgz#c2dfe56e6343ea5bcfa3701a4be29fe912dbd9c7" + resolved "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.11.1.tgz" integrity sha512-8LfFpbF/BczoxPwo2oltto5bph8bJkGOATXsg3E9ddMJOGnWJciKHldx2zDj5XIBflaKzPfVCjOTl6tMh7lErg== dependencies: simple-wcswidth "^1.0.1" @@ -4411,7 +4473,7 @@ conventional-changelog-angular@^5.0.12: conventional-changelog-core@^4.2.4: version "4.2.4" - resolved "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz#e50d047e8ebacf63fac3dc67bf918177001e1e9f" + resolved "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz" integrity sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg== dependencies: add-stream "^1.0.0" @@ -4485,7 +4547,7 @@ conventional-recommended-bump@^6.1.0: convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.8.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== dependencies: safe-buffer "~5.1.1" @@ -4502,7 +4564,7 @@ cookie@0.4.2, cookie@^0.4.0, cookie@^0.4.1: cookiejar@^2.1.3: version "2.1.3" - resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" + resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz" integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== core-util-is@1.0.2: @@ -4570,7 +4632,7 @@ create-hmac@^1.1.4, create-hmac@^1.1.7: create-require@^1.1.0: version "1.1.1" - resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-fetch@3.1.5: @@ -4608,17 +4670,17 @@ crypto-js@^3.1.9-1: cssom@^0.4.4: version "0.4.4" - resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz" integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== cssom@~0.3.6: version "0.3.8" - resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + resolved "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz" integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== cssstyle@^2.3.0: version "2.3.0" - resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + resolved "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz" integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== dependencies: cssom "~0.3.6" @@ -4630,7 +4692,7 @@ dargs@^7.0.0: data-urls@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" + resolved "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz" integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== dependencies: abab "^2.0.3" @@ -4686,7 +4748,7 @@ decimal.js-light@^2.5.0: decimal.js@^10.2.1: version "10.3.1" - resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz" integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== dedent@^0.7.0: @@ -4696,12 +4758,12 @@ dedent@^0.7.0: deep-extend@~0.6.0: version "0.6.0" - resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== deepmerge@^4.2.2: @@ -4718,7 +4780,7 @@ defaults@^1.0.3: define-lazy-prop@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== delay@^5.0.0: @@ -4773,12 +4835,12 @@ detect-libc@^2.0.0: detect-newline@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== dezalgo@1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" + resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz" integrity sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ== dependencies: asap "^2.0.0" @@ -4794,12 +4856,12 @@ dezalgo@^1.0.0: diff-sequences@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== diff@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dir-glob@^3.0.1: @@ -4811,7 +4873,7 @@ dir-glob@^3.0.1: doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" @@ -4823,7 +4885,7 @@ dom-walk@^0.1.0: domexception@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" + resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz" integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== dependencies: webidl-conversions "^5.0.0" @@ -4844,7 +4906,7 @@ dot-prop@^6.0.0, dot-prop@^6.0.1: dotenv@~10.0.0: version "10.0.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== dottie@^2.0.2: @@ -4891,14 +4953,14 @@ ejs@^2.6.1: ejs@^3.1.7: version "3.1.8" - resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + resolved "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz" integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: jake "^10.8.5" electron-to-chromium@^1.4.147: version "1.4.154" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.154.tgz#d69c60499fc467a6c59591d29183e520afbc78a1" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.154.tgz" integrity sha512-GbV9djOkrnj6xmW+YYVVEI3VCQnJ0pnSTu7TW2JyjKd5cakoiSaG5R4RbEtfaD92GsY10DzbU3GYRe+IOA9kqA== elliptic@6.5.4, elliptic@^6.5.4: @@ -4921,7 +4983,7 @@ emittery@^0.10.0: emittery@^0.8.1: version "0.8.1" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz" integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg== emoji-regex@^8.0.0: @@ -4936,7 +4998,7 @@ encodeurl@~1.0.2: encoding@^0.1.13: version "0.1.13" - resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== dependencies: iconv-lite "^0.6.2" @@ -4957,7 +5019,7 @@ enquirer@2.3.4: enquirer@~2.3.6: version "2.3.6" - resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: ansi-colors "^4.1.1" @@ -5018,17 +5080,17 @@ escape-string-regexp@^1.0.5: escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escodegen@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz" integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== dependencies: esprima "^4.0.1" @@ -5040,12 +5102,12 @@ escodegen@^2.0.0: eslint-config-prettier@8.5.0, eslint-config-prettier@^8.5.0: version "8.5.0" - resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz" integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== eslint-scope@^5.1.1: version "5.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== dependencies: esrecurse "^4.3.0" @@ -5053,7 +5115,7 @@ eslint-scope@^5.1.1: eslint-scope@^7.1.1: version "7.1.1" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz" integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== dependencies: esrecurse "^4.3.0" @@ -5061,24 +5123,24 @@ eslint-scope@^7.1.1: eslint-utils@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz" integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== dependencies: eslint-visitor-keys "^2.0.0" eslint-visitor-keys@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: version "3.3.0" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== eslint@8.13.0: version "8.13.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz" integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ== dependencies: "@eslint/eslintrc" "^1.2.1" @@ -5119,7 +5181,7 @@ eslint@8.13.0: eslint@8.14.0: version "8.14.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz" integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw== dependencies: "@eslint/eslintrc" "^1.2.2" @@ -5160,7 +5222,7 @@ eslint@8.14.0: espree@^9.3.1, espree@^9.3.2: version "9.3.2" - resolved "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + resolved "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz" integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: acorn "^8.7.1" @@ -5169,36 +5231,36 @@ espree@^9.3.1, espree@^9.3.2: esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.0.1, esquery@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^4.1.1: version "4.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== etag@~1.8.1: @@ -5247,7 +5309,7 @@ ethereumjs-util@^7.1.0: ethers@5.7.0, ethers@^5.6.0: version "5.7.0" - resolved "https://registry.npmjs.org/ethers/-/ethers-5.7.0.tgz#0055da174b9e076b242b8282638bc94e04b39835" + resolved "https://registry.npmjs.org/ethers/-/ethers-5.7.0.tgz" integrity sha512-5Xhzp2ZQRi0Em+0OkOcRHxPzCfoBfgtOQA+RUylSkuHbhTEaQklnYi2hsWbRgs3ztJsXVXd9VKBcO1ScWL8YfA== dependencies: "@ethersproject/abi" "5.7.0" @@ -5368,12 +5430,12 @@ execa@^5.0.0: exit@^0.1.2: version "0.1.2" - resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== expect@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz" integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== dependencies: "@jest/types" "^27.5.1" @@ -5473,7 +5535,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: fast-glob@3.2.7: version "3.2.7" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz" integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== dependencies: "@nodelib/fs.stat" "^2.0.2" @@ -5510,7 +5572,7 @@ fast-json-stringify@^2.5.2: fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-redact@^3.0.0: @@ -5568,7 +5630,7 @@ fastq@^1.6.0, fastq@^1.6.1: fb-watchman@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz" integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== dependencies: bser "2.1.1" @@ -5582,7 +5644,7 @@ figures@3.2.0, figures@^3.0.0: file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" @@ -5594,7 +5656,7 @@ file-uri-to-path@1.0.0: filelist@^1.0.1: version "1.0.4" - resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== dependencies: minimatch "^5.0.1" @@ -5631,7 +5693,7 @@ find-my-way@^4.1.0: find-replace@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" + resolved "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz" integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== dependencies: array-back "^3.0.1" @@ -5658,7 +5720,7 @@ findit2@^2.2.3: flat-cache@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz" integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: flatted "^3.1.0" @@ -5666,7 +5728,7 @@ flat-cache@^3.0.4: flat@^5.0.2: version "5.0.2" - resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatstr@^1.0.12: @@ -5676,7 +5738,7 @@ flatstr@^1.0.12: flatted@^3.1.0: version "3.2.5" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== follow-redirects@^1.14.0, follow-redirects@^1.14.8: @@ -5686,7 +5748,7 @@ follow-redirects@^1.14.0, follow-redirects@^1.14.8: follow-redirects@^1.15.0: version "1.15.2" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== form-data-encoder@^1.7.1: @@ -5696,7 +5758,7 @@ form-data-encoder@^1.7.1: form-data@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== dependencies: asynckit "^0.4.0" @@ -5705,7 +5767,7 @@ form-data@^3.0.0: form-data@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" @@ -5722,7 +5784,7 @@ formdata-node@^4.3.1: formidable@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" + resolved "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz" integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ== dependencies: dezalgo "1.0.3" @@ -5742,12 +5804,12 @@ fresh@0.5.2: fs-constants@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== fs-extra@^10.1.0: version "10.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz" integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== dependencies: graceful-fs "^4.2.0" @@ -5756,7 +5818,7 @@ fs-extra@^10.1.0: fs-extra@^7.0.0: version "7.0.1" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz" integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== dependencies: graceful-fs "^4.1.2" @@ -5813,7 +5875,7 @@ function-bind@^1.1.1: functional-red-black-tree@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz" integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== gauge@^3.0.0: @@ -5833,7 +5895,7 @@ gauge@^3.0.0: gauge@^4.0.3: version "4.0.4" - resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz#52ff0652f2bbf607a989793d53b751bef2328dce" + resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz" integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== dependencies: aproba "^1.0.3 || ^2.0.0" @@ -5866,7 +5928,7 @@ gcp-metadata@^4.0.0, gcp-metadata@^4.2.0: gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: @@ -5885,7 +5947,7 @@ get-intrinsic@^1.0.2: get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-pkg-repo@^4.0.0: @@ -5944,7 +6006,7 @@ git-semver-tags@^4.1.1: git-up@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz#bace30786e36f56ea341b6f69adfd83286337467" + resolved "https://registry.npmjs.org/git-up/-/git-up-7.0.0.tgz" integrity sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ== dependencies: is-ssh "^1.4.0" @@ -5952,7 +6014,7 @@ git-up@^7.0.0: git-url-parse@^13.1.0: version "13.1.0" - resolved "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz#07e136b5baa08d59fabdf0e33170de425adf07b4" + resolved "https://registry.npmjs.org/git-url-parse/-/git-url-parse-13.1.0.tgz" integrity sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA== dependencies: git-up "^7.0.0" @@ -5973,14 +6035,14 @@ glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.2: glob-parent@^6.0.1: version "6.0.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" glob@7.1.4: version "7.1.4" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz" integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== dependencies: fs.realpath "^1.0.0" @@ -5992,7 +6054,7 @@ glob@7.1.4: glob@7.1.7: version "7.1.7" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" @@ -6016,7 +6078,7 @@ glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: glob@^7.1.2: version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -6028,7 +6090,7 @@ glob@^7.1.2: glob@^8.0.1: version "8.0.3" - resolved "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" + resolved "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz" integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== dependencies: fs.realpath "^1.0.0" @@ -6047,12 +6109,12 @@ global@4.4.0: globals@^11.1.0: version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globals@^13.15.0, globals@^13.6.0: version "13.15.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + resolved "https://registry.npmjs.org/globals/-/globals-13.15.0.tgz" integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" @@ -6266,7 +6328,7 @@ helmet@5.0.2: hexoid@1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== hmac-drbg@^1.0.1: @@ -6292,7 +6354,7 @@ hosted-git-info@^2.1.4: hosted-git-info@^3.0.6: version "3.0.8" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz#6e35d4cc87af2c5f816e4cb9ce350ba87a3f370d" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.8.tgz" integrity sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw== dependencies: lru-cache "^6.0.0" @@ -6306,21 +6368,21 @@ hosted-git-info@^4.0.0, hosted-git-info@^4.0.1: hosted-git-info@^5.0.0: version "5.2.1" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz#0ba1c97178ef91f3ab30842ae63d6a272341156f" + resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-5.2.1.tgz" integrity sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw== dependencies: lru-cache "^7.5.1" html-encoding-sniffer@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" + resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz" integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== dependencies: whatwg-encoding "^1.0.5" html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== http-cache-semantics@^4.1.0: @@ -6425,14 +6487,14 @@ ieee754@^1.1.13: ignore-walk@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776" + resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz" integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== dependencies: minimatch "^5.0.1" ignore@^5.0.4: version "5.2.1" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz#c2b1f76cb999ede1502f3a226a9310fdfe88d46c" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz" integrity sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA== ignore@^5.1.8, ignore@^5.2.0: @@ -6496,7 +6558,7 @@ ini@^1.3.2, ini@^1.3.4: init-package-json@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/init-package-json/-/init-package-json-3.0.2.tgz#f5bc9bac93f2bdc005778bc2271be642fecfcd69" + resolved "https://registry.npmjs.org/init-package-json/-/init-package-json-3.0.2.tgz" integrity sha512-YhlQPEjNFqlGdzrBfDNRLhvoSgX7iQRgSxgsNknRQ9ITXFT7UMfVMWhBTOh2Y+25lRnGrv5Xz8yZwQ3ACR6T3A== dependencies: npm-package-arg "^9.0.1" @@ -6509,7 +6571,7 @@ init-package-json@^3.0.2: inquirer@^8.2.4: version "8.2.5" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz#d8654a7542c35a9b9e069d27e2df4858784d54f8" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz" integrity sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ== dependencies: ansi-escapes "^4.2.1" @@ -6545,7 +6607,7 @@ is-arrayish@^0.2.1: is-binary-path@~2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" @@ -6566,7 +6628,7 @@ is-core-module@^2.5.0, is-core-module@^2.8.1: is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" - resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extglob@^2.1.1: @@ -6586,7 +6648,7 @@ is-fullwidth-code-point@^3.0.0: is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: @@ -6645,12 +6707,12 @@ is-plain-object@^5.0.0: is-potential-custom-element-name@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== is-ssh@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2" + resolved "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.0.tgz" integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ== dependencies: protocols "^2.0.1" @@ -6679,12 +6741,12 @@ is-typedarray@^1.0.0: is-unicode-supported@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-wsl@^2.2.0: version "2.2.0" - resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: is-docker "^2.0.0" @@ -6719,12 +6781,12 @@ isomorphic-ws@^4.0.1: istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz" integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== dependencies: "@babel/core" "^7.12.3" @@ -6735,7 +6797,7 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-report@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== dependencies: istanbul-lib-coverage "^3.0.0" @@ -6744,7 +6806,7 @@ istanbul-lib-report@^3.0.0: istanbul-lib-source-maps@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" @@ -6753,7 +6815,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-reports@^3.1.3: version "3.1.4" - resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== dependencies: html-escaper "^2.0.0" @@ -6766,7 +6828,7 @@ iterall@^1.2.1: jake@^10.8.5: version "10.8.5" - resolved "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + resolved "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz" integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== dependencies: async "^3.2.3" @@ -6797,7 +6859,7 @@ jayson@3.6.6: jest-changed-files@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz" integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw== dependencies: "@jest/types" "^27.5.1" @@ -6806,7 +6868,7 @@ jest-changed-files@^27.5.1: jest-circus@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz" integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw== dependencies: "@jest/environment" "^27.5.1" @@ -6831,7 +6893,7 @@ jest-circus@^27.5.1: jest-cli@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz" integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw== dependencies: "@jest/core" "^27.5.1" @@ -6849,7 +6911,7 @@ jest-cli@^27.5.1: jest-config@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz" integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA== dependencies: "@babel/core" "^7.8.0" @@ -6879,7 +6941,7 @@ jest-config@^27.5.1: jest-diff@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz" integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== dependencies: chalk "^4.0.0" @@ -6889,14 +6951,14 @@ jest-diff@^27.5.1: jest-docblock@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz" integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ== dependencies: detect-newline "^3.0.0" jest-each@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz" integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ== dependencies: "@jest/types" "^27.5.1" @@ -6907,7 +6969,7 @@ jest-each@^27.5.1: jest-environment-jsdom@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz" integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw== dependencies: "@jest/environment" "^27.5.1" @@ -6920,7 +6982,7 @@ jest-environment-jsdom@^27.5.1: jest-environment-node@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz" integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw== dependencies: "@jest/environment" "^27.5.1" @@ -6932,12 +6994,12 @@ jest-environment-node@^27.5.1: jest-get-type@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== jest-haste-map@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz" integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng== dependencies: "@jest/types" "^27.5.1" @@ -6957,7 +7019,7 @@ jest-haste-map@^27.5.1: jest-jasmine2@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz" integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ== dependencies: "@jest/environment" "^27.5.1" @@ -6980,7 +7042,7 @@ jest-jasmine2@^27.5.1: jest-leak-detector@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz" integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ== dependencies: jest-get-type "^27.5.1" @@ -6988,7 +7050,7 @@ jest-leak-detector@^27.5.1: jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz" integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== dependencies: chalk "^4.0.0" @@ -6998,7 +7060,7 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-message-util@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz" integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== dependencies: "@babel/code-frame" "^7.12.13" @@ -7013,7 +7075,7 @@ jest-message-util@^27.5.1: jest-mock@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz" integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== dependencies: "@jest/types" "^27.5.1" @@ -7021,17 +7083,17 @@ jest-mock@^27.5.1: jest-pnp-resolver@^1.2.2: version "1.2.2" - resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== jest-regex-util@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz" integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== jest-resolve-dependencies@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz" integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg== dependencies: "@jest/types" "^27.5.1" @@ -7040,7 +7102,7 @@ jest-resolve-dependencies@^27.5.1: jest-resolve@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz" integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== dependencies: "@jest/types" "^27.5.1" @@ -7056,7 +7118,7 @@ jest-resolve@^27.5.1: jest-runner@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz" integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ== dependencies: "@jest/console" "^27.5.1" @@ -7083,7 +7145,7 @@ jest-runner@^27.5.1: jest-runtime@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz" integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A== dependencies: "@jest/environment" "^27.5.1" @@ -7111,7 +7173,7 @@ jest-runtime@^27.5.1: jest-serializer@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz" integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w== dependencies: "@types/node" "*" @@ -7119,7 +7181,7 @@ jest-serializer@^27.5.1: jest-snapshot@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz" integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA== dependencies: "@babel/core" "^7.7.2" @@ -7147,7 +7209,7 @@ jest-snapshot@^27.5.1: jest-util@^27.0.0, jest-util@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz" integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw== dependencies: "@jest/types" "^27.5.1" @@ -7159,7 +7221,7 @@ jest-util@^27.0.0, jest-util@^27.5.1: jest-validate@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz" integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ== dependencies: "@jest/types" "^27.5.1" @@ -7171,7 +7233,7 @@ jest-validate@^27.5.1: jest-watcher@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz" integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw== dependencies: "@jest/test-result" "^27.5.1" @@ -7184,7 +7246,7 @@ jest-watcher@^27.5.1: jest-worker@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== dependencies: "@types/node" "*" @@ -7193,7 +7255,7 @@ jest-worker@^27.5.1: jest@27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" + resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz" integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== dependencies: "@jest/core" "^27.5.1" @@ -7212,14 +7274,14 @@ js-sha3@0.8.0, js-sha3@^0.8.0: js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" js-yaml@^3.10.0, js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -7232,7 +7294,7 @@ jsbi@^3.1.1: jsdom@^16.6.0: version "16.7.0" - resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" + resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== dependencies: abab "^2.0.5" @@ -7265,7 +7327,7 @@ jsdom@^16.6.0: jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== json-bigint@^1.0.0: @@ -7297,12 +7359,12 @@ json-schema-traverse@^1.0.0: json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json-stringify-nice@^1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz#2c937962b80181d3f317dd39aa323e14f5a60a67" + resolved "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz" integrity sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw== json-stringify-safe@^5.0.1: @@ -7312,24 +7374,24 @@ json-stringify-safe@^5.0.1: json5@2.x, json5@^2.2.1: version "2.2.1" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== json5@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz" integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== dependencies: minimist "^1.2.0" jsonc-parser@3.2.0: version "3.2.0" - resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz" integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== jsonfile@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" @@ -7350,12 +7412,12 @@ jsonparse@^1.2.0, jsonparse@^1.3.1: just-diff-apply@^5.2.0: version "5.4.1" - resolved "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.4.1.tgz#1debed059ad009863b4db0e8d8f333d743cdd83b" + resolved "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.4.1.tgz" integrity sha512-AAV5Jw7tsniWwih8Ly3fXxEZ06y+6p5TwQMsw0dzZ/wPKilzyDgdAnL0Ug4NNIquPUOh1vfFWEHbmXUqM5+o8g== just-diff@^5.0.1: version "5.1.1" - resolved "https://registry.npmjs.org/just-diff/-/just-diff-5.1.1.tgz#8da6414342a5ed6d02ccd64f5586cbbed3146202" + resolved "https://registry.npmjs.org/just-diff/-/just-diff-5.1.1.tgz" integrity sha512-u8HXJ3HlNrTzY7zrYYKjNEfBlyjqhdBkoyTVdjtn7p02RJD5NvR8rIClzeGA7t+UYP1/7eAkWNLU0+P3QrEqKQ== jwa@^2.0.0: @@ -7391,12 +7453,12 @@ kind-of@^6.0.2, kind-of@^6.0.3: kleur@^3.0.3: version "3.0.3" - resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== lerna@6.1.0: version "6.1.0" - resolved "https://registry.npmjs.org/lerna/-/lerna-6.1.0.tgz#693145393ec22fd3ca98d817deab2246c1e2b107" + resolved "https://registry.npmjs.org/lerna/-/lerna-6.1.0.tgz" integrity sha512-3qAjIj8dgBwHtCAiLbq4VU/C1V9D1tvTLm2owZubdGAN72aB5TxuCu2mcw+yeEorOcXuR9YWx7EXIkAf+G0N2w== dependencies: "@lerna/add" "6.1.0" @@ -7425,12 +7487,12 @@ lerna@6.1.0: leven@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== levn@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" @@ -7438,7 +7500,7 @@ levn@^0.4.1: levn@~0.3.0: version "0.3.0" - resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + resolved "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz" integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== dependencies: prelude-ls "~1.1.2" @@ -7446,7 +7508,7 @@ levn@~0.3.0: libnpmaccess@^6.0.3: version "6.0.4" - resolved "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-6.0.4.tgz#2dd158bd8a071817e2207d3b201d37cf1ad6ae6b" + resolved "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-6.0.4.tgz" integrity sha512-qZ3wcfIyUoW0+qSFkMBovcTrSGJ3ZeyvpR7d5N9pEYv/kXs8sHP2wiqEIXBKLFrZlmM0kR0RJD7mtfLngtlLag== dependencies: aproba "^2.0.0" @@ -7456,7 +7518,7 @@ libnpmaccess@^6.0.3: libnpmpublish@^6.0.4: version "6.0.5" - resolved "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-6.0.5.tgz#5a894f3de2e267d62f86be2a508e362599b5a4b1" + resolved "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-6.0.5.tgz" integrity sha512-LUR08JKSviZiqrYTDfywvtnsnxr+tOvBU0BF8H+9frt7HMvc6Qn6F8Ubm72g5hDTHbq8qupKfDvDAln2TVPvFg== dependencies: normalize-package-data "^4.0.0" @@ -7520,6 +7582,16 @@ lodash.camelcase@^4.3.0: resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz" + integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz" @@ -7540,14 +7612,19 @@ lodash.lowerfirst@^4.3.1: resolved "https://registry.npmjs.org/lodash.lowerfirst/-/lodash.lowerfirst-4.3.1.tgz" integrity sha1-3jx7EuAsZSSgBZwvbLfFxSZVoT0= +lodash.mapvalues@^4.6.0: + version "4.6.0" + resolved "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" + integrity sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ== + lodash.memoize@4.x: version "4.1.2" - resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash.pad@^4.5.1: @@ -7572,7 +7649,7 @@ lodash.repeat@^4.1.0: lodash.set@^4.3.2: version "4.3.2" - resolved "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" + resolved "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz" integrity sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg== lodash.snakecase@^4.1.1: @@ -7615,6 +7692,16 @@ lodash.upperfirst@^4.3.1: resolved "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz" integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984= +lodash.xor@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.xor/-/lodash.xor-4.5.0.tgz#4d48ed7e98095b0632582ba714d3ff8ae8fb1db6" + integrity sha512-sVN2zimthq7aZ5sPGXnSz32rZPuqcparVW50chJQe+mzTYV+IsxSsl/2gnkWWE2Of7K3myBQBqtLKOUEHJKRsQ== + +lodash.zip@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020" + integrity sha512-C7IOaBBK/0gMORRBd8OETNx3kmOkgIWIPvyDpZSCTwUrpYmgZwJkjZeOD8ww4xbOUOs4/attY+pciKvadNfFbg== + lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" @@ -7629,7 +7716,7 @@ log-symbols@^3.0.0: log-symbols@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" @@ -7656,7 +7743,7 @@ lru-cache@^6.0.0: lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: version "7.14.1" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz" integrity sha512-ysxwsnTKdAx96aTRdhDOCQfDgbHnt8SK0KY8SEjO0wHinhWOFTESbjVCMPbU1uGXg/ch4lifqx0wfjOawU2+WA== lru_map@^0.3.3: @@ -7681,12 +7768,12 @@ make-dir@^3.0.0, make-dir@^3.1.0: make-error@1.x, make-error@^1.1.1: version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6: version "10.2.1" - resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" + resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz" integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== dependencies: agentkeepalive "^4.2.1" @@ -7708,7 +7795,7 @@ make-fetch-happen@^10.0.3, make-fetch-happen@^10.0.6: makeerror@1.0.12: version "1.0.12" - resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" @@ -7817,7 +7904,7 @@ mime@1.6.0: mime@2.6.0: version "2.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== mimic-fn@^2.1.0: @@ -7854,7 +7941,7 @@ minimalistic-crypto-utils@^1.0.1: minimatch@3.0.5: version "3.0.5" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz" integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw== dependencies: brace-expansion "^1.1.7" @@ -7868,7 +7955,7 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: minimatch@^5.0.1: version "5.1.1" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz#6c9dffcf9927ff2a31e74b5af11adf8b9604b022" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz" integrity sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g== dependencies: brace-expansion "^2.0.1" @@ -7896,7 +7983,7 @@ minipass-collect@^1.0.2: minipass-fetch@^2.0.3: version "2.1.2" - resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" + resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz" integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== dependencies: minipass "^3.1.6" @@ -7943,14 +8030,14 @@ minipass@^3.0.0, minipass@^3.1.1: minipass@^3.1.6: version "3.3.6" - resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== dependencies: yallist "^4.0.0" minipass@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz#7cebb0f9fa7d56f0c5b17853cbe28838a8dbbd3b" + resolved "https://registry.npmjs.org/minipass/-/minipass-4.0.0.tgz" integrity sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw== dependencies: yallist "^4.0.0" @@ -7982,18 +8069,23 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment-timezone@^0.5.34: - version "0.5.34" - resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz" - integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== +moment-timezone@^0.5.35: + version "0.5.43" + resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz" + integrity sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ== dependencies: - moment ">= 2.9.0" + moment "^2.29.4" -"moment@>= 2.9.0", moment@^2.29.1: +moment@^2.29.1: version "2.29.3" resolved "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz" integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== +moment@^2.29.4: + version "2.29.4" + resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz" + integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== + morgan@1.10.0: version "1.10.0" resolved "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz" @@ -8054,12 +8146,12 @@ nan@^2.14.0: natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== negotiator@0.6.3, negotiator@^0.6.3: version "0.6.3" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== neo-async@^2.6.0: @@ -8074,7 +8166,7 @@ ngeohash@0.6.3: nock@13.2.4: version "13.2.4" - resolved "https://registry.npmjs.org/nock/-/nock-13.2.4.tgz#43a309d93143ee5cdcca91358614e7bde56d20e1" + resolved "https://registry.npmjs.org/nock/-/nock-13.2.4.tgz" integrity sha512-8GPznwxcPNCH/h8B+XZcKjYPXnUV5clOKCjAqyjsiqA++MpNx9E9+t8YPp0MbThO+KauRo7aZJ1WuIZmOrT2Ug== dependencies: debug "^4.1.0" @@ -8089,7 +8181,7 @@ node-addon-api@^2.0.0: node-addon-api@^3.2.1: version "3.2.1" - resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== node-domexception@1.0.0: @@ -8116,12 +8208,12 @@ node-gyp-build@^4.2.0: node-gyp-build@^4.3.0: version "4.5.0" - resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" + resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.5.0.tgz" integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== node-gyp@^9.0.0: version "9.3.0" - resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.0.tgz#f8eefe77f0ad8edb3b3b898409b53e697642b319" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.0.tgz" integrity sha512-A6rJWfXFz7TQNjpldJ915WFb1LnhO4lIve3ANPbWreuEoLoKlFT3sxIepPBkLhM27crW8YmN+pjlgbasH6cH/Q== dependencies: env-paths "^2.2.0" @@ -8137,12 +8229,12 @@ node-gyp@^9.0.0: node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== node-pre-gyp-github@1.4.4: version "1.4.4" - resolved "https://registry.npmjs.org/node-pre-gyp-github/-/node-pre-gyp-github-1.4.4.tgz#327da096266f35ecf4317d37ee201b448c14bb96" + resolved "https://registry.npmjs.org/node-pre-gyp-github/-/node-pre-gyp-github-1.4.4.tgz" integrity sha512-oE9JD1aXRi4+1jSH7Q+ybEhfujW5bJ66n4YMGpaUp/k2/X/8i09ouK1seznf3wOagcKjytRJCkf71DdEJx2zhA== dependencies: "@octokit/rest" "18.12.0" @@ -8150,7 +8242,7 @@ node-pre-gyp-github@1.4.4: node-releases@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz" integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== nopt@^5.0.0: @@ -8162,7 +8254,7 @@ nopt@^5.0.0: nopt@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d" + resolved "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz" integrity sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g== dependencies: abbrev "^1.0.0" @@ -8189,7 +8281,7 @@ normalize-package-data@^3.0.0: normalize-package-data@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.1.tgz#b46b24e0616d06cadf9d5718b29b6d445a82a62c" + resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-4.0.1.tgz" integrity sha512-EBk5QKKuocMJhB3BILuKhmaPjI8vNRSpIfO9woLC6NyHVkKKdVEdAO1mrT0ZfxNR1lKwCcTkuZfmGIFdizZ8Pg== dependencies: hosted-git-info "^5.0.0" @@ -8199,7 +8291,7 @@ normalize-package-data@^4.0.0: normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-bundled@^1.1.1: @@ -8211,14 +8303,14 @@ npm-bundled@^1.1.1: npm-bundled@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz#94113f7eb342cd7a67de1e789f896b04d2c600f4" + resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz" integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== dependencies: npm-normalize-package-bin "^2.0.0" npm-install-checks@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz#5ff27d209a4e3542b8ac6b0c1db6063506248234" + resolved "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz" integrity sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA== dependencies: semver "^7.1.1" @@ -8230,12 +8322,12 @@ npm-normalize-package-bin@^1.0.1: npm-normalize-package-bin@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz#9447a1adaaf89d8ad0abe24c6c84ad614a675fff" + resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz" integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== npm-package-arg@8.1.1: version "8.1.1" - resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.1.tgz#00ebf16ac395c63318e67ce66780a06db6df1b04" + resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.1.tgz" integrity sha512-CsP95FhWQDwNqiYS+Q0mZ7FAEDytDZAkNxQqea6IaAFJTAY9Lhhqyl0irU/6PMc7BGfUmnsbHcqxJD7XuVM/rg== dependencies: hosted-git-info "^3.0.6" @@ -8244,7 +8336,7 @@ npm-package-arg@8.1.1: npm-package-arg@^9.0.0, npm-package-arg@^9.0.1: version "9.1.2" - resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.2.tgz#fc8acecb00235f42270dda446f36926ddd9ac2bc" + resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-9.1.2.tgz" integrity sha512-pzd9rLEx4TfNJkovvlBSLGhq31gGu2QDexFPWT19yCDh0JgnRhlBLNo5759N0AJmBk+kQ9Y/hXoLnlgFD+ukmg== dependencies: hosted-git-info "^5.0.0" @@ -8254,7 +8346,7 @@ npm-package-arg@^9.0.0, npm-package-arg@^9.0.1: npm-packlist@^5.1.0, npm-packlist@^5.1.1: version "5.1.3" - resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz#69d253e6fd664b9058b85005905012e00e69274b" + resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz" integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== dependencies: glob "^8.0.1" @@ -8264,7 +8356,7 @@ npm-packlist@^5.1.0, npm-packlist@^5.1.1: npm-pick-manifest@^7.0.0: version "7.0.2" - resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.2.tgz#1d372b4e7ea7c6712316c0e99388a73ed3496e84" + resolved "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-7.0.2.tgz" integrity sha512-gk37SyRmlIjvTfcYl6RzDbSmS9Y4TOBXfsPnoYqTHARNgWbyDiCSMLUpmALDj4jjcTZpURiEfsSHJj9k7EV4Rw== dependencies: npm-install-checks "^5.0.0" @@ -8274,7 +8366,7 @@ npm-pick-manifest@^7.0.0: npm-registry-fetch@^13.0.0, npm-registry-fetch@^13.0.1, npm-registry-fetch@^13.3.0: version "13.3.1" - resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz#bb078b5fa6c52774116ae501ba1af2a33166af7e" + resolved "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-13.3.1.tgz" integrity sha512-eukJPi++DKRTjSBRcDZSDDsGqRK3ehbxfFUcgaRd0Yp6kRwOwh2WVn0r+8rMB4nnuzvAk6rQVzl6K5CkYOmnvw== dependencies: make-fetch-happen "^10.0.6" @@ -8304,7 +8396,7 @@ npmlog@^5.0.1: npmlog@^6.0.0, npmlog@^6.0.2: version "6.0.2" - resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz#c8166017a42f2dea92d6453168dd865186a70830" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz" integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== dependencies: are-we-there-yet "^3.0.0" @@ -8322,12 +8414,12 @@ number-to-bn@1.7.0: nwsapi@^2.2.0: version "2.2.0" - resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== nx@15.3.0, "nx@>=14.8.6 < 16": version "15.3.0" - resolved "https://registry.npmjs.org/nx/-/nx-15.3.0.tgz#50916064145cf33ba68fb8bd03ff8ffc2b9ebc7b" + resolved "https://registry.npmjs.org/nx/-/nx-15.3.0.tgz" integrity sha512-5tBrEF2zDkGBDfe8wThazJqBDhsVkRrxc6OttzfBmkXP4VPp8w5MMtUEOry181AXKfjDGkw//UnCSkUNynTDlw== dependencies: "@nrwl/cli" "15.3.0" @@ -8421,7 +8513,7 @@ onetime@^5.1.0, onetime@^5.1.2: open@^8.4.0: version "8.4.0" - resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz" integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== dependencies: define-lazy-prop "^2.0.0" @@ -8438,7 +8530,7 @@ optimism@^0.16.1: optionator@^0.8.1: version "0.8.3" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== dependencies: deep-is "~0.1.3" @@ -8450,7 +8542,7 @@ optionator@^0.8.1: optionator@^0.9.1: version "0.9.1" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz" integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== dependencies: deep-is "^0.1.3" @@ -8476,7 +8568,7 @@ ora@^4.0.0: ora@^5.4.1: version "5.4.1" - resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz" integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== dependencies: bl "^4.1.0" @@ -8591,7 +8683,7 @@ p-retry@4.6.1: p-timeout@4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz#788253c0452ab0ffecf18a62dff94ff1bd09ca0a" + resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz" integrity sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw== p-timeout@^3.2.0: @@ -8625,7 +8717,7 @@ packet-reader@1.0.0: pacote@^13.0.3, pacote@^13.6.1: version "13.6.2" - resolved "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz#0d444ba3618ab3e5cd330b451c22967bbd0ca48a" + resolved "https://registry.npmjs.org/pacote/-/pacote-13.6.2.tgz" integrity sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg== dependencies: "@npmcli/git" "^3.0.0" @@ -8659,7 +8751,7 @@ parent-module@^1.0.0: parse-conflict-json@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz#3d05bc8ffe07d39600dc6436c6aefe382033d323" + resolved "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-2.0.2.tgz" integrity sha512-jDbRGb00TAPFsKWCpZZOT93SxVP9nONOSgES3AevqRq/CHvavEBvKAjxX9p5Y5F0RZLxH9Ufd9+RwtCsa+lFDA== dependencies: json-parse-even-better-errors "^2.3.1" @@ -8696,21 +8788,21 @@ parse-ms@^2.1.0: parse-path@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b" + resolved "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz" integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog== dependencies: protocols "^2.0.0" parse-url@^8.1.0: version "8.1.0" - resolved "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" + resolved "https://registry.npmjs.org/parse-url/-/parse-url-8.1.0.tgz" integrity sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w== dependencies: parse-path "^7.0.0" parse5@6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + resolved "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== parseurl@~1.3.3: @@ -8718,6 +8810,11 @@ parseurl@~1.3.3: resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +parsimmon@^1.18.1: + version "1.18.1" + resolved "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz" + integrity sha512-u7p959wLfGAhJpSDJVYXoyMCXWYwHia78HhRBWqk7AIbxdmlrfdp5wX0l3xv/iTSH5HvhN9K7o26hwwpgS5Nmw== + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" @@ -8831,7 +8928,7 @@ pgpass@1.x: picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: @@ -8932,7 +9029,7 @@ pino@^7.0.0: pirates@^4.0.4: version "4.0.5" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== pkg-dir@^4.2.0: @@ -8987,27 +9084,27 @@ pprof@3.2.0: prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prelude-ls@~1.1.2: version "1.1.2" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== prettier@2.6.2: version "2.6.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz" integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== prettier@^2.3.1: version "2.7.0" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.0.tgz#a4fdae07e5596c51c9857ea676cd41a0163879d6" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.0.tgz" integrity sha512-nwoX4GMFgxoPC6diHvSwmK/4yU8FFH3V8XWtLQrbj4IBsK2pkYhG4kf/ljF/haaZ/aii+wNJqISrCDPgxGWDVQ== pretty-format@^27.0.0, pretty-format@^27.5.1: version "27.5.1" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: ansi-regex "^5.0.1" @@ -9023,7 +9120,7 @@ pretty-ms@^7.0.0: proc-log@^2.0.0, proc-log@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz#8f3f69a1f608de27878f91f5c688b225391cb685" + resolved "https://registry.npmjs.org/proc-log/-/proc-log-2.0.1.tgz" integrity sha512-Kcmo2FhfDTXdcbfDH76N7uBYHINxc/8GW7UAVuVP9I+Va3uHSerrnKV6dLooga/gh7GlgzuCCr/eoldnL1muGw== process-nextick-args@~2.0.0: @@ -9050,12 +9147,12 @@ prom-client@14.0.1: promise-all-reject-late@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz#f8ebf13483e5ca91ad809ccc2fcf25f26f8643c2" + resolved "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz" integrity sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw== promise-call-limit@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz#4bdee03aeb85674385ca934da7114e9bcd3c6e24" + resolved "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz" integrity sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q== promise-inflight@^1.0.1: @@ -9073,7 +9170,7 @@ promise-retry@^2.0.1: prompts@^2.0.1: version "2.4.2" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" @@ -9097,7 +9194,7 @@ prop-types@^15.7.2: propagate@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + resolved "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== proto-list@~1.2.1: @@ -9133,7 +9230,7 @@ protobufjs@6.11.2, protobufjs@^6.10.0, protobufjs@^6.11.2, protobufjs@~6.11.0: protocols@^2.0.0, protocols@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86" + resolved "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz" integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q== proxy-addr@^2.0.7, proxy-addr@~2.0.7: @@ -9146,7 +9243,7 @@ proxy-addr@^2.0.7, proxy-addr@~2.0.7: proxy-from-env@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== psl@^1.1.33: @@ -9183,7 +9280,7 @@ q@^1.5.1: qs@6.9.3: version "6.9.3" - resolved "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" + resolved "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz" integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== qs@6.9.6: @@ -9198,7 +9295,7 @@ qs@6.9.7: qs@^6.10.3: version "6.10.5" - resolved "https://registry.npmjs.org/qs/-/qs-6.10.5.tgz#974715920a80ff6a262264acd2c7e6c2a53282b4" + resolved "https://registry.npmjs.org/qs/-/qs-6.10.5.tgz" integrity sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ== dependencies: side-channel "^1.0.4" @@ -9279,17 +9376,17 @@ react-is@^16.13.1, react-is@^16.7.0: react-is@^17.0.1: version "17.0.2" - resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== read-cmd-shim@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-3.0.1.tgz#868c235ec59d1de2db69e11aec885bc095aea087" + resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-3.0.1.tgz" integrity sha512-kEmDUoYf/CDy8yZbLTmhB1X9kkjf9Q80PCNsDMb7ufrGd6zZSQA1+UyjrO+pZm5K/S4OXCWJeiIt1JA8kAsa6g== read-package-json-fast@^2.0.2, read-package-json-fast@^2.0.3: version "2.0.3" - resolved "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz#323ca529630da82cb34b36cc0b996693c98c2b83" + resolved "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz" integrity sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ== dependencies: json-parse-even-better-errors "^2.3.0" @@ -9297,7 +9394,7 @@ read-package-json-fast@^2.0.2, read-package-json-fast@^2.0.3: read-package-json@^5.0.0, read-package-json@^5.0.1: version "5.0.2" - resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.2.tgz#b8779ccfd169f523b67208a89cc912e3f663f3fa" + resolved "https://registry.npmjs.org/read-package-json/-/read-package-json-5.0.2.tgz" integrity sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q== dependencies: glob "^8.0.1" @@ -9372,7 +9469,7 @@ readable-stream@~2.3.6: readdir-scoped-modules@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" + resolved "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz" integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== dependencies: debuglog "^1.0.1" @@ -9382,7 +9479,7 @@ readdir-scoped-modules@^1.1.0: readdirp@~3.6.0: version "3.6.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -9402,12 +9499,12 @@ redent@^3.0.0: reduce-flatten@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" + resolved "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== regexpp@^3.2.0: version "3.2.0" - resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== require-directory@^2.1.1: @@ -9439,7 +9536,7 @@ resolve-from@^5.0.0: resolve.exports@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== resolve@^1.10.0, resolve@^1.20.0: @@ -9464,10 +9561,10 @@ ret@~0.2.0: resolved "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz" integrity sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ== -retry-as-promised@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-5.0.0.tgz" - integrity sha512-6S+5LvtTl2ggBumk04hBo/4Uf6fRJUwIgunGZ7CYEBCeufGFW1Pu6ucUf/UskHeWOIsUcLOGLFXPig5tR5V1nA== +retry-as-promised@^7.0.3: + version "7.0.4" + resolved "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.4.tgz" + integrity sha512-XgmCoxKWkDofwH8WddD0w85ZfqYz+ZHlr5yo+3YUCfycWawU56T5ckWXsScsj5B8tqUcIG67DxXByo3VUgiAdA== retry-request@^4.0.0, retry-request@^4.2.2: version "4.2.2" @@ -9547,7 +9644,7 @@ run-parallel@^1.1.9: rxjs@^7.5.5: version "7.6.0" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.6.0.tgz#361da5362b6ddaa691a2de0b4f2d32028f1eb5a2" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.6.0.tgz" integrity sha512-DDa7d8TFNUalGC9VqXvQ1euWNN7sc63TrUCuM9J998+ViviahMIjKSOU7rfcgFOF+FCD71BhDRv4hrFz+ImDLQ== dependencies: tslib "^2.1.0" @@ -9581,7 +9678,7 @@ safe-stable-stringify@^2.1.0: saxes@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + resolved "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz" integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== dependencies: xmlchars "^2.2.0" @@ -9617,7 +9714,7 @@ semver-store@^0.3.0: semver@7.3.4: version "7.3.4" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + resolved "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== dependencies: lru-cache "^6.0.0" @@ -9658,10 +9755,10 @@ sequelize-pool@^7.1.0: resolved "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz" integrity sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg== -sequelize@6.19.0: - version "6.19.0" - resolved "https://registry.npmjs.org/sequelize/-/sequelize-6.19.0.tgz#bea0ac091d89cbbc94cabe0797b8c1359734e2e6" - integrity sha512-B3oGIdpYBERDjRDm74h7Ky67f6ZLcmBXOA7HscYObiOSo4pD7VBc9mtm44wNV7unc0uk8I1d30nbZBTQCE377A== +sequelize@6.19.0, sequelize@6.31.1: + version "6.31.1" + resolved "https://registry.npmjs.org/sequelize/-/sequelize-6.31.1.tgz" + integrity sha512-cahWtRrYLjqoZP/aurGBoaxn29qQCF4bxkAUPEQ/ozjJjt6mtL4Q113S3N39mQRmX5fgxRbli+bzZARP/N51eg== dependencies: "@types/debug" "^4.1.7" "@types/validator" "^13.7.1" @@ -9670,9 +9767,9 @@ sequelize@6.19.0: inflection "^1.13.2" lodash "^4.17.21" moment "^2.29.1" - moment-timezone "^0.5.34" + moment-timezone "^0.5.35" pg-connection-string "^2.5.0" - retry-as-promised "^5.0.0" + retry-as-promised "^7.0.3" semver "^7.3.5" sequelize-pool "^7.1.0" toposort-class "^1.0.1" @@ -9753,12 +9850,12 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: simple-wcswidth@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz#8ab18ac0ae342f9d9b629604e54d2aa1ecb018b2" + resolved "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz" integrity sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg== sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^3.0.0: @@ -9782,7 +9879,7 @@ smart-buffer@^4.2.0: socks-proxy-agent@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" + resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz" integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== dependencies: agent-base "^6.0.2" @@ -9828,7 +9925,7 @@ sort-keys@^4.0.0: source-map-support@^0.5.6: version "0.5.21" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" @@ -9896,14 +9993,14 @@ sprintf-js@~1.0.2: ssri@^9.0.0, ssri@^9.0.1: version "9.0.1" - resolved "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" + resolved "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz" integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== dependencies: minipass "^3.1.1" stack-utils@^2.0.3: version "2.0.5" - resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== dependencies: escape-string-regexp "^2.0.0" @@ -9937,12 +10034,12 @@ string-argv@~0.3.1: string-format@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" + resolved "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== string-length@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" @@ -10029,7 +10126,7 @@ strip-indent@^3.0.0: strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== strong-log-transformer@^2.1.0: @@ -10059,7 +10156,7 @@ subscriptions-transport-ws@^0.11.0: superagent@^7.1.0: version "7.1.6" - resolved "https://registry.npmjs.org/superagent/-/superagent-7.1.6.tgz#64f303ed4e4aba1e9da319f134107a54cacdc9c6" + resolved "https://registry.npmjs.org/superagent/-/superagent-7.1.6.tgz" integrity sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g== dependencies: component-emitter "^1.3.0" @@ -10076,7 +10173,7 @@ superagent@^7.1.0: supertest@6.2.2: version "6.2.2" - resolved "https://registry.npmjs.org/supertest/-/supertest-6.2.2.tgz#04a5998fd3efaff187cb69f07a169755d655b001" + resolved "https://registry.npmjs.org/supertest/-/supertest-6.2.2.tgz" integrity sha512-wCw9WhAtKJsBvh07RaS+/By91NNE0Wh0DN19/hWPlBOU8tAfOtbZoVSV4xXeoKoxgPx0rx2y+y+8660XtE7jzg== dependencies: methods "^1.1.2" @@ -10098,14 +10195,14 @@ supports-color@^7.0.0, supports-color@^7.1.0: supports-color@^8.0.0: version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-hyperlinks@^2.0.0: version "2.2.0" - resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz" integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== dependencies: has-flag "^4.0.0" @@ -10128,7 +10225,7 @@ symbol-observable@^4.0.0: symbol-tree@^3.2.4: version "3.2.4" - resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== sync-fetch@^0.3.1: @@ -10141,7 +10238,7 @@ sync-fetch@^0.3.1: table-layout@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz#c4038a1853b0136d63365a734b6931cf4fad4a04" + resolved "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz" integrity sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A== dependencies: array-back "^4.0.1" @@ -10162,7 +10259,7 @@ table@6.7.5: tar-stream@~2.2.0: version "2.2.0" - resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== dependencies: bl "^4.0.3" @@ -10185,7 +10282,7 @@ tar@^6.1.0, tar@^6.1.11: tar@^6.1.2: version "6.1.13" - resolved "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b" + resolved "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz" integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw== dependencies: chownr "^2.0.0" @@ -10220,7 +10317,7 @@ temp-dir@^1.0.0: terminal-link@^2.0.0: version "2.1.1" - resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + resolved "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz" integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== dependencies: ansi-escapes "^4.2.1" @@ -10228,7 +10325,7 @@ terminal-link@^2.0.0: test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -10242,7 +10339,7 @@ text-extensions@^1.0.0: text-table@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== thread-stream@^0.13.0: @@ -10261,7 +10358,7 @@ thread-stream@^0.15.1: throat@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" + resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz" integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w== through2@^2.0.0: @@ -10316,19 +10413,19 @@ tmp@^0.0.33: tmp@~0.2.1: version "0.2.1" - resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz" integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== dependencies: rimraf "^3.0.0" tmpl@1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: @@ -10360,7 +10457,7 @@ toposort-class@^1.0.1: tough-cookie@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz" integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== dependencies: psl "^1.1.33" @@ -10386,7 +10483,7 @@ treeify@^1.1.0: treeverse@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/treeverse/-/treeverse-2.0.0.tgz#036dcef04bc3fd79a9b79a68d4da03e882d8a9ca" + resolved "https://registry.npmjs.org/treeverse/-/treeverse-2.0.0.tgz" integrity sha512-N5gJCkLu1aXccpOTtqV6ddSEi6ZmGkh3hjmbu1IjcavJK4qyOVQmi0myQKM7z5jVGmD68SJoliaVrMmVObhj6A== trim-newlines@^3.0.0: @@ -10396,7 +10493,7 @@ trim-newlines@^3.0.0: ts-command-line-args@^2.2.0: version "2.3.1" - resolved "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.3.1.tgz#b6188e42efc6cf7a8898e438a873fbb15505ddd6" + resolved "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.3.1.tgz" integrity sha512-FR3y7pLl/fuUNSmnPhfLArGqRrpojQgIEEOVzYx9DhTmfIN7C9RWSfpkJEF4J+Gk7aVx5pak8I7vWZsaN4N84g== dependencies: chalk "^4.1.0" @@ -10411,7 +10508,7 @@ ts-custom-error@^3.2.0: ts-essentials@^7.0.1: version "7.0.3" - resolved "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz#686fd155a02133eedcc5362dc8b5056cde3e5a38" + resolved "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz" integrity sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ== ts-invariant@^0.4.0: @@ -10430,7 +10527,7 @@ ts-invariant@^0.9.4: ts-jest@27.1.4: version "27.1.4" - resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.4.tgz#84d42cf0f4e7157a52e7c64b1492c46330943e00" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.4.tgz" integrity sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ== dependencies: bs-logger "0.x" @@ -10444,7 +10541,7 @@ ts-jest@27.1.4: ts-node@10.7.0: version "10.7.0" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz" integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== dependencies: "@cspotcode/source-map-support" "0.7.0" @@ -10463,7 +10560,7 @@ ts-node@10.7.0: tsconfig-paths@^3.9.0: version "3.14.1" - resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz" integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== dependencies: "@types/json5" "^0.0.29" @@ -10483,33 +10580,33 @@ tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.3.0: tslib@^2.4.0: version "2.4.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz" integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== tsutils@^3.21.0: version "3.21.0" - resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== dependencies: tslib "^1.8.1" type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-check@~0.3.2: version "0.3.2" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== dependencies: prelude-ls "~1.1.2" type-detect@4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.18.0: @@ -10519,7 +10616,7 @@ type-fest@^0.18.0: type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== type-fest@^0.21.3: @@ -10557,7 +10654,7 @@ type-is@~1.6.18: typechain@8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/typechain/-/typechain-8.0.0.tgz#a5dbe754717a7e16247df52b5285903de600e8ff" + resolved "https://registry.npmjs.org/typechain/-/typechain-8.0.0.tgz" integrity sha512-rqDfDYc9voVAhmfVfAwzg3VYFvhvs5ck1X9T/iWkX745Cul4t+V/smjnyqrbDzWDbzD93xfld1epg7Y/uFAesQ== dependencies: "@types/prettier" "^2.1.1" @@ -10585,22 +10682,22 @@ typedarray@^0.0.6: typescript@4.6.3: version "4.6.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz" integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== "typescript@^3 || ^4": version "4.9.4" - resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz" integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== typical@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" + resolved "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz" integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== typical@^5.2.0: version "5.2.0" - resolved "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz#4daaac4f2b5315460804f0acf6cb69c52bb93066" + resolved "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz" integrity sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg== uglify-js@^3.1.4: @@ -10633,14 +10730,14 @@ undici@^5.0.0: unique-filename@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" + resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz" integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== dependencies: unique-slug "^3.0.0" unique-slug@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" + resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz" integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== dependencies: imurmurhash "^0.1.4" @@ -10652,7 +10749,7 @@ universal-user-agent@^6.0.0: universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" - resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== universalify@^2.0.0: @@ -10699,17 +10796,17 @@ uuid@^8.0.0, uuid@^8.3.2: v8-compile-cache-lib@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-compile-cache@2.3.0, v8-compile-cache@^2.0.3: version "2.3.0" - resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== v8-to-istanbul@^8.1.0: version "8.1.1" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz" integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" @@ -10733,7 +10830,7 @@ validate-npm-package-name@^3.0.0: validate-npm-package-name@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz#fe8f1c50ac20afdb86f177da85b3600f0ac0d747" + resolved "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-4.0.0.tgz" integrity sha512-mzR0L8ZDktZjpX4OB46KT+56MAhl4EIazWP/+G/HPGuvfdaqg4YsCdtOm6U9+LOFyYDoh4dpnpxZRB9MQQns5Q== dependencies: builtins "^5.0.0" @@ -10764,26 +10861,26 @@ verror@^1.10.0: w3c-hr-time@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + resolved "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz" integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== dependencies: browser-process-hrtime "^1.0.0" w3c-xmlserializer@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" + resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz" integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== dependencies: xml-name-validator "^3.0.0" walk-up-path@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" + resolved "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz" integrity sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg== walker@^1.0.7: version "1.0.8" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" @@ -10825,7 +10922,7 @@ webidl-conversions@^3.0.0: webidl-conversions@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz" integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== webidl-conversions@^6.1.0: @@ -10835,7 +10932,7 @@ webidl-conversions@^6.1.0: whatwg-encoding@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" + resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz" integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== dependencies: iconv-lite "0.4.24" @@ -10847,7 +10944,7 @@ whatwg-fetch@^3.4.1: whatwg-mimetype@^2.3.0: version "2.3.0" - resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" + resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz" integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== whatwg-url@^5.0.0: @@ -10876,7 +10973,7 @@ which@^2.0.0, which@^2.0.1, which@^2.0.2: wide-align@^1.1.2, wide-align@^1.1.5: version "1.1.5" - resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== dependencies: string-width "^1.0.2 || 2 || 3 || 4" @@ -10895,7 +10992,7 @@ wonka@^4.0.14: word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" - resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== wordwrap@^1.0.0: @@ -10905,7 +11002,7 @@ wordwrap@^1.0.0: wordwrapjs@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz#d9790bccfb110a0fc7836b5ebce0937b37a8b98f" + resolved "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz" integrity sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA== dependencies: reduce-flatten "^2.0.0" @@ -10946,7 +11043,7 @@ write-file-atomic@^3.0.0: write-file-atomic@^4.0.0, write-file-atomic@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" + resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" @@ -10997,7 +11094,7 @@ ws@7.4.6: ws@^7.4.6: version "7.5.8" - resolved "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a" + resolved "https://registry.npmjs.org/ws/-/ws-7.5.8.tgz" integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw== ws@^8.3.0: @@ -11007,12 +11104,12 @@ ws@^8.3.0: xml-name-validator@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== xmlchars@^2.2.0: version "2.2.0" - resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== xtend@^4.0.0, xtend@~4.0.1: @@ -11037,7 +11134,7 @@ yaml@1.10.2, yaml@^1.10.0, yaml@^1.7.2: yaml@^2.0.0-10: version "2.0.0-10" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-10.tgz#d5b59e2d14b8683313a534f2bbc648e211a2753e" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0-10.tgz" integrity sha512-FHV8s5ODFFQXX/enJEU2EkanNl1UDBUz8oa4k5Qo/sR+Iq7VmhCDkRMb0/mjJCNeAWQ31W8WV6PYStDE4d9EIw== yargs-parser@20.2.4: @@ -11052,7 +11149,7 @@ yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3: yargs-parser@21.1.1, yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs-parser@^16.1.0: @@ -11096,7 +11193,7 @@ yargs@^16.2.0: yargs@^17.6.2: version "17.6.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" + resolved "https://registry.npmjs.org/yargs/-/yargs-17.6.2.tgz" integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== dependencies: cliui "^8.0.1" @@ -11109,7 +11206,7 @@ yargs@^17.6.2: yn@3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: @@ -11136,3 +11233,13 @@ zen-observable@0.8.15, zen-observable@^0.8.0: version "0.8.15" resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== + +zod-validation-error@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-1.3.0.tgz#6a37e8b1896e45362a4e4cf9506eca203fad3c6e" + integrity sha512-4WoQnuWnj06kwKR4A+cykRxFmy+CTvwMQO5ogTXLiVx1AuvYYmMjixh7sbkSsQTr1Fvtss6d5kVz8PGeMPUQjQ== + +zod@^3.21.4: + version "3.21.4" + resolved "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db" + integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw== From 6991b8ac3ec54a6202ddca1749a2deca226925f1 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 13:57:42 -0300 Subject: [PATCH 02/16] all: update jest and typescript config --- packages/indexer-agent/jest.config.js | 7 +++++-- packages/indexer-cli/jest.config.js | 17 +++++++++-------- packages/indexer-common/jest.config.js | 5 +++-- packages/indexer-common/tsconfig.json | 2 +- packages/indexer-native/jest.config.js | 1 + packages/indexer-service/jest.config.js | 19 ++++++++++--------- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/packages/indexer-agent/jest.config.js b/packages/indexer-agent/jest.config.js index 7a42563ed..3d76b8f04 100644 --- a/packages/indexer-agent/jest.config.js +++ b/packages/indexer-agent/jest.config.js @@ -1,15 +1,18 @@ -const bail = (s) => { +const bail = s => { throw new Error(s) } module.exports = { collectCoverage: true, + forceExit: true, preset: 'ts-jest', testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/', '/dist/', '.yalc'], globals: { __DATABASE__: { - host: process.env.POSTGRES_TEST_HOST || bail('POSTGRES_TEST_HOST is not defined'), + host: + process.env.POSTGRES_TEST_HOST || + bail('POSTGRES_TEST_HOST is not defined'), port: parseInt(process.env.POSTGRES_TEST_PORT || '5432'), username: process.env.POSTGRES_TEST_USERNAME || diff --git a/packages/indexer-cli/jest.config.js b/packages/indexer-cli/jest.config.js index c1d71ddff..a21637a25 100644 --- a/packages/indexer-cli/jest.config.js +++ b/packages/indexer-cli/jest.config.js @@ -1,10 +1,11 @@ -const bail = (s) => { +const bail = s => { throw new Error(s) } module.exports = { collectCoverage: false, preset: 'ts-jest', + forceExit: true, testEnvironment: 'node', // The glob patterns Jest uses to detect test files testMatch: ['**/?(*.)+(spec|test).ts?(x)'], @@ -15,15 +16,15 @@ module.exports = { host: process.env.POSTGRES_TEST_HOST || bail('POSTGRES_TEST_HOST is not defined'), port: parseInt(process.env.POSTGRES_TEST_PORT || '5432'), username: - process.env.POSTGRES_TEST_USERNAME || - bail('POSTGRES_TEST_USERNAME is not defined'), + process.env.POSTGRES_TEST_USERNAME || + bail('POSTGRES_TEST_USERNAME is not defined'), password: - process.env.POSTGRES_TEST_PASSWORD || - bail('POSTGRES_TEST_PASSWORD is not defined'), + process.env.POSTGRES_TEST_PASSWORD || + bail('POSTGRES_TEST_PASSWORD is not defined'), database: - process.env.POSTGRES_TEST_DATABASE || - bail('POSTGRES_TEST_DATABASE is not defined'), + process.env.POSTGRES_TEST_DATABASE || + bail('POSTGRES_TEST_DATABASE is not defined'), }, - __LOG_LEVEL__: process.env.LOG_LEVEL || 'info' + __LOG_LEVEL__: process.env.LOG_LEVEL || 'info', }, } diff --git a/packages/indexer-common/jest.config.js b/packages/indexer-common/jest.config.js index 76c857d61..489c309cc 100644 --- a/packages/indexer-common/jest.config.js +++ b/packages/indexer-common/jest.config.js @@ -5,8 +5,9 @@ const bail = (s) => { module.exports = { collectCoverage: true, preset: 'ts-jest', + forceExit: true, testEnvironment: 'node', - testPathIgnorePatterns: ['/node_modules/', '/dist/', '/.yalc'], + testPathIgnorePatterns: ['/node_modules/', '/dist/', '/.yalc', 'util.ts'], globals: { __DATABASE__: { host: process.env.POSTGRES_TEST_HOST || bail('POSTGRES_TEST_HOST is not defined'), @@ -21,6 +22,6 @@ module.exports = { process.env.POSTGRES_TEST_DATABASE || bail('POSTGRES_TEST_DATABASE is not defined'), }, - __LOG_LEVEL__: process.env.LOG_LEVEL || 'info' + __LOG_LEVEL__: process.env.LOG_LEVEL || 'info', }, } diff --git a/packages/indexer-common/tsconfig.json b/packages/indexer-common/tsconfig.json index 1a9b5ce39..a205ad910 100644 --- a/packages/indexer-common/tsconfig.json +++ b/packages/indexer-common/tsconfig.json @@ -19,6 +19,6 @@ ] }, "include": ["src/**/*.ts"], - "exclude": ["src/**/__tests__/*.ts"], + "exclude": [], "references": [] } diff --git a/packages/indexer-native/jest.config.js b/packages/indexer-native/jest.config.js index 0b4fb0802..e36d277c8 100644 --- a/packages/indexer-native/jest.config.js +++ b/packages/indexer-native/jest.config.js @@ -1,4 +1,5 @@ module.exports = { + forceExit: true, collectCoverage: true, testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/', '/dist/', '.yalc'] diff --git a/packages/indexer-service/jest.config.js b/packages/indexer-service/jest.config.js index ca2c1bf76..623c9340f 100644 --- a/packages/indexer-service/jest.config.js +++ b/packages/indexer-service/jest.config.js @@ -1,29 +1,30 @@ -const bail = (s) => { +const bail = s => { throw new Error(s) } module.exports = { collectCoverage: true, + forceExit: true, preset: 'ts-jest', testEnvironment: 'node', testPathIgnorePatterns: ['/node_modules/', '/dist/', '.yalc'], globals: { - "ts-jest": { + 'ts-jest': { isolatedModules: true, }, __DATABASE__: { host: process.env.POSTGRES_TEST_HOST || bail('POSTGRES_TEST_HOST is not defined'), port: parseInt(process.env.POSTGRES_TEST_PORT || '5432'), username: - process.env.POSTGRES_TEST_USERNAME || - bail('POSTGRES_TEST_USERNAME is not defined'), + process.env.POSTGRES_TEST_USERNAME || + bail('POSTGRES_TEST_USERNAME is not defined'), password: - process.env.POSTGRES_TEST_PASSWORD || - bail('POSTGRES_TEST_PASSWORD is not defined'), + process.env.POSTGRES_TEST_PASSWORD || + bail('POSTGRES_TEST_PASSWORD is not defined'), database: - process.env.POSTGRES_TEST_DATABASE || - bail('POSTGRES_TEST_DATABASE is not defined'), + process.env.POSTGRES_TEST_DATABASE || + bail('POSTGRES_TEST_DATABASE is not defined'), }, - __LOG_LEVEL__: process.env.LOG_LEVEL || 'info' + __LOG_LEVEL__: process.env.LOG_LEVEL || 'info', }, } From d51416a6b39125b3fe325b155dc5b51da83da879 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:03:23 -0300 Subject: [PATCH 03/16] common: new indexer-common modules --- .../src/{indexing-status.ts => graph-node.ts} | 392 ++++++++++-- .../src/indexer-management/subgraphs.ts | 215 ------- packages/indexer-common/src/multi-networks.ts | 111 ++++ .../src/network-specification.ts | 163 +++++ packages/indexer-common/src/operator.ts | 571 ++++++++++++++++++ .../src/parsers/__tests__/parsers.ts | 10 + .../indexer-common/src/parsers/basic-types.ts | 34 ++ packages/indexer-common/src/parsers/index.ts | 1 + .../indexer-common/src/parsers/validators.ts | 30 + 9 files changed, 1256 insertions(+), 271 deletions(-) rename packages/indexer-common/src/{indexing-status.ts => graph-node.ts} (52%) delete mode 100644 packages/indexer-common/src/indexer-management/subgraphs.ts create mode 100644 packages/indexer-common/src/multi-networks.ts create mode 100644 packages/indexer-common/src/network-specification.ts create mode 100644 packages/indexer-common/src/operator.ts create mode 100644 packages/indexer-common/src/parsers/__tests__/parsers.ts create mode 100644 packages/indexer-common/src/parsers/basic-types.ts create mode 100644 packages/indexer-common/src/parsers/index.ts create mode 100644 packages/indexer-common/src/parsers/validators.ts diff --git a/packages/indexer-common/src/indexing-status.ts b/packages/indexer-common/src/graph-node.ts similarity index 52% rename from packages/indexer-common/src/indexing-status.ts rename to packages/indexer-common/src/graph-node.ts index 6c80774f9..f605598d7 100644 --- a/packages/indexer-common/src/indexing-status.ts +++ b/packages/indexer-common/src/graph-node.ts @@ -1,14 +1,21 @@ -import fetch from 'isomorphic-fetch' import gql from 'graphql-tag' -import { Client, createClient } from '@urql/core' +import jayson, { Client as RpcClient } from 'jayson/promise' import { Logger, SubgraphDeploymentID } from '@graphprotocol/common-ts' +import { Client, createClient } from '@urql/core' +import { + INDEXER_ERROR_MESSAGES, + IndexerError, + indexerError, + IndexerErrorCode, +} from './errors' import { BlockPointer, ChainIndexingStatus, IndexingStatus } from './types' -import { indexerError, IndexerErrorCode, INDEXER_ERROR_MESSAGES } from './errors' import pRetry from 'p-retry' +import axios, { AxiosInstance } from 'axios' +import fetch from 'isomorphic-fetch' -export interface IndexingStatusFetcherOptions { - logger: Logger - statusEndpoint: string +interface indexNode { + id: string + deployments: string[] } export interface SubgraphDeploymentAssignment { @@ -16,6 +23,11 @@ export interface SubgraphDeploymentAssignment { node: string } +export interface IndexingStatusFetcherOptions { + logger: Logger + statusEndpoint: string +} + export interface SubgraphFeatures { // `null` is only expected when Graph Node detects validation errors in the Subgraph Manifest. network: string | null @@ -48,19 +60,327 @@ export const parseGraphQLBlockPointer = (block: any): BlockPointer | null => } : null -export class IndexingStatusResolver { +export class GraphNode { + admin: RpcClient + private queryBaseURL: URL + status: Client logger: Logger - statuses: Client + indexNodeIDs: string[] - constructor(options: IndexingStatusFetcherOptions) { - this.logger = options.logger.child({ component: 'IndexingStatusFetcher' }) - this.statuses = createClient({ - url: options.statusEndpoint, + constructor( + logger: Logger, + adminEndpoint: string, + queryEndpoint: string, + statusEndpoint: string, + indexNodeIDs: string[], + ) { + this.logger = logger.child({ component: 'GraphNode' }) + this.status = createClient({ + url: statusEndpoint, fetch, requestPolicy: 'network-only', }) + + if (adminEndpoint.startsWith('https')) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.admin = jayson.Client.https(adminEndpoint as any) + } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.admin = jayson.Client.http(adminEndpoint as any) + } + + this.queryBaseURL = new URL(`/subgraphs/id/`, queryEndpoint) + + this.indexNodeIDs = indexNodeIDs + } + + async connect(): Promise { + try { + this.logger.info(`Check if indexing status API is available`) + const currentDeployments = await this.subgraphDeployments() + this.logger.info(`Successfully connected to indexing status API`, { + currentDeployments: currentDeployments.map((deployment) => deployment.display), + }) + } catch (error) { + const err = indexerError(IndexerErrorCode.IE024, error) + this.logger.error(`Failed to connect to indexing status API`, { + err, + }) + throw err + } + } + + // AxiosClient factory scoped by subgraph IFPS hash + getQueryClient(deploymentIpfsHash: string): AxiosInstance { + return axios.create({ + baseURL: new URL(deploymentIpfsHash, this.queryBaseURL).toString(), + headers: { 'content-type': 'application/json' }, + responseType: 'text', // Don't parse responses as JSON + transformResponse: (data) => data, // Don't transform responses + }) + } + + public async subgraphDeployments(): Promise { + return (await this.subgraphDeploymentsAssignments()).map((details) => details.id) + } + + public async subgraphDeploymentsAssignments(): Promise { + try { + this.logger.debug('Fetch subgraph deployment assignments') + const result = await this.status + .query( + gql` + { + indexingStatuses { + subgraphDeployment: subgraph + node + } + } + `, + ) + .toPromise() + + if (result.error) { + throw result.error + } + + type QueryResult = { subgraphDeployment: string; node: string } + + return result.data.indexingStatuses + .filter((status: QueryResult) => status.node && status.node !== 'removed') + .map((status: QueryResult) => { + return { + id: new SubgraphDeploymentID(status.subgraphDeployment), + node: status.node, + } + }) + } catch (error) { + const err = indexerError(IndexerErrorCode.IE018, error) + this.logger.error(`Failed to query indexing status API`, { err }) + throw err + } + } + + async indexNodes(): Promise { + try { + this.logger.trace(`Querying indexing statuses`) + const result = await this.status + .query( + gql` + { + indexingStatuses { + subgraphDeployment: subgraph + node + } + } + `, + ) + .toPromise() + + if (result.error) { + throw result.error + } + + this.logger.trace(`Queried indexing statuses`, { + data: result.data, + }) + + if (!result.data.indexingStatuses) { + throw new Error( + "Received invalid results when querying indexing statuses: Response is missing a value for the 'indexingStatus' field", + ) + } + + const indexNodes: indexNode[] = [] + result.data.indexingStatuses.map( + (status: { subgraphDeployment: string; node: string }) => { + const node = indexNodes.find((node) => node.id === status.node) + node + ? node.deployments.push(status.subgraphDeployment) + : indexNodes.push({ + id: status.node, + deployments: [status.subgraphDeployment], + }) + }, + ) + + this.logger.trace(`Queried index nodes`, { + indexNodes, + }) + return indexNodes + } catch (error) { + const err = indexerError(IndexerErrorCode.IE018, error) + this.logger.error(`Failed to query index nodes API (Should get a different IE?)`, { + err, + }) + throw err + } + } + + // -------------------------------------------------------------------------------- + // * Subgraph Management + // -------------------------------------------------------------------------------- + + async create(name: string): Promise { + try { + this.logger.info(`Create subgraph name`, { name }) + const response = await this.admin.request('subgraph_create', { name }) + if (response.error) { + throw response.error + } + this.logger.info(`Successfully created subgraph name`, { name }) + } catch (error) { + if (error.message.includes('already exists')) { + this.logger.debug(`Subgraph name already exists, will deploy to existing name`, { + name, + }) + return + } + throw error + } + } + + async deploy( + name: string, + deployment: SubgraphDeploymentID, + node_id: string, + ): Promise { + try { + this.logger.info(`Deploy subgraph deployment`, { + name, + deployment: deployment.display, + node_id, + }) + const response = await this.admin.request('subgraph_deploy', { + name, + ipfs_hash: deployment.ipfsHash, + node_id: node_id, + }) + + this.logger.trace(`Response from 'subgraph_deploy' call`, { + deployment: deployment.display, + name, + node_id, + response, + }) + + if (response.error) { + throw response.error + } + this.logger.info(`Successfully deployed subgraph deployment`, { + name, + deployment: deployment.display, + }) + } catch (error) { + // If more specific error not found use the generic 'Failed to deploy' error code + let errorCode = IndexerErrorCode.IE026 + + if (error.message && error.message.includes('network not supported')) { + errorCode = IndexerErrorCode.IE074 + } + + const err = indexerError(errorCode, error) + this.logger.error(INDEXER_ERROR_MESSAGES[errorCode], { + name, + deployment: deployment.display, + err, + }) + throw err + } + } + + async remove(deployment: SubgraphDeploymentID): Promise { + try { + this.logger.info(`Remove subgraph deployment`, { + deployment: deployment.display, + }) + const response = await this.admin.request('subgraph_reassign', { + node_id: 'removed', + ipfs_hash: deployment.ipfsHash, + }) + if (response.error) { + throw response.error + } + this.logger.info(`Successfully removed subgraph deployment`, { + deployment: deployment.display, + }) + } catch (error) { + const errorCode = IndexerErrorCode.IE027 + this.logger.error(INDEXER_ERROR_MESSAGES[errorCode], { + deployment: deployment.display, + error: indexerError(errorCode, error), + }) + } + } + + async reassign(deployment: SubgraphDeploymentID, node: string): Promise { + try { + this.logger.info(`Reassign subgraph deployment`, { + deployment: deployment.display, + node, + }) + const response = await this.admin.request('subgraph_reassign', { + node_id: node, + ipfs_hash: deployment.ipfsHash, + }) + if (response.error) { + throw response.error + } + } catch (error) { + if (error.message.includes('unchanged')) { + this.logger.debug(`Subgraph deployment assignment unchanged`, { + deployment: deployment.display, + node, + }) + return + } + const errorCode = IndexerErrorCode.IE028 + const err = indexerError(errorCode, error) + this.logger.error(INDEXER_ERROR_MESSAGES[errorCode], { + deployment: deployment.display, + err, + }) + throw err + } } + async ensure(name: string, deployment: SubgraphDeploymentID): Promise { + try { + // Randomly assign to unused nodes if they exist, + // otherwise use the node with lowest deployments assigned + const indexNodes = (await this.indexNodes()).filter( + (node: { id: string; deployments: Array }) => { + return node.id && node.id !== 'removed' + }, + ) + const usedIndexNodeIDs = indexNodes.map((node) => node.id) + const unusedNodes = this.indexNodeIDs.filter( + (nodeID) => !(nodeID in usedIndexNodeIDs), + ) + + const targetNode = unusedNodes + ? unusedNodes[Math.floor(Math.random() * unusedNodes.length)] + : indexNodes.sort((nodeA, nodeB) => { + return nodeA.deployments.length - nodeB.deployments.length + })[0].id + await this.create(name) + await this.deploy(name, deployment, targetNode) + await this.reassign(deployment, targetNode) + } catch (error) { + if (!(error instanceof IndexerError)) { + const errorCode = IndexerErrorCode.IE020 + this.logger.error(INDEXER_ERROR_MESSAGES[errorCode], { + name, + deployment: deployment.display, + error: indexerError(errorCode, error), + }) + } + } + } + + // -------------------------------------------------------------------------------- + // * Indexing Status + // -------------------------------------------------------------------------------- public async indexingStatus( deployments: SubgraphDeploymentID[], ): Promise { @@ -104,7 +424,7 @@ export class IndexingStatusResolver { }` const queryIndexingStatuses = async () => { - const result = await this.statuses + const result = await this.status .query(query, { deployments: deployments.map((id) => id.ipfsHash) }) .toPromise() @@ -149,7 +469,7 @@ export class IndexingStatusResolver { try { return await pRetry( async (attempt) => { - const result = await this.statuses + const result = await this.status .query( gql` query proofOfIndexing( @@ -226,7 +546,7 @@ export class IndexingStatusResolver { try { return await pRetry( async (attempt) => { - const result = await this.statuses + const result = await this.status .query( gql` query blockHashFromNumber($network: String!, $blockNumber: Int!) { @@ -282,52 +602,12 @@ export class IndexingStatusResolver { } } - public async subgraphDeployments(): Promise { - return (await this.subgraphDeploymentsAssignments()).map((details) => details.id) - } - - public async subgraphDeploymentsAssignments(): Promise { - try { - const result = await this.statuses - .query( - gql` - { - indexingStatuses { - subgraphDeployment: subgraph - node - } - } - `, - ) - .toPromise() - - if (result.error) { - throw result.error - } - - type QueryResult = { subgraphDeployment: string; node: string } - - return result.data.indexingStatuses - .filter((status: QueryResult) => status.node && status.node !== 'removed') - .map((status: QueryResult) => { - return { - id: new SubgraphDeploymentID(status.subgraphDeployment), - node: status.node, - } - }) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE018, error) - this.logger.error(`Failed to query indexing status API`, { err }) - throw err - } - } - public async subgraphFeatures( subgraphDeploymentId: SubgraphDeploymentID, ): Promise { const subgraphId = subgraphDeploymentId.ipfsHash try { - const result = await this.statuses + const result = await this.status .query( gql` query subgraphFeatures($subgraphId: String!) { diff --git a/packages/indexer-common/src/indexer-management/subgraphs.ts b/packages/indexer-common/src/indexer-management/subgraphs.ts deleted file mode 100644 index 7d90386f8..000000000 --- a/packages/indexer-common/src/indexer-management/subgraphs.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { - indexerError, - IndexerErrorCode, - IndexerManagementModels, -} from '@graphprotocol/indexer-common' -import { Logger, SubgraphDeploymentID } from '@graphprotocol/common-ts' -import jayson, { Client as RpcClient } from 'jayson/promise' -import pTimeout from 'p-timeout' - -export class SubgraphManager { - client: RpcClient - indexNodeIDs: string[] - - constructor(endpoint: string, indexNodeIDs: string[]) { - if (endpoint.startsWith('https')) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.client = jayson.Client.https(endpoint as any) - } else { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.client = jayson.Client.http(endpoint as any) - } - this.indexNodeIDs = indexNodeIDs - } - - async createSubgraph(logger: Logger, name: string): Promise { - try { - logger.info(`Create subgraph name`, { name }) - const response = await this.client.request('subgraph_create', { name }) - if (response.error) { - throw response.error - } - logger.info(`Successfully created subgraph name`, { name }) - } catch (error) { - if (error.message.includes('already exists')) { - logger.debug(`Subgraph name already exists`, { name }) - } - throw error - } - } - - async deploy( - logger: Logger, - models: IndexerManagementModels, - name: string, - deployment: SubgraphDeploymentID, - indexNode: string | undefined, - ): Promise { - try { - let targetNode: string - if (indexNode) { - targetNode = indexNode - if (!this.indexNodeIDs.includes(targetNode)) { - logger.warn( - `Specified deployment target node not present in indexNodeIDs supplied at startup, proceeding with deploy to target node anyway.`, - { - targetNode: indexNode, - indexNodeIDs: this.indexNodeIDs, - }, - ) - } - } else { - targetNode = - this.indexNodeIDs[Math.floor(Math.random() * this.indexNodeIDs.length)] - } - logger.info(`Deploy subgraph`, { - name, - deployment: deployment.display, - targetNode, - }) - const requestPromise = this.client.request('subgraph_deploy', { - name, - ipfs_hash: deployment.ipfsHash, - node_id: targetNode, - }) - // Timeout deployment after 2 minutes - const response = await pTimeout(requestPromise, 120000) - - if (response.error) { - throw response.error - } - logger.info(`Successfully deployed subgraph`, { - name, - deployment: deployment.display, - endpoints: response.result, - }) - - // TODO: Insert an offchain indexing rule if one matching this deployment doesn't yet exist - // Will be useful for supporting deploySubgraph resolver - } catch (error) { - const err = indexerError(IndexerErrorCode.IE026, error) - logger.error(`Failed to deploy subgraph deployment`, { - name, - deployment: deployment.display, - err, - }) - throw err - } - } - - async remove( - logger: Logger, - models: IndexerManagementModels, - deployment: SubgraphDeploymentID, - ): Promise { - try { - logger.info(`Remove subgraph deployment`, { - deployment: deployment.display, - }) - const response = await this.client.request('subgraph_reassign', { - node_id: 'removed', - ipfs_hash: deployment.ipfsHash, - }) - if (response.error) { - throw response.error - } - logger.info(`Successfully removed subgraph deployment`, { - deployment: deployment.display, - }) - - if ( - await models.IndexingRule.findOne({ - where: { identifier: deployment.ipfsHash }, - }) - ) { - logger.info( - `Remove indexing rules, so indexer-agent will not attempt to redeploy`, - ) - await models.IndexingRule.destroy({ - where: { - identifier: deployment.ipfsHash, - }, - }) - logger.info(`Sucessfully removed indexing rule for '${deployment.ipfsHash}'`) - } - } catch (error) { - const err = indexerError(IndexerErrorCode.IE027, error) - logger.error(`Failed to remove subgraph deployment`, { - deployment: deployment.display, - err, - }) - } - } - - async reassign( - logger: Logger, - deployment: SubgraphDeploymentID, - indexNode: string | undefined, - ): Promise { - let targetNode: string - if (indexNode) { - targetNode = indexNode - if (!this.indexNodeIDs.includes(targetNode)) { - logger.warn( - `Specified deployment target node not present in indexNodeIDs supplied at startup, proceeding with deploy to target node anyway.`, - { - targetNode: indexNode, - indexNodeIDs: this.indexNodeIDs, - }, - ) - } - } else { - targetNode = this.indexNodeIDs[Math.floor(Math.random() * this.indexNodeIDs.length)] - } - try { - logger.info(`Reassign subgraph deployment`, { - deployment: deployment.display, - targetNode, - }) - const response = await this.client.request('subgraph_reassign', { - node_id: targetNode, - ipfs_hash: deployment.ipfsHash, - }) - if (response.error) { - throw response.error - } - } catch (error) { - if (error.message.includes('unchanged')) { - logger.debug(`Subgraph deployment assignment unchanged`, { - deployment: deployment.display, - targetNode, - }) - throw error - } - const err = indexerError(IndexerErrorCode.IE028, error) - logger.error(`Failed to reassign subgraph deployment`, { - deployment: deployment.display, - err, - }) - throw err - } - } - - async ensure( - logger: Logger, - models: IndexerManagementModels, - name: string, - deployment: SubgraphDeploymentID, - indexNode: string | undefined, - ): Promise { - try { - await this.createSubgraph(logger, name) - await this.deploy(logger, models, name, deployment, indexNode) - await this.reassign(logger, deployment, indexNode) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE020, error) - logger.error(`Failed to ensure subgraph deployment is indexing`, { - name, - deployment: deployment.display, - targetNode: indexNode, - err, - }) - throw error - } - } -} diff --git a/packages/indexer-common/src/multi-networks.ts b/packages/indexer-common/src/multi-networks.ts new file mode 100644 index 000000000..f305d2087 --- /dev/null +++ b/packages/indexer-common/src/multi-networks.ts @@ -0,0 +1,111 @@ +import pReduce from 'p-reduce' +import isEqual from 'lodash.isequal' +import xor from 'lodash.xor' + +// A mapping of different values of type T keyed by their network identifiers +export type NetworkMapped = Record + +// Function to extract the network identifier from a value of type T +type NetworkIdentity = (element: T) => string + +// Wrapper type for performing calls over multiple values of any type, most notably +// Network and Operator instances. +// All public-facing methods should return a `NetworkMapped` or `void`. +export class MultiNetworks { + inner: NetworkMapped + constructor(elements: T[], networkIdentity: NetworkIdentity) { + if (elements.length === 0) { + throw new Error('MultiNetworks component was initialized with empty values') + } + + function reducer(accumulator: NetworkMapped, current: T): NetworkMapped { + const key = networkIdentity(current) + if (key in accumulator) { + throw new Error( + `Duplicate network identifier found while mapping value's network: ${key}`, + ) + } + // TODO: parse and validate network identifiers to standardize them + accumulator[key] = current + return accumulator + } + this.inner = elements.reduce(reducer, {}) + } + + private checkEqualKeys(a: NetworkMapped, b: NetworkMapped) { + const aKeys = Object.keys(a) + const bKeys = Object.keys(b) + if (!isEqual(aKeys, bKeys)) { + const differentKeys = xor(aKeys, bKeys) + throw new Error(`Network Mapped objects have different keys: ${differentKeys}`) + } + } + + async map(func: (value: T) => Promise): Promise> { + const entries: [string, T][] = Object.entries(this.inner) + return pReduce( + entries, + async (acc, pair) => { + const [networkIdentifier, element]: [string, T] = pair + const result = await func(element) + acc[networkIdentifier] = result + return acc + }, + {} as NetworkMapped, + ) + } + + zip(a: NetworkMapped, b: NetworkMapped): NetworkMapped<[U, V]> { + this.checkEqualKeys(a, b) + const result = {} as NetworkMapped<[U, V]> + for (const key in a) { + result[key] = [a[key], b[key]] + } + return result + } + + zip3( + a: NetworkMapped, + b: NetworkMapped, + c: NetworkMapped, + ): NetworkMapped<[U, V, W]> { + this.checkEqualKeys(a, b) + const result = {} as NetworkMapped<[U, V, W]> + for (const key in a) { + result[key] = [a[key], b[key], c[key]] + } + return result + } + + zip4( + a: NetworkMapped, + b: NetworkMapped, + c: NetworkMapped, + d: NetworkMapped, + ): NetworkMapped<[U, V, W, Y]> { + this.checkEqualKeys(a, b) + const result = {} as NetworkMapped<[U, V, W, Y]> + for (const key in a) { + result[key] = [a[key], b[key], c[key], d[key]] + } + return result + } + + async mapNetworkMapped( + nmap: NetworkMapped, + func: (inner: T, value: U) => Promise, + ): Promise> { + return pReduce( + Object.entries(nmap), + async (acc, [networkIdentifier, value]: [string, U]) => { + const inner = this.inner[networkIdentifier] + if (!inner) { + throw new Error(`Network identifier not found: ${networkIdentifier}`) + } + acc[networkIdentifier] = await func(inner, value) + return acc + }, + {} as NetworkMapped, + ) + } +} diff --git a/packages/indexer-common/src/network-specification.ts b/packages/indexer-common/src/network-specification.ts new file mode 100644 index 000000000..d2bae2c14 --- /dev/null +++ b/packages/indexer-common/src/network-specification.ts @@ -0,0 +1,163 @@ +import { toAddress, parseGRT } from '@graphprotocol/common-ts' +import { BigNumber } from 'ethers' +import { validateNetworkIdentifier, validateIpfsHash } from './parsers' +import { AllocationManagementMode } from './types' +import { z } from 'zod' + +// TODO: make sure those values are always in sync with the AllocationManagementMode enum. Can we do this in compile time? +const ALLOCATION_MANAGEMENT_MODE = ['auto', 'manual', 'oversight'] as const + +function positiveNumber(): z.ZodNumber { + return z.number().positive().finite() +} + +function GRT(): z.ZodEffects { + return z + .number() + .nonnegative() + .finite() + .transform((x) => parseGRT(x.toString())) +} + +// Gateway endpoints +export const Gateway = z + .object({ + url: z.string().url(), + }) + .strict() +export type Gateway = z.infer + +// Indexer identification and network behavior options +export const IndexerOptions = z + .object({ + address: z.string().transform(toAddress), + mnemonic: z.string(), + url: z.string().url(), + geoCoordinates: z.number().array().length(2).default([31.780715, -41.179504]), + restakeRewards: z.boolean().default(true), + rebateClaimThreshold: GRT().default(200), + rebateClaimBatchThreshold: GRT().default(200), + rebateClaimMaxBatchSize: positiveNumber().default(100), + poiDisputeMonitoring: z.boolean().default(false), + poiDisputableEpochs: positiveNumber().default(1), + defaultAllocationAmount: GRT().default(0.1), + voucherRedemptionThreshold: GRT().default(200), + voucherRedemptionBatchThreshold: GRT().default(2000), + voucherRedemptionMaxBatchSize: positiveNumber().default(100), + allocationManagementMode: z + .enum(ALLOCATION_MANAGEMENT_MODE) + .default('auto') + .transform((x) => x as AllocationManagementMode), + autoAllocationMinBatchSize: positiveNumber().default(1), + allocateOnNetworkSubgraph: z.boolean().default(false), + register: z.boolean().default(true), + }) + .strict() +export type IndexerOptions = z.infer + +// Transaction handling options +export const TransactionMonitoring = z + .object({ + gasIncreaseTimeout: positiveNumber() + .default(240) + .transform((x) => x * 10 ** 3), + gasIncreaseFactor: positiveNumber().default(1.2), + gasPriceMax: positiveNumber() + .default(100) + .transform((x) => x * 10 ** 9), + baseFeePerGasMax: positiveNumber() + .default(100) + .transform((x) => x * 10 ** 9) + .optional(), + maxTransactionAttempts: z.number().nonnegative().finite().default(0), + }) + .strict() + .default({}) // defaults will be used for instantiation when the TransactionMonitoring group is absent. + +export type TransactionMonitoring = z.infer + +// Generic subgraph specification +export const Subgraph = z + .object({ + url: z.string().url().optional(), + deployment: z.string().transform(transformIpfsHash).optional(), + }) + .strict() + .refine((obj) => !(!obj.url && !obj.deployment), { + message: 'At least one of `url` or `deployment` must be set', + }) +export type Subgraph = z.infer + +// All pertinent subgraphs in the protocol +export const ProtocolSubgraphs = z + .object({ + networkSubgraph: Subgraph, + epochSubgraph: Subgraph, + }) + .strict() + // TODO: Ensure the `url` property is always defined until Epoch Subgraph + // indexing is supported. + .refine((subgraphs) => subgraphs.epochSubgraph.url, { + message: 'Epoch Subgraph endpoint must be defined', + path: ['epochSubgraph', 'url'], + }) +export type ProtocolSubgraphs = z.infer + +export const NetworkProvider = z + .object({ + url: z.string().url(), + pollingInterval: positiveNumber().default(4 * 10 ** 3), + }) + .strict() +export type NetworkProvider = z.infer + +export const Dai = z + .object({ + contractAddress: z + .string() + .default('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') + .transform(toAddress), + inject: z.boolean().default(true), + }) + .strict() + .default({}) // defaults will be used for instantiation when the Dai group is absent. +export type Dai = z.infer + +// All necessary information to describe a Protocol Network +export const NetworkSpecification = z + .object({ + networkIdentifier: z.string().transform(transformNetworkIdentifier), + gateway: Gateway, + indexerOptions: IndexerOptions, + transactionMonitoring: TransactionMonitoring, + subgraphs: ProtocolSubgraphs, + networkProvider: NetworkProvider, + dai: Dai, + }) + .strict() + +export type NetworkSpecification = z.infer + +function transformNetworkIdentifier(input: string, ctx: z.RefinementCtx): string { + try { + return validateNetworkIdentifier(input) + } catch (e) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid network identifier', + }) + return z.NEVER + } +} + +function transformIpfsHash(input: string, ctx: z.RefinementCtx): string { + try { + return validateIpfsHash(input) + } catch (e) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Invalid IPFS hash', + }) + return z.NEVER + } +} diff --git a/packages/indexer-common/src/operator.ts b/packages/indexer-common/src/operator.ts new file mode 100644 index 000000000..2cb57739b --- /dev/null +++ b/packages/indexer-common/src/operator.ts @@ -0,0 +1,571 @@ +import { + ActionFilter, + ActionItem, + ActionResult, + ActionStatus, + ActionType, + Allocation, + AllocationDecision, + AllocationManagementMode, + INDEXING_RULE_GLOBAL, + IndexerErrorCode, + IndexerManagementClient, + IndexingRuleAttributes, + SubgraphIdentifierType, + indexerError, + specification as spec, + Action, + POIDisputeAttributes, +} from '@graphprotocol/indexer-common' +import { Logger, formatGRT } from '@graphprotocol/common-ts' +import { BigNumber, utils } from 'ethers' +import gql from 'graphql-tag' +import pMap from 'p-map' +import { CombinedError } from '@urql/core' + +const POI_DISPUTES_CONVERTERS_FROM_GRAPHQL: Record< + keyof POIDisputeAttributes, + (x: never) => string | BigNumber | number | null +> = { + allocationID: (x) => x, + subgraphDeploymentID: (x) => x, + allocationIndexer: (x) => x, + allocationAmount: (x) => x, + allocationProof: (x) => x, + closedEpoch: (x) => +x, + closedEpochStartBlockHash: (x) => x, + closedEpochStartBlockNumber: (x) => +x, + closedEpochReferenceProof: (x) => x, + previousEpochStartBlockHash: (x) => x, + previousEpochStartBlockNumber: (x) => +x, + previousEpochReferenceProof: (x) => x, + status: (x) => x, + protocolNetwork: (x) => x, +} + +/** + * Parses a POI dispute returned from the indexer management GraphQL + * API into normalized form. + */ +const disputeFromGraphQL = ( + dispute: Partial, +): POIDisputeAttributes => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const obj = {} as any + for (const [key, value] of Object.entries(dispute)) { + if (key === '__typename') { + continue + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + obj[key] = (POI_DISPUTES_CONVERTERS_FROM_GRAPHQL as any)[key](value) + } + return obj as POIDisputeAttributes +} + +// This component is responsible for managing indexing rules, actions, allocations, and +// POI disputes. +export class Operator { + logger: Logger + indexerManagement: IndexerManagementClient + specification: spec.NetworkSpecification + + constructor( + logger: Logger, + indexerManagement: IndexerManagementClient, + specification: spec.NetworkSpecification, + ) { + this.logger = logger.child({ + component: 'Operator', + protocolNetwork: specification.networkIdentifier, + }) + this.indexerManagement = indexerManagement + this.specification = specification + } + + // -------------------------------------------------------------------------------- + // * Indexing Rules + // -------------------------------------------------------------------------------- + // Retrieves the indexing rules from the indexer management API. + async indexingRules(merged: boolean): Promise { + try { + this.logger.debug('Fetching indexing rules') + const result = await this.indexerManagement + .query( + gql` + query indexingRules($merged: Boolean!, $protocolNetwork: String) { + indexingRules(merged: $merged, protocolNetwork: $protocolNetwork) { + identifier + identifierType + allocationAmount + allocationLifetime + autoRenewal + parallelAllocations + maxAllocationPercentage + minSignal + maxSignal + minStake + minAverageQueryFees + custom + decisionBasis + requireSupported + protocolNetwork + } + } + `, + { merged, protocolNetwork: this.specification.networkIdentifier }, + ) + .toPromise() + + if (result.error) { + throw result.error + } + this.logger.trace('Fetched indexing rules', { + count: result.data.indexingRules.length, + rules: result.data.indexingRules.map((rule: IndexingRuleAttributes) => { + return { + identifier: rule.identifier, + identifierType: rule.identifierType, + decisionBasis: rule.decisionBasis, + protocolNetwork: rule.protocolNetwork, + } + }), + }) + return result.data.indexingRules + } catch (error) { + const err = indexerError(IndexerErrorCode.IE025, error) + this.logger.error(`Failed to query indexer management API`, { err }) + throw err + } + } + + async ensureGlobalIndexingRule(): Promise { + const identifier = { + identifier: INDEXING_RULE_GLOBAL, + protocolNetwork: this.specification.networkIdentifier, + } + try { + const globalRule = await this.indexerManagement + .query( + gql` + query indexingRule($identifier: IndexingRuleIdentifier!) { + indexingRule(identifier: $identifier, merged: false) { + identifier + identifierType + allocationAmount + decisionBasis + requireSupported + protocolNetwork + } + } + `, + { identifier }, + ) + .toPromise() + + if (!globalRule.data.indexingRule) { + this.logger.info(`Creating default "global" indexing rule`) + + const defaults = { + ...identifier, + identifierType: SubgraphIdentifierType.GROUP, + allocationAmount: + this.specification.indexerOptions.defaultAllocationAmount.toString(), + parallelAllocations: 1, + decisionBasis: 'rules', + requireSupported: true, + safety: true, + } + + const defaultGlobalRule = await this.indexerManagement + .mutation( + gql` + mutation setIndexingRule($rule: IndexingRuleInput!) { + setIndexingRule(rule: $rule) { + identifier + identifierType + allocationAmount + allocationLifetime + autoRenewal + parallelAllocations + maxAllocationPercentage + minSignal + maxSignal + minStake + minAverageQueryFees + custom + decisionBasis + requireSupported + safety + protocolNetwork + } + } + `, + { rule: defaults }, + ) + .toPromise() + + if (defaultGlobalRule.error) { + throw defaultGlobalRule.error + } + + this.logger.info(`Created default "global" indexing rule`, { + rule: defaultGlobalRule.data.setIndexingRule, + }) + } + } catch (error) { + const err = indexerError(IndexerErrorCode.IE017, error) + this.logger.warn('Failed to ensure default "global" indexing rule', { + err, + }) + throw err + } + } + + // -------------------------------------------------------------------------------- + // * Actions + // -------------------------------------------------------------------------------- + + async fetchActions(actionFilter: ActionFilter): Promise { + const result = await this.indexerManagement + .query( + gql` + query actions($filter: ActionFilter!) { + actions(filter: $filter) { + id + type + allocationID + deploymentID + amount + poi + force + source + reason + priority + transaction + status + failureReason + } + } + `, + { filter: actionFilter }, + ) + .toPromise() + + if (result.error) { + throw result.error + } + + return result.data.actions + } + + async queueAction(action: ActionItem): Promise { + let status = ActionStatus.QUEUED + switch (this.specification.indexerOptions.allocationManagementMode) { + case AllocationManagementMode.MANUAL: + throw Error(`Cannot queue actions when AllocationManagementMode = 'MANUAL'`) + case AllocationManagementMode.AUTO: + status = ActionStatus.APPROVED + break + case AllocationManagementMode.OVERSIGHT: + status = ActionStatus.QUEUED + } + + const actionInput = { + ...action.params, + status, + type: action.type, + source: 'indexerAgent', + reason: action.reason, + priority: 0, + protocolNetwork: action.protocolNetwork, + } + this.logger.trace(`Queueing action input`, { + actionInput, + }) + const actionResult = await this.indexerManagement + .mutation( + gql` + mutation queueActions($actions: [ActionInput!]!) { + queueActions(actions: $actions) { + id + type + deploymentID + source + reason + priority + status + protocolNetwork + } + } + `, + { actions: [actionInput] }, + ) + .toPromise() + + if (actionResult.error) { + if ( + actionResult.error instanceof CombinedError && + actionResult.error.message.includes('Duplicate') + ) { + this.logger.warn( + `Action not queued: Already a queued action targeting ${actionInput.deploymentID} from another source`, + { action }, + ) + return [] + } + throw actionResult.error + } + + if (actionResult.data.queueActions.length > 0) { + this.logger.info(`Queued ${action.type} action for execution`, { + queuedAction: actionResult.data.queueActions, + }) + } + + return actionResult.data.queueActions + } + + // -------------------------------------------------------------------------------- + // * Allocations + // -------------------------------------------------------------------------------- + async createAllocation( + logger: Logger, + deploymentAllocationDecision: AllocationDecision, + mostRecentlyClosedAllocation: Allocation | undefined, + ): Promise { + const desiredAllocationAmount = deploymentAllocationDecision.ruleMatch.rule + ?.allocationAmount + ? BigNumber.from(deploymentAllocationDecision.ruleMatch.rule.allocationAmount) + : this.specification.indexerOptions.defaultAllocationAmount + + logger.info(`No active allocation for deployment, creating one now`, { + allocationAmount: formatGRT(desiredAllocationAmount), + }) + + // Skip allocating if the previous allocation for this deployment was closed with 0x00 POI but rules set to un-safe + if ( + deploymentAllocationDecision.ruleMatch.rule?.safety && + mostRecentlyClosedAllocation && + mostRecentlyClosedAllocation.poi === utils.hexlify(Array(32).fill(0)) + ) { + logger.warn( + `Skipping allocation to this deployment as the last allocation to it was closed with a zero POI`, + { + notSafe: !deploymentAllocationDecision.ruleMatch.rule?.safety, + deployment: deploymentAllocationDecision.deployment, + closedAllocation: mostRecentlyClosedAllocation.id, + }, + ) + return + } + + // Send AllocateAction to the queue + await this.queueAction({ + params: { + deploymentID: deploymentAllocationDecision.deployment.ipfsHash, + amount: formatGRT(desiredAllocationAmount), + }, + type: ActionType.ALLOCATE, + reason: deploymentAllocationDecision.reasonString(), + protocolNetwork: deploymentAllocationDecision.protocolNetwork, + }) + + return + } + + async closeEligibleAllocations( + logger: Logger, + deploymentAllocationDecision: AllocationDecision, + activeDeploymentAllocations: Allocation[], + epoch: number, + ): Promise { + const activeDeploymentAllocationsEligibleForClose = activeDeploymentAllocations + .filter((allocation) => allocation.createdAtEpoch < epoch) + .map((allocation) => allocation.id) + // Make sure to close all active allocations on the way out + if (activeDeploymentAllocationsEligibleForClose.length > 0) { + logger.info( + `Deployment is not (or no longer) worth allocating towards, close allocation`, + { + eligibleForClose: activeDeploymentAllocationsEligibleForClose, + }, + ) + await pMap( + // We can only close allocations from a previous epoch; + // try the others again later + activeDeploymentAllocationsEligibleForClose, + async (allocation) => { + // Send unallocate action to the queue + await this.queueAction({ + params: { + allocationID: allocation, + deploymentID: deploymentAllocationDecision.deployment.ipfsHash, + poi: undefined, + force: false, + }, + type: ActionType.UNALLOCATE, + reason: deploymentAllocationDecision.reasonString(), + protocolNetwork: deploymentAllocationDecision.protocolNetwork, + } as ActionItem) + }, + { concurrency: 1 }, + ) + } + } + + async refreshExpiredAllocations( + logger: Logger, + deploymentAllocationDecision: AllocationDecision, + expiredAllocations: Allocation[], + ): Promise { + if (deploymentAllocationDecision.ruleMatch.rule?.autoRenewal) { + logger.info(`Reallocating expired allocations`, { + number: expiredAllocations.length, + expiredAllocations: expiredAllocations.map((allocation) => allocation.id), + }) + + const desiredAllocationAmount = deploymentAllocationDecision.ruleMatch.rule + ?.allocationAmount + ? BigNumber.from(deploymentAllocationDecision.ruleMatch.rule.allocationAmount) + : this.specification.indexerOptions.defaultAllocationAmount + + // Queue reallocate actions to be picked up by the worker + await pMap(expiredAllocations, async (allocation) => { + await this.queueAction({ + params: { + allocationID: allocation.id, + deploymentID: deploymentAllocationDecision.deployment.ipfsHash, + amount: formatGRT(desiredAllocationAmount), + }, + type: ActionType.REALLOCATE, + reason: `${deploymentAllocationDecision.reasonString()}:allocationExpiring`, // Need to update to include 'ExpiringSoon' + protocolNetwork: deploymentAllocationDecision.protocolNetwork, + }) + }) + } else { + logger.info( + `Skipping reallocating expired allocation since the corresponding rule has 'autoRenewal' = False`, + { + number: expiredAllocations.length, + expiredAllocations: expiredAllocations.map((allocation) => allocation.id), + }, + ) + } + return + } + // -------------------------------------------------------------------------------- + // POI Disputes + // -------------------------------------------------------------------------------- + + async storePoiDisputes( + disputes: POIDisputeAttributes[], + ): Promise { + try { + const result = await this.indexerManagement + .mutation( + gql` + mutation storeDisputes($disputes: [POIDisputeInput!]!) { + storeDisputes(disputes: $disputes) { + allocationID + subgraphDeploymentID + allocationIndexer + allocationAmount + allocationProof + closedEpoch + closedEpochStartBlockHash + closedEpochStartBlockNumber + closedEpochReferenceProof + previousEpochStartBlockHash + previousEpochStartBlockNumber + previousEpochReferenceProof + status + protocolNetwork + } + } + `, + { disputes: disputes }, + ) + .toPromise() + + if (result.error) { + throw result.error + } + + return result.data.storeDisputes.map( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dispute: Record) => { + return disputeFromGraphQL(dispute) + }, + ) + } catch (error) { + const err = indexerError(IndexerErrorCode.IE039, error) + this.logger.error('Failed to store potential POI disputes', { + err, + }) + throw err + } + } + + async fetchPOIDisputes( + status: string, + minClosedEpoch: number, + protocolNetwork: string | undefined, + ): Promise { + try { + const result = await this.indexerManagement + .query( + gql` + query disputes( + $status: String! + $minClosedEpoch: Int! + $protocolNetwork: String! + ) { + disputes( + status: $status + minClosedEpoch: $minClosedEpoch + protocolNetwork: $protocolNetwork + ) { + allocationID + subgraphDeploymentID + allocationIndexer + allocationAmount + allocationProof + closedEpoch + closedEpochStartBlockHash + closedEpochStartBlockNumber + closedEpochReferenceProof + previousEpochStartBlockHash + previousEpochStartBlockNumber + previousEpochReferenceProof + status + protocolNetwork + } + } + `, + { + status, + minClosedEpoch, + protocolNetwork, + }, + ) + .toPromise() + + if (result.error) { + throw result.error + } + + return result.data.disputes.map( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (dispute: Record) => { + return disputeFromGraphQL(dispute) + }, + ) + } catch (error) { + const err = indexerError(IndexerErrorCode.IE039, error) + this.logger.error('Failed to store potential POI disputes', { + err, + }) + throw err + } + } +} diff --git a/packages/indexer-common/src/parsers/__tests__/parsers.ts b/packages/indexer-common/src/parsers/__tests__/parsers.ts new file mode 100644 index 000000000..4a7595270 --- /dev/null +++ b/packages/indexer-common/src/parsers/__tests__/parsers.ts @@ -0,0 +1,10 @@ +import { validateNetworkIdentifier } from '../validators' + +describe('validateNetworkIdentifier tests', () => { + it('should parse valid network identifiers', () => { + expect(validateNetworkIdentifier('goerli')).toBe('eip155:5') + expect(validateNetworkIdentifier('mainnet')).toBe('eip155:1') + expect(validateNetworkIdentifier('eip155:1')).toBe('eip155:1') + expect(validateNetworkIdentifier('eip155:5')).toBe('eip155:5') + }) +}) diff --git a/packages/indexer-common/src/parsers/basic-types.ts b/packages/indexer-common/src/parsers/basic-types.ts new file mode 100644 index 000000000..68b945b5e --- /dev/null +++ b/packages/indexer-common/src/parsers/basic-types.ts @@ -0,0 +1,34 @@ +// Parser combinators for basic types + +import P from 'parsimmon' +import { resolveChainId } from '../indexer-management/types' + +// Checks if the provided network identifier is supported by the Indexer Agent. +function validateNetworkIdentifier(n: string): P.Parser { + try { + const valid = resolveChainId(n) + return P.succeed(valid) + } catch (e) { + return P.fail('a supported network identifier') + } +} + +// A basic URL parser. +export const url = P.regex(/^https?:.*/) + .map((x) => new URL(x)) + .desc('a valid URL') + +// Source: https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-2.md +export const caip2IdRegex = /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/ +const caip2Id = P.regex(caip2IdRegex).chain(validateNetworkIdentifier) + +// A valid human friendly network name / alias. +const networkAlias = P.regex(/[a-z-]+/).chain(validateNetworkIdentifier) + +// Either a CAIP-2 or an alias. +export const networkIdentifier = P.alt(caip2Id, networkAlias) + +// A basic `base58btc` parser for CIDv0 (IPFS Hashes) +export const base58 = P.regex(/^Qm[1-9A-HJ-NP-Za-km-z]{44,}$/).desc( + 'An IPFS Content Identifer (Qm...)', +) diff --git a/packages/indexer-common/src/parsers/index.ts b/packages/indexer-common/src/parsers/index.ts new file mode 100644 index 000000000..2ec2291fc --- /dev/null +++ b/packages/indexer-common/src/parsers/index.ts @@ -0,0 +1 @@ +export * from './validators' diff --git a/packages/indexer-common/src/parsers/validators.ts b/packages/indexer-common/src/parsers/validators.ts new file mode 100644 index 000000000..cf81e70a2 --- /dev/null +++ b/packages/indexer-common/src/parsers/validators.ts @@ -0,0 +1,30 @@ +// Final parsers (validators) that use parser combinators defined in the 'parsers' module but don't +// expose their internal parsing interface. + +import P from 'parsimmon' + +import { networkIdentifier, base58 } from './basic-types' +export { caip2IdRegex } from './basic-types' + +// Generic function that takes a parser of type T and attempts to parse it from a string. If it +// fails, then it will throw an error with an explanation of what was expected, as well as the +// portion of the input that was parsed and what's remaining to parse. +function parse(parser: P.Parser, input: string): T { + const parseResult = parser.parse(input) + if (parseResult.status) { + return parseResult.value + } + const expected = parseResult.expected[0] + const parsed = input.slice(0, parseResult.index.offset) + const remaining = input.slice(parseResult.index.offset) + throw new Error( + `Failed to parse "${input}". Expected: ${expected}. Parsed up to: "${parsed}". Remaining: "${remaining}"`, + ) +} +export function validateNetworkIdentifier(input: string): string { + return parse(networkIdentifier, input) +} + +export function validateIpfsHash(input: string): string { + return parse(base58, input) +} From 16fbb91c8b7f82e1451daa09db9ce0190f01fc5e Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:05:08 -0300 Subject: [PATCH 04/16] common: updated existing modules for the new protocolNetwork field --- packages/indexer-common/src/actions.ts | 22 +- .../src/allocations/query-fees.ts | 152 ++-- packages/indexer-common/src/errors.ts | 4 +- packages/indexer-common/src/index.ts | 12 +- .../indexer-common/src/network-subgraph.ts | 43 +- packages/indexer-common/src/network.ts | 683 +++++++----------- .../src/query-fees/allocation-utils.ts | 2 + .../indexer-common/src/query-fees/models.ts | 51 ++ packages/indexer-common/src/rules.ts | 2 + packages/indexer-common/src/subgraphs.ts | 17 +- packages/indexer-common/src/transactions.ts | 44 +- packages/indexer-common/src/types.ts | 21 +- packages/indexer-common/src/utils.ts | 39 + 13 files changed, 571 insertions(+), 521 deletions(-) diff --git a/packages/indexer-common/src/actions.ts b/packages/indexer-common/src/actions.ts index 7a464a1e1..a6f920ab8 100644 --- a/packages/indexer-common/src/actions.ts +++ b/packages/indexer-common/src/actions.ts @@ -1,8 +1,9 @@ -import { ActionManager, NetworkMonitor } from './indexer-management' +import { NetworkMonitor } from './indexer-management' import { AllocationStatus } from './allocations' import { WhereOperators, WhereOptions } from 'sequelize' import { Op } from 'sequelize' import { WhereAttributeHashValue } from 'sequelize/types/model' +import { validateNetworkIdentifier } from './parsers' export interface ActionParamsInput { deploymentID?: string @@ -17,6 +18,7 @@ export interface ActionItem { type: ActionType reason: string status?: ActionStatus + protocolNetwork: string } export interface ActionUpdateInput { @@ -28,6 +30,7 @@ export interface ActionUpdateInput { type?: ActionType status?: ActionStatus reason?: string + protocolNetwork?: string } export interface ActionInput { @@ -41,6 +44,7 @@ export interface ActionInput { reason: string status: ActionStatus priority: number | undefined + protocolNetwork: string } export const isValidActionInput = ( @@ -76,12 +80,23 @@ export const isValidActionInput = ( export const validateActionInputs = async ( actions: ActionInput[], - actionManager: ActionManager, networkMonitor: NetworkMonitor, ): Promise => { // Validate actions before adding to queue // TODO: Perform all checks simultaneously and throw combined error if 1 or more fail for (const action of actions) { + // Must have a valid protocol network identifier + if (!action.protocolNetwork) { + throw Error("Cannot set an action without the field 'protocolNetwork'") + } + + try { + // Set the parsed network identifier back in the action input object + action.protocolNetwork = validateNetworkIdentifier(action.protocolNetwork) + } catch (e) { + throw Error(`Invalid value for the field 'protocolNetwork'. ${e}`) + } + // Must have the required params for the action type if (!isValidActionInput(action)) { throw new Error( @@ -143,6 +158,7 @@ export interface ActionFilter { source?: string reason?: string updatedAt?: WhereOperators + protocolNetwork?: string } export const actionFilterToWhereOptions = (filter: ActionFilter): WhereOptions => { @@ -173,6 +189,7 @@ export interface ActionResult { priority: number | undefined failureReason: string | null transaction: string | null + protocolNetwork: string } export enum ActionType { @@ -205,4 +222,5 @@ export enum ActionParams { PRIORITY = 'priority', CREATED_AT = 'createdAt', UPDATED_AT = 'updatedAt', + PROTOCOL_NETWORK = 'protocolNetwork', } diff --git a/packages/indexer-common/src/allocations/query-fees.ts b/packages/indexer-common/src/allocations/query-fees.ts index 8bf1674ad..ebfbeb111 100644 --- a/packages/indexer-common/src/allocations/query-fees.ts +++ b/packages/indexer-common/src/allocations/query-fees.ts @@ -18,7 +18,8 @@ import { Voucher, ensureAllocationSummary, TransactionManager, -} from '@graphprotocol/indexer-common' + specification as spec, +} from '..' import { DHeap } from '@thi.ng/heaps' import { BigNumber, BigNumberish, Contract } from 'ethers' import { Op } from 'sequelize' @@ -66,10 +67,7 @@ export interface AllocationReceiptCollectorOptions { transactionManager: TransactionManager allocationExchange: Contract models: QueryFeeModels - collectEndpoint: string - voucherRedemptionThreshold: BigNumber - voucherRedemptionBatchThreshold: BigNumber - voucherRedemptionMaxBatchSize: number + networkSpecification: spec.NetworkSpecification } export interface ReceiptCollector { @@ -90,37 +88,40 @@ export class AllocationReceiptCollector implements ReceiptCollector { private voucherRedemptionThreshold: BigNumber private voucherRedemptionBatchThreshold: BigNumber private voucherRedemptionMaxBatchSize: number + private protocolNetwork: string constructor({ logger, metrics, transactionManager, models, - collectEndpoint, allocationExchange, - voucherRedemptionThreshold, - voucherRedemptionBatchThreshold, - voucherRedemptionMaxBatchSize, + networkSpecification, }: AllocationReceiptCollectorOptions) { this.logger = logger.child({ component: 'AllocationReceiptCollector' }) - this.metrics = registerReceiptMetrics(metrics) + this.metrics = registerReceiptMetrics(metrics, networkSpecification.networkIdentifier) this.transactionManager = transactionManager this.models = models - this.collectEndpoint = new URL(collectEndpoint) this.allocationExchange = allocationExchange - this.partialVoucherEndpoint = new URL( - collectEndpoint.replace('/collect-receipts', '/partial-voucher'), - ) - this.voucherEndpoint = new URL( - collectEndpoint.replace('/collect-receipts', '/voucher'), - ) - - this.voucherRedemptionThreshold = voucherRedemptionThreshold - this.voucherRedemptionBatchThreshold = voucherRedemptionBatchThreshold - this.voucherRedemptionMaxBatchSize = voucherRedemptionMaxBatchSize - + this.protocolNetwork = networkSpecification.networkIdentifier + + // Process Gateway routes + const gatewayUrls = processGatewayRoutes(networkSpecification.gateway.url) + this.collectEndpoint = gatewayUrls.collectReceipts + this.voucherEndpoint = gatewayUrls.voucher + this.partialVoucherEndpoint = gatewayUrls.partialVoucher + + const { indexerOptions } = networkSpecification + this.voucherRedemptionThreshold = indexerOptions.voucherRedemptionThreshold + this.voucherRedemptionBatchThreshold = indexerOptions.voucherRedemptionBatchThreshold + this.voucherRedemptionMaxBatchSize = indexerOptions.voucherRedemptionMaxBatchSize + + // Start the AllocationReceiptCollector + // TODO: Consider calling methods conditionally based on a boolean + // flag during startup. this.startReceiptCollecting() this.startVoucherProcessing() + this.queuePendingReceiptsFromDatabase() } async rememberAllocations( @@ -143,6 +144,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { this.models, allocation, transaction, + this.protocolNetwork, ) await summary.save() } @@ -177,14 +179,17 @@ export class AllocationReceiptCollector implements ReceiptCollector { await this.models.allocationSummaries.update( { closedAt: now }, { - where: { allocation: allocation.id }, + where: { + allocation: allocation.id, + protocolNetwork: this.protocolNetwork, + }, transaction, }, ) // Return all receipts for the just-closed allocation return this.models.allocationReceipts.findAll({ - where: { allocation: allocation.id }, + where: { allocation: allocation.id, protocolNetwork: this.protocolNetwork }, order: ['id'], transaction, }) @@ -257,7 +262,13 @@ export class AllocationReceiptCollector implements ReceiptCollector { private startVoucherProcessing() { timer(30_000).pipe(async () => { - const pendingVouchers = await this.pendingVouchers() // Ordered by value + let pendingVouchers: Voucher[] = [] + try { + pendingVouchers = await this.pendingVouchers() // Ordered by value + } catch (err) { + this.logger.warn(`Failed to query pending vouchers`, { err }) + return + } const logger = this.logger.child({}) @@ -267,7 +278,10 @@ export class AllocationReceiptCollector implements ReceiptCollector { if (await this.allocationExchange.allocationsRedeemed(voucher.allocation)) { try { await this.models.vouchers.destroy({ - where: { allocation: voucher.allocation }, + where: { + allocation: voucher.allocation, + protocolNetwork: this.protocolNetwork, + }, }) logger.warn( `Query fee voucher for allocation already redeemed, deleted local voucher copy`, @@ -343,6 +357,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { private async pendingVouchers(): Promise { return this.models.vouchers.findAll({ + where: { protocolNetwork: this.protocolNetwork }, order: [['amount', 'DESC']], // sorted by highest value to maximise the value of the batch limit: this.voucherRedemptionMaxBatchSize, // limit the number of vouchers to the max batch size }) @@ -433,11 +448,23 @@ export class AllocationReceiptCollector implements ReceiptCollector { ) } - const voucher = response.data as { + logger.trace('Gateway response', { + response, + allocation, + }) + + // Depending of which Gateway endpoint was used, fee information can come in different fields + const fees = response.data.fees ?? response.data.amount + if (!fees || !response.data.allocation || !response.data.signature) { + throw new Error('Failed to parse response from Gateay') + } + + const voucher = { ...response.data, fees } as { allocation: string fees: string signature: string } + this.metrics.vouchers.inc({ allocation, }) @@ -456,6 +483,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { await this.models.allocationReceipts.destroy({ where: { id: receipts.map((receipt) => receipt.id), + protocolNetwork: this.protocolNetwork, }, transaction, }) @@ -469,6 +497,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { this.models, toAddress(voucher.allocation), transaction, + this.protocolNetwork, ) summary.collectedFees = BigNumber.from(summary.collectedFees) .add(voucher.fees) @@ -477,11 +506,15 @@ export class AllocationReceiptCollector implements ReceiptCollector { // Add the voucher to the database await this.models.vouchers.findOrCreate({ - where: { allocation: toAddress(voucher.allocation) }, + where: { + allocation: toAddress(voucher.allocation), + protocolNetwork: this.protocolNetwork, + }, defaults: { allocation: toAddress(voucher.allocation), amount: voucher.fees, signature: voucher.signature, + protocolNetwork: this.protocolNetwork, }, transaction, }) @@ -543,6 +576,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { this.models, toAddress(voucher.allocation), transaction, + this.protocolNetwork, ) summary.withdrawnFees = BigNumber.from(summary.withdrawnFees) .add(voucher.amount) @@ -564,7 +598,10 @@ export class AllocationReceiptCollector implements ReceiptCollector { logger.info(`Successfully redeemed query fee voucher, delete local copy`) try { await this.models.vouchers.destroy({ - where: { allocation: vouchers.map((voucher) => voucher.allocation) }, + where: { + allocation: vouchers.map((voucher) => voucher.allocation), + protocolNetwork: this.protocolNetwork, + }, }) this.metrics.successVoucherRedeems.inc({ allocation: vouchers[0].allocation }) logger.info(`Successfully deleted local voucher copy`) @@ -578,7 +615,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { public async queuePendingReceiptsFromDatabase(): Promise { // Obtain all closed allocations const closedAllocations = await this.models.allocationSummaries.findAll({ - where: { closedAt: { [Op.not]: null } }, + where: { closedAt: { [Op.not]: null }, protocolNetwork: this.protocolNetwork }, }) // Create a receipts batch for each of these allocations @@ -596,6 +633,7 @@ export class AllocationReceiptCollector implements ReceiptCollector { const uncollectedReceipts = await this.models.allocationReceipts.findAll({ where: { allocation: closedAllocations.map((summary) => summary.allocation), + protocolNetwork: this.protocolNetwork, }, order: ['id'], }) @@ -643,80 +681,110 @@ export function encodePartialVouchers( } } -const registerReceiptMetrics = (metrics: Metrics) => ({ +const registerReceiptMetrics = (metrics: Metrics, networkIdentifier: string) => ({ receiptsToCollect: new metrics.client.Gauge({ - name: 'indexer_agent_receipts_to_collect', + name: `indexer_agent_receipts_to_collect_${networkIdentifier}`, help: 'Individual receipts to collect', registers: [metrics.registry], labelNames: ['allocation'], }), failedReceipts: new metrics.client.Counter({ - name: 'indexer_agent_receipts_failed', + name: `indexer_agent_receipts_failed_${networkIdentifier}`, help: 'Failed to queue receipts to collect', registers: [metrics.registry], labelNames: ['allocation'], }), partialVouchersToExchange: new metrics.client.Gauge({ - name: 'indexer_agent_vouchers_to_exchange', + name: `indexer_agent_vouchers_to_exchange_${networkIdentifier}`, help: 'Individual partial vouchers to exchange', registers: [metrics.registry], labelNames: ['allocation'], }), receiptsCollectDuration: new metrics.client.Histogram({ - name: 'indexer_agent_receipts_exchange_duration', + name: `indexer_agent_receipts_exchange_duration_${networkIdentifier}`, help: 'Duration of processing and exchanging receipts to voucher', registers: [metrics.registry], labelNames: ['allocation'], }), vouchers: new metrics.client.Counter({ - name: 'indexer_agent_vouchers', + name: `indexer_agent_vouchers_${networkIdentifier}`, help: 'Individual vouchers to redeem', registers: [metrics.registry], labelNames: ['allocation'], }), successVoucherRedeems: new metrics.client.Counter({ - name: 'indexer_agent_voucher_exchanges_ok', + name: `indexer_agent_voucher_exchanges_ok_${networkIdentifier}`, help: 'Successfully redeemed vouchers', registers: [metrics.registry], labelNames: ['allocation'], }), invalidVoucherRedeems: new metrics.client.Counter({ - name: 'indexer_agent_voucher_exchanges_invalid', + name: `indexer_agent_voucher_exchanges_invalid_${networkIdentifier}`, help: 'Invalid vouchers redeems - tx paused or unauthorized', registers: [metrics.registry], labelNames: ['allocation'], }), failedVoucherRedeems: new metrics.client.Counter({ - name: 'indexer_agent_voucher_redeems_failed', + name: `indexer_agent_voucher_redeems_failed_${networkIdentifier}`, help: 'Failed redeems for vouchers', registers: [metrics.registry], labelNames: ['allocation'], }), vouchersRedeemDuration: new metrics.client.Histogram({ - name: 'indexer_agent_vouchers_redeem_duration', + name: `indexer_agent_vouchers_redeem_duration_${networkIdentifier}`, help: 'Duration of redeeming vouchers', registers: [metrics.registry], labelNames: ['allocation'], }), vouchersBatchRedeemSize: new metrics.client.Gauge({ - name: 'indexer_agent_vouchers_redeem', + name: `indexer_agent_vouchers_redeem_${networkIdentifier}`, help: 'Size of redeeming batched vouchers', registers: [metrics.registry], }), voucherCollectedFees: new metrics.client.Gauge({ - name: 'indexer_agent_voucher_collected_fees', + name: `indexer_agent_voucher_collected_fees_${networkIdentifier}`, help: 'Amount of query fees collected for a voucher', registers: [metrics.registry], labelNames: ['allocation'], }), }) + +interface GatewayRoutes { + collectReceipts: URL + voucher: URL + partialVoucher: URL +} + +function processGatewayRoutes(input: string): GatewayRoutes { + const GATEWAY_ROUTES = { + collectReceipts: 'collect-receipts', + voucher: 'voucher', + partialVoucher: 'partial-voucher', + } + + // Strip existing information except for protocol and host + const inputURL = new URL(input) + const base = `${inputURL.protocol}//${inputURL.host}` + + function route(pathname: string): URL { + const url = new URL(base) + url.pathname = pathname + return url + } + + return { + collectReceipts: route(GATEWAY_ROUTES.collectReceipts), + voucher: route(GATEWAY_ROUTES.voucher), + partialVoucher: route(GATEWAY_ROUTES.partialVoucher), + } +} diff --git a/packages/indexer-common/src/errors.ts b/packages/indexer-common/src/errors.ts index a5433f612..01af19fa2 100644 --- a/packages/indexer-common/src/errors.ts +++ b/packages/indexer-common/src/errors.ts @@ -84,6 +84,7 @@ export enum IndexerErrorCode { IE071 = 'IE071', IE072 = 'IE072', IE073 = 'IE073', + IE074 = 'IE074', } export const INDEXER_ERROR_MESSAGES: Record = { @@ -95,7 +96,7 @@ export const INDEXER_ERROR_MESSAGES: Record = { IE006: 'Failed to cross-check allocation state with contracts', IE007: 'Failed to check for network pause', IE008: 'Failed to check operator status for indexer', - IE009: 'Failed to query subgraph deployments worth indexing', + IE009: 'Failed to query subgraph deployments', IE010: 'Failed to query indexer allocations', IE011: 'Failed to query claimable indexer allocations', IE012: 'Failed to register indexer', @@ -161,6 +162,7 @@ export const INDEXER_ERROR_MESSAGES: Record = { IE071: 'Add Epoch subgraph support for non-protocol chains', IE072: 'Failed to execute batch tx (contract: staking)', IE073: 'Failed to query subgraph features from indexing statuses endpoint', + IE074: 'Failed to deploy subgraph: network not supported', } export type IndexerErrorCause = unknown diff --git a/packages/indexer-common/src/index.ts b/packages/indexer-common/src/index.ts index 3c931d0d2..0090cad0d 100644 --- a/packages/indexer-common/src/index.ts +++ b/packages/indexer-common/src/index.ts @@ -1,15 +1,19 @@ export * from './actions' export * from './allocations' export * from './async-cache' +export * from './epoch-subgraph' export * from './errors' -export * from './query-fees' export * from './indexer-management' -export * from './indexing-status' +export * from './graph-node' +export * from './operator' export * from './network' export * from './network-subgraph' -export * from './epoch-subgraph' +export * from './query-fees' export * from './rules' export * from './subgraphs' export * from './transactions' -export * from './utils' export * from './types' +export * from './utils' +export * from './parsers' +export * as specification from './network-specification' +export * from './multi-networks' diff --git a/packages/indexer-common/src/network-subgraph.ts b/packages/indexer-common/src/network-subgraph.ts index b3fe18997..893fdcab3 100644 --- a/packages/indexer-common/src/network-subgraph.ts +++ b/packages/indexer-common/src/network-subgraph.ts @@ -3,14 +3,13 @@ import { Eventual, Logger, SubgraphDeploymentID, timer } from '@graphprotocol/co import { DocumentNode, print } from 'graphql' import { OperationResult, CombinedError } from '@urql/core' import { BlockPointer, IndexingError } from './types' -import { IndexingStatusResolver } from './indexing-status' +import { GraphNode } from './graph-node' export interface NetworkSubgraphCreateOptions { logger: Logger endpoint?: string deployment?: { - indexingStatusResolver: IndexingStatusResolver - graphNodeQueryEndpoint: string + graphNode: GraphNode deployment: SubgraphDeploymentID } } @@ -29,7 +28,7 @@ interface NetworkSubgraphOptions { deployment?: { id: SubgraphDeploymentID status: Eventual - graphNodeQueryEndpoint: string + graphNode: GraphNode } } @@ -45,8 +44,8 @@ export class NetworkSubgraph { public readonly deployment?: { id: SubgraphDeploymentID - client: AxiosInstance status: Eventual + endpointClient: AxiosInstance } private constructor(options: NetworkSubgraphOptions) { @@ -66,26 +65,16 @@ export class NetworkSubgraph { } if (options.deployment) { - const client = axios.create({ - baseURL: new URL( - `/subgraphs/id/${options.deployment.id.ipfsHash}`, - options.deployment.graphNodeQueryEndpoint, - ).toString(), - - headers: { 'content-type': 'application/json' }, - - // Don't parse responses as JSON - responseType: 'text', - - // Don't transform responses - transformResponse: (data) => data, - }) const status = options.deployment.status + const graphNodeEndpointClient = options.deployment.graphNode.getQueryClient( + options.deployment.id.ipfsHash, + ) + this.deployment = { id: options.deployment.id, - client, status, + endpointClient: graphNodeEndpointClient, } } } @@ -110,21 +99,21 @@ export class NetworkSubgraph { | { id: SubgraphDeploymentID status: Eventual - graphNodeQueryEndpoint: string + graphNode: GraphNode } | undefined if (deployment) { const status = await monitorDeployment({ logger, - indexingStatusResolver: deployment.indexingStatusResolver, + graphNode: deployment.graphNode, deployment: deployment.deployment, }) deploymentInfo = { id: deployment.deployment, status, - graphNodeQueryEndpoint: deployment.graphNodeQueryEndpoint, + graphNode: deployment.graphNode, } } @@ -152,7 +141,7 @@ export class NetworkSubgraph { if (healthy) { this.logger.trace('Use own deployment for network subgraph query') - return this.deployment.client + return this.deployment.endpointClient } else if (this.endpointClient) { this.logger.trace('Use provided endpoint for network subgraph query') return this.endpointClient @@ -194,11 +183,11 @@ export class NetworkSubgraph { const monitorDeployment = async ({ logger, - indexingStatusResolver, + graphNode, deployment, }: { logger: Logger - indexingStatusResolver: IndexingStatusResolver + graphNode: GraphNode deployment: SubgraphDeploymentID }): Promise> => { const initialStatus: DeploymentStatus = { @@ -213,7 +202,7 @@ const monitorDeployment = async ({ try { logger.trace(`Checking the network subgraph deployment status`) - const indexingStatuses = await indexingStatusResolver.indexingStatus([deployment]) + const indexingStatuses = await graphNode.indexingStatus([deployment]) const indexingStatus = indexingStatuses.pop() if (!indexingStatus) { throw `No indexing status found` diff --git a/packages/indexer-common/src/network.ts b/packages/indexer-common/src/network.ts index 45cb343e5..a9fac8d0e 100644 --- a/packages/indexer-common/src/network.ts +++ b/packages/indexer-common/src/network.ts @@ -1,14 +1,10 @@ import { - Address, - Eventual, - formatGRT, Logger, Metrics, - mutable, NetworkContracts, SubgraphDeploymentID, - timer, - toAddress, + connectContracts, + Eventual, } from '@graphprotocol/common-ts' import { Allocation, @@ -17,152 +13,224 @@ import { indexerError, IndexerErrorCode, NetworkSubgraph, - parseGraphQLAllocation, parseGraphQLEpochs, TransactionManager, -} from '@graphprotocol/indexer-common' -import { BigNumber, providers, utils, Wallet } from 'ethers' + specification as spec, + GraphNode, + EpochSubgraph, + NetworkMonitor, + AllocationReceiptCollector, +} from '.' +import { BigNumber, providers, Wallet } from 'ethers' import { strict as assert } from 'assert' import gql from 'graphql-tag' import geohash from 'ngeohash' import pFilter from 'p-filter' import pRetry from 'p-retry' - -interface IndexerConfig { - url: string - geoCoordinates: [string, string] - restakeRewards: boolean - rebateClaimThreshold: BigNumber - rebateClaimBatchThreshold: BigNumber - rebateClaimMaxBatchSize: number - poiDisputeMonitoring: boolean - poiDisputableEpochs: number -} +import { resolveChainId } from './indexer-management' +import { monitorEthBalance } from './utils' +import { QueryFeeModels } from './query-fees' export class Network { logger: Logger networkSubgraph: NetworkSubgraph contracts: NetworkContracts - indexerAddress: Address - ethereum: providers.StaticJsonRpcProvider + wallet: Wallet + networkProvider: providers.StaticJsonRpcProvider transactionManager: TransactionManager - indexerConfigs: IndexerConfig - indexerUrl: string - indexerGeoCoordinates: [string, string] + networkMonitor: NetworkMonitor + receiptCollector: AllocationReceiptCollector + specification: spec.NetworkSpecification + paused: Eventual + isOperator: Eventual private constructor( logger: Logger, - wallet: Wallet, - indexerAddress: Address, - indexerUrl: string, - geoCoordinates: [string, string], contracts: NetworkContracts, + wallet: Wallet, networkSubgraph: NetworkSubgraph, - ethereum: providers.StaticJsonRpcProvider, + networkProvider: providers.StaticJsonRpcProvider, + transactionManager: TransactionManager, + networkMonitor: NetworkMonitor, + receiptCollector: AllocationReceiptCollector, + specification: spec.NetworkSpecification, paused: Eventual, isOperator: Eventual, - restakeRewards: boolean, - rebateClaimThreshold: BigNumber, - rebateClaimBatchThreshold: BigNumber, - rebateClaimMaxBatchSize: number, - poiDisputeMonitoring: boolean, - poiDisputableEpochs: number, - gasIncreaseTimeout: number, - gasIncreaseFactor: number, - baseFeePerGasMax: number, - maxTransactionAttempts: number, ) { this.logger = logger - this.indexerAddress = indexerAddress - this.indexerUrl = indexerUrl - this.indexerGeoCoordinates = geoCoordinates this.contracts = contracts + this.wallet = wallet this.networkSubgraph = networkSubgraph - this.ethereum = ethereum - this.indexerConfigs = { - url: indexerUrl, - geoCoordinates: geoCoordinates, - restakeRewards: restakeRewards, - rebateClaimThreshold: rebateClaimThreshold, - rebateClaimBatchThreshold: rebateClaimBatchThreshold, - rebateClaimMaxBatchSize: rebateClaimMaxBatchSize, - poiDisputeMonitoring: poiDisputeMonitoring, - poiDisputableEpochs: poiDisputableEpochs, - } - - this.transactionManager = new TransactionManager( - ethereum, - wallet, - paused, - isOperator, - gasIncreaseTimeout, - gasIncreaseFactor, - baseFeePerGasMax, - maxTransactionAttempts, - ) + this.networkProvider = networkProvider + this.transactionManager = transactionManager + this.networkMonitor = networkMonitor + this.receiptCollector = receiptCollector + this.specification = specification + this.paused = paused + this.isOperator = isOperator } static async create( parentLogger: Logger, - ethereum: providers.StaticJsonRpcProvider, - contracts: NetworkContracts, - wallet: Wallet, - indexerAddress: Address, - indexerUrl: string, - geoCoordinates: [string, string], - networkSubgraph: NetworkSubgraph, - restakeRewards: boolean, - rebateClaimThreshold: BigNumber, - rebateClaimBatchThreshold: BigNumber, - rebateClaimMaxBatchSize: number, - poiDisputeMonitoring: boolean, - poiDisputableEpochs: number, - gasIncreaseTimeout: number, - gasIncreaseFactor: number, - baseFeePerGasMax: number, - maxTransactionAttempts: number, + specification: spec.NetworkSpecification, + queryFeeModels: QueryFeeModels, + graphNode: GraphNode, + metrics: Metrics, ): Promise { - const logger = parentLogger.child({ + // Incomplete logger for initial operations, will be replaced as new labels emerge. + let logger = parentLogger.child({ component: 'Network', - indexer: indexerAddress.toString(), + indexer: specification.indexerOptions.address, + protocolNetwork: specification.networkIdentifier, + }) + + // * ----------------------------------------------------------------------- + // * Network Subgraph + // * ----------------------------------------------------------------------- + const networkSubgraphDeploymentId = specification.subgraphs.networkSubgraph.deployment + ? new SubgraphDeploymentID(specification.subgraphs.networkSubgraph.deployment) + : undefined + + const networkSubgraph = await NetworkSubgraph.create({ + logger, + endpoint: specification.subgraphs.networkSubgraph.url, + deployment: + networkSubgraphDeploymentId !== undefined + ? { + graphNode, + deployment: networkSubgraphDeploymentId, + } + : undefined, + }) + + // * ----------------------------------------------------------------------- + // * Network Provider + // * ----------------------------------------------------------------------- + const networkProvider = await Network.provider( + logger, + metrics, + specification.networkIdentifier, + specification.networkProvider.url, + specification.networkProvider.pollingInterval, + ) + + // * ----------------------------------------------------------------------- + // * Contracts + // * ----------------------------------------------------------------------- + const wallet = await connectWallet( + networkProvider, + specification.networkIdentifier, + specification.indexerOptions.mnemonic, + logger, + ) + + // Include wallet address in this logger + logger = logger.child({ operator: wallet.address, }) - const paused = await monitorNetworkPauses(logger, contracts, networkSubgraph) - const isOperator = await monitorIsOperator(logger, contracts, indexerAddress, wallet) + // Monitor ETH balance of the operator and write the latest value to a metric + await monitorEthBalance(logger, wallet, metrics, specification.networkIdentifier) - return new Network( + const contracts = await connectToProtocolContracts( + wallet, + specification.networkIdentifier, + logger, + ) + + // * ----------------------------------------------------------------------- + // * Epoch Subgraph + // * ----------------------------------------------------------------------- + const epochSubgraph = await EpochSubgraph.create( + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- + * Accept the non-null `url` property of the Epoch Subgraph, as it has + * already been validated during parsing. Once indexing is supported, + * initialize it in the same way as the NetworkSubgraph + */ + specification.subgraphs.epochSubgraph.url!, + ) + + // * ----------------------------------------------------------------------- + // * Network Monitor + // * ----------------------------------------------------------------------- + const networkMonitor = new NetworkMonitor( + specification.networkIdentifier, + contracts, + specification.indexerOptions, + logger.child({ + component: 'NetworkMonitor', + protocolNetwork: specification.networkIdentifier, + }), + graphNode, + networkSubgraph, + networkProvider, + epochSubgraph, + ) + + // * ----------------------------------------------------------------------- + // * Transaction Manager + // * ----------------------------------------------------------------------- + const paused = await networkMonitor.monitorNetworkPauses( logger, + contracts, + networkSubgraph, + ) + + const isOperator = await networkMonitor.monitorIsOperator( + logger, + contracts, + specification.indexerOptions.address, wallet, - indexerAddress, - indexerUrl, - geoCoordinates, + ) + + const transactionManager = new TransactionManager( + networkProvider, + wallet, + paused, + isOperator, + specification.transactionMonitoring, + ) + + // -------------------------------------------------------------------------------- + // * Allocation Receipt Collector + // -------------------------------------------------------------------------------- + const receiptCollector = new AllocationReceiptCollector({ + logger, + metrics, + transactionManager: transactionManager, + models: queryFeeModels, + allocationExchange: contracts.allocationExchange, + networkSpecification: specification, + }) + + // -------------------------------------------------------------------------------- + // * Network + // -------------------------------------------------------------------------------- + return new Network( + logger, contracts, + wallet, networkSubgraph, - ethereum, + networkProvider, + transactionManager, + networkMonitor, + receiptCollector, + specification, paused, isOperator, - restakeRewards, - rebateClaimThreshold, - rebateClaimBatchThreshold, - rebateClaimMaxBatchSize, - poiDisputeMonitoring, - poiDisputableEpochs, - gasIncreaseTimeout, - gasIncreaseFactor, - baseFeePerGasMax, - maxTransactionAttempts, ) } static async provider( logger: Logger, metrics: Metrics, + networkIdentifier: string, networkURL: string, pollingInterval: number, ): Promise { logger.info(`Connect to Network chain`, { provider: networkURL, + pollingInterval, }) let providerUrl @@ -176,9 +244,11 @@ export class Network { process.exit(1) } + const provider_requests_metric_name = `eth_provider_requests_${networkIdentifier}` + logger.info('Metric name: ', { provider_requests_metric_name }) const ethProviderMetrics = { requests: new metrics.client.Counter({ - name: 'eth_provider_requests', + name: provider_requests_metric_name, help: 'Ethereum provider requests', registers: [metrics.registry], labelNames: ['method'], @@ -239,229 +309,6 @@ export class Network { return networkProvider } - // TODO: Move to NetworkMonitor - async claimableAllocations(disputableEpoch: number): Promise { - try { - const result = await this.networkSubgraph.query( - gql` - query allocations( - $indexer: String! - $disputableEpoch: Int! - $minimumQueryFeesCollected: BigInt! - ) { - allocations( - where: { - indexer: $indexer - closedAtEpoch_lte: $disputableEpoch - queryFeesCollected_gte: $minimumQueryFeesCollected - status: Closed - } - first: 1000 - ) { - id - indexer { - id - } - queryFeesCollected - allocatedTokens - createdAtEpoch - closedAtEpoch - createdAtBlockHash - closedAtBlockHash - subgraphDeployment { - id - stakedTokens - signalledTokens - queryFeesAmount - } - } - } - `, - { - indexer: this.indexerAddress.toLocaleLowerCase(), - disputableEpoch, - minimumQueryFeesCollected: this.indexerConfigs.rebateClaimThreshold.toString(), - }, - ) - - if (result.error) { - throw result.error - } - - const totalFees: BigNumber = result.data.allocations.reduce( - (total: BigNumber, rawAlloc: { queryFeesCollected: string }) => { - return total.add(BigNumber.from(rawAlloc.queryFeesCollected)) - }, - BigNumber.from(0), - ) - - const parsedAllocs: Allocation[] = - result.data.allocations.map(parseGraphQLAllocation) - - // If the total fees claimable do not meet the minimum required for batching, return an empty array - if ( - parsedAllocs.length > 0 && - totalFees.lt(this.indexerConfigs.rebateClaimBatchThreshold) - ) { - this.logger.info( - `Allocation rebate batch value does not meet minimum for claiming`, - { - batchValueGRT: formatGRT(totalFees), - rebateClaimBatchThreshold: formatGRT( - this.indexerConfigs.rebateClaimBatchThreshold, - ), - rebateClaimMaxBatchSize: this.indexerConfigs.rebateClaimMaxBatchSize, - batchSize: parsedAllocs.length, - allocations: parsedAllocs.map((allocation) => { - return { - allocation: allocation.id, - deployment: allocation.subgraphDeployment.id.display, - createdAtEpoch: allocation.createdAtEpoch, - closedAtEpoch: allocation.closedAtEpoch, - createdAtBlockHash: allocation.createdAtBlockHash, - } - }), - }, - ) - return [] - } - // Otherwise return the allos for claiming since the batch meets the minimum - return parsedAllocs - } catch (error) { - const err = indexerError(IndexerErrorCode.IE011, error) - this.logger.error(INDEXER_ERROR_MESSAGES[IndexerErrorCode.IE011], { - err, - }) - throw err - } - } - - // TODO: Move to NetworkMonitor - async disputableAllocations( - currentEpoch: number, - deployments: SubgraphDeploymentID[], - minimumAllocation: number, - ): Promise { - const logger = this.logger.child({ component: 'POI Monitor' }) - if (!this.indexerConfigs.poiDisputeMonitoring) { - logger.trace('POI monitoring disabled, skipping') - return Promise.resolve([]) - } - - logger.debug( - 'Query network for any newly closed allocations for deployment this indexer is syncing (available reference POIs)', - ) - - let dataRemaining = true - let allocations: Allocation[] = [] - - try { - const zeroPOI = utils.hexlify(Array(32).fill(0)) - const disputableEpoch = currentEpoch - this.indexerConfigs.poiDisputableEpochs - let lastCreatedAt = 0 - while (dataRemaining) { - const result = await this.networkSubgraph.query( - gql` - query allocations( - $deployments: [String!]! - $minimumAllocation: Int! - $disputableEpoch: Int! - $zeroPOI: String! - $createdAt: Int! - ) { - allocations( - where: { - createdAt_gt: $createdAt - subgraphDeployment_in: $deployments - allocatedTokens_gt: $minimumAllocation - closedAtEpoch_gte: $disputableEpoch - status: Closed - poi_not: $zeroPOI - } - first: 1000 - orderBy: createdAt - orderDirection: asc - ) { - id - createdAt - indexer { - id - } - poi - allocatedTokens - createdAtEpoch - closedAtEpoch - closedAtBlockHash - subgraphDeployment { - id - stakedTokens - signalledTokens - queryFeesAmount - } - } - } - `, - { - deployments: deployments.map((subgraph) => subgraph.bytes32), - minimumAllocation, - disputableEpoch, - createdAt: lastCreatedAt, - zeroPOI, - }, - ) - - if (result.error) { - throw result.error - } - if (result.data.allocations.length == 0) { - dataRemaining = false - } else { - lastCreatedAt = result.data.allocations.slice(-1)[0].createdAt - const parsedResult: Allocation[] = - result.data.allocations.map(parseGraphQLAllocation) - allocations = allocations.concat(parsedResult) - } - } - - // Get the unique set of dispute epochs to reduce the work fetching epoch start block hashes in the next step - let disputableEpochs = await this.epochs([ - ...allocations.reduce((epochNumbers: Set, allocation: Allocation) => { - epochNumbers.add(allocation.closedAtEpoch) - epochNumbers.add(allocation.closedAtEpoch - 1) - return epochNumbers - }, new Set()), - ]) - - disputableEpochs = await Promise.all( - disputableEpochs.map(async (epoch: Epoch): Promise => { - // TODO: May need to retry or skip epochs where obtaining start block fails - epoch.startBlockHash = (await this.ethereum.getBlock(epoch.startBlock))?.hash - return epoch - }), - ) - - return await Promise.all( - allocations.map(async (allocation) => { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - allocation.closedAtEpochStartBlockHash = disputableEpochs.find( - (epoch) => epoch.id == allocation.closedAtEpoch, - )!.startBlockHash - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - allocation.previousEpochStartBlockHash = disputableEpochs.find( - (epoch) => epoch.id == allocation.closedAtEpoch - 1, - )!.startBlockHash - return allocation - }), - ) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE037, error) - logger.error(INDEXER_ERROR_MESSAGES.IE037, { - err, - }) - throw err - } - } - async epochs(epochNumbers: number[]): Promise { try { const result = await this.networkSubgraph.query( @@ -501,17 +348,24 @@ export class Network { // Start of SEND functions async register(): Promise { const geoHash = geohash.encode( - +this.indexerConfigs.geoCoordinates[0], - +this.indexerConfigs.geoCoordinates[1], + +this.specification.indexerOptions.geoCoordinates[0], + +this.specification.indexerOptions.geoCoordinates[1], ) const logger = this.logger.child({ - address: this.indexerAddress, - url: this.indexerConfigs.url, - geoCoordinates: this.indexerConfigs.geoCoordinates, + address: this.specification.indexerOptions.address, + url: this.specification.indexerOptions.url, + geoCoordinates: this.specification.indexerOptions.geoCoordinates, geoHash, }) + if (!this.specification.indexerOptions.register) { + logger.debug( + "Indexer was not registered because it was explicitly disabled in this Network's configuration.", + ) + return + } + await pRetry( async () => { try { @@ -520,13 +374,16 @@ export class Network { // Register the indexer (only if it hasn't been registered yet or // if its URL is different from what is registered on chain) const isRegistered = await this.contracts.serviceRegistry.isRegistered( - this.indexerAddress, + this.specification.indexerOptions.address, ) if (isRegistered) { const service = await this.contracts.serviceRegistry.services( - this.indexerAddress, + this.specification.indexerOptions.address, ) - if (service.url === this.indexerConfigs.url && service.geohash === geoHash) { + if ( + service.url === this.specification.indexerOptions.url && + service.geohash === geoHash + ) { if (await this.transactionManager.isOperator.value()) { logger.info(`Indexer already registered, operator status already granted`) return @@ -538,14 +395,14 @@ export class Network { const receipt = await this.transactionManager.executeTransaction( () => this.contracts.serviceRegistry.estimateGas.registerFor( - this.indexerAddress, - this.indexerConfigs.url, + this.specification.indexerOptions.address, + this.specification.indexerOptions.url, geoHash, ), (gasLimit) => this.contracts.serviceRegistry.registerFor( - this.indexerAddress, - this.indexerConfigs.url, + this.specification.indexerOptions.address, + this.specification.indexerOptions.url, geoHash, { gasLimit, @@ -577,6 +434,26 @@ export class Network { ) } + async claimRebateRewards(allocations: Allocation[]): Promise { + if (allocations.length > 0) { + this.logger.info(`Claim rebate rewards`, { + claimable: allocations.map((allocation) => ({ + id: allocation.id, + deployment: allocation.subgraphDeployment.id.display, + createdAtEpoch: allocation.createdAtEpoch, + amount: allocation.queryFeeRebates, + })), + }) + try { + await this.claimMany(allocations) + } catch (err) { + this.logger.warn(`Failed to claim rebate rewards`, { err }) + } + } else { + this.logger.info(`No allocations to claim rebate rewards for`) + } + } + async claimMany(allocations: Allocation[]): Promise { const logger = this.logger.child({ action: 'ClaimMany', @@ -594,7 +471,7 @@ export class Network { createdAtBlockHash: allocation.createdAtBlockHash, } }), - restakeRewards: this.indexerConfigs.restakeRewards, + restakeRewards: this.specification.indexerOptions.restakeRewards, }, ) @@ -626,7 +503,7 @@ export class Network { // We get at least 21k gas savings per inclusion of a claim in a batch // A reasonable upper bound for this value is 200 assuming the system has the memory // requirements to construct the transaction - const maxClaimsPerBatch = this.indexerConfigs.rebateClaimMaxBatchSize + const maxClaimsPerBatch = this.specification.indexerOptions.rebateClaimMaxBatchSize // When we construct the batch, we sort desc by query fees collected // in order to maximise the value of the truncated batch @@ -657,12 +534,12 @@ export class Network { () => this.contracts.staking.estimateGas.claimMany( allocationIds, - this.indexerConfigs.restakeRewards, + this.specification.indexerOptions.restakeRewards, ), (gasLimit) => this.contracts.staking.claimMany( allocationIds, - this.indexerConfigs.restakeRewards, + this.specification.indexerOptions.restakeRewards, { gasLimit, }, @@ -685,90 +562,54 @@ export class Network { } } -// TODO: Move to NetworkMonitor -async function monitorNetworkPauses( +async function connectWallet( + networkProvider: providers.Provider, + networkIdentifier: string, + mnemonic: string, logger: Logger, - contracts: NetworkContracts, - networkSubgraph: NetworkSubgraph, -): Promise> { - let networkPaused: boolean - try { - networkPaused = await contracts.controller.paused() - } catch (error) { - logger.error(`Failed to check for network pause with contracts controller`, { - suberror: IndexerErrorCode.IE007, - cause: error.message, - }) - throw indexerError(IndexerErrorCode.IE007, `Failed to check for network pause`) - } - return timer(60_000) - .reduce(async (currentlyPaused) => { - try { - const result = await networkSubgraph.query( - gql` - { - graphNetworks { - isPaused - } - } - `, - ) - - if (result.error) { - throw result.error - } - - if (!result.data || result.data.length === 0) { - throw new Error(`No data returned by network subgraph`) - } - - return result.data.graphNetworks[0].isPaused - } catch (err) { - logger.warn(`Failed to check for network pause, assuming it has not changed`, { - err: indexerError(IndexerErrorCode.IE007, err), - paused: currentlyPaused, - }) - return currentlyPaused - } - }, networkPaused) - .map((paused) => { - logger.info(paused ? `Network paused` : `Network active`) - return paused - }) +): Promise { + logger.info(`Connect wallet`, { + networkIdentifier: networkIdentifier, + }) + let wallet = Wallet.fromMnemonic(mnemonic) + wallet = wallet.connect(networkProvider) + logger.info(`Connected wallet`) + return wallet } -// TODO: Move to NetworkMonitor -async function monitorIsOperator( - logger: Logger, - contracts: NetworkContracts, - indexerAddress: Address, +async function connectToProtocolContracts( wallet: Wallet, -): Promise> { - // If indexer and operator address are identical, operator status is - // implicitly granted => we'll never have to check again - if (indexerAddress === toAddress(wallet.address)) { - logger.info(`Indexer and operator are identical, operator status granted`) - return mutable(true) + networkIdentifier: string, + logger: Logger, +): Promise { + const numericNetworkId = parseInt(networkIdentifier.split(':')[1]) + + // Confidence check: Should be unreachable since NetworkSpecification was validated before + if (resolveChainId(numericNetworkId) !== networkIdentifier) { + throw new Error(`Invalid network identifier: ${networkIdentifier}`) } - return timer(60_000) - .reduce(async (isOperator) => { - try { - return await contracts.staking.isOperator(wallet.address, indexerAddress) - } catch (err) { - logger.warn( - `Failed to check operator status for indexer, assuming it has not changed`, - { err: indexerError(IndexerErrorCode.IE008, err), isOperator }, - ) - return isOperator - } - }, await contracts.staking.isOperator(wallet.address, indexerAddress)) - .map((isOperator) => { - logger.info( - isOperator - ? `Have operator status for indexer` - : `No operator status for indexer`, - ) - return isOperator - }) + logger.info(`Connect to contracts`, { + network: networkIdentifier, + }) + + let contracts + try { + contracts = await connectContracts(wallet, numericNetworkId) + } catch (err) { + throw new Error( + `Failed to connect to contracts, please ensure you are using the intended protocol network`, + ) + } + logger.info(`Successfully connected to contracts`, { + curation: contracts.curation.address, + disputeManager: contracts.disputeManager.address, + epochManager: contracts.epochManager.address, + gns: contracts.gns.address, + rewardsManager: contracts.rewardsManager.address, + serviceRegistry: contracts.serviceRegistry.address, + staking: contracts.staking.address, + token: contracts.token.address, + }) + return contracts } diff --git a/packages/indexer-common/src/query-fees/allocation-utils.ts b/packages/indexer-common/src/query-fees/allocation-utils.ts index d43ce5841..659ae696e 100644 --- a/packages/indexer-common/src/query-fees/allocation-utils.ts +++ b/packages/indexer-common/src/query-fees/allocation-utils.ts @@ -6,6 +6,7 @@ export const ensureAllocationSummary = async ( models: QueryFeeModels, allocation: Address, transaction: Transaction, + protocolNetwork: string, ): Promise<[AllocationSummary, boolean]> => { const [summary, isNew] = await models.allocationSummaries.findOrBuild({ where: { allocation }, @@ -18,6 +19,7 @@ export const ensureAllocationSummary = async ( openTransfers: 0, collectedFees: '0', withdrawnFees: '0', + protocolNetwork, }, transaction, }) diff --git a/packages/indexer-common/src/query-fees/models.ts b/packages/indexer-common/src/query-fees/models.ts index 01b1458db..37bd4b827 100644 --- a/packages/indexer-common/src/query-fees/models.ts +++ b/packages/indexer-common/src/query-fees/models.ts @@ -1,11 +1,13 @@ import { DataTypes, Sequelize, Model, Association } from 'sequelize' import { Address } from '@graphprotocol/common-ts' +import { caip2IdRegex } from '../parsers' export interface AllocationReceiptAttributes { id: string allocation: Address fees: string signature: string + protocolNetwork: string } export class AllocationReceipt @@ -16,6 +18,7 @@ export class AllocationReceipt public allocation!: Address public fees!: string public signature!: string + public protocolNetwork!: string public readonly createdAt!: Date public readonly updatedAt!: Date @@ -25,6 +28,7 @@ export interface VoucherAttributes { allocation: Address amount: string signature: string + protocolNetwork: string } export class Voucher extends Model implements VoucherAttributes { @@ -34,6 +38,7 @@ export class Voucher extends Model implements VoucherAttribut public readonly createdAt!: Date public readonly updatedAt!: Date + public protocolNetwork!: string public readonly allocationSummary?: AllocationSummary @@ -47,6 +52,7 @@ export interface TransferReceiptAttributes { signer: Address fees: string signature: string + protocolNetwork: string } export class TransferReceipt @@ -57,6 +63,7 @@ export class TransferReceipt public signer!: Address public fees!: string public signature!: string + public protocolNetwork!: string public readonly createdAt!: Date public readonly updatedAt!: Date @@ -81,6 +88,7 @@ export interface TransferAttributes { signer: Address allocationClosedAt: Date | null status: TransferStatus + protocolNetwork: string } export class Transfer extends Model implements TransferAttributes { @@ -89,6 +97,7 @@ export class Transfer extends Model implements TransferAttri public signer!: Address public allocationClosedAt!: Date | null public status!: TransferStatus + public protocolNetwork!: string public readonly createdAt!: Date public readonly updatedAt!: Date @@ -109,6 +118,7 @@ export interface AllocationSummaryAttributes { openTransfers: number collectedFees: string withdrawnFees: string + protocolNetwork: string } export class AllocationSummary @@ -123,6 +133,7 @@ export class AllocationSummary public openTransfers!: number public collectedFees!: string public withdrawnFees!: string + public protocolNetwork!: string public readonly createdAt!: Date public readonly updatedAt!: Date @@ -174,6 +185,14 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { min: 0.0, }, }, + protocolNetwork: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + validate: { + is: caip2IdRegex, + }, + }, }, { sequelize, tableName: 'allocation_receipts' }, ) @@ -196,6 +215,14 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { type: DataTypes.STRING, allowNull: false, }, + protocolNetwork: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + validate: { + is: caip2IdRegex, + }, + }, }, { sequelize, tableName: 'vouchers' }, ) @@ -223,6 +250,14 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { min: 0.0, }, }, + protocolNetwork: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + validate: { + is: caip2IdRegex, + }, + }, }, { sequelize, tableName: 'transfer_receipts' }, ) @@ -256,6 +291,14 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { ), allowNull: false, }, + protocolNetwork: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + validate: { + is: caip2IdRegex, + }, + }, }, { sequelize, tableName: 'transfers' }, ) @@ -295,6 +338,14 @@ export function defineQueryFeeModels(sequelize: Sequelize): QueryFeeModels { type: DataTypes.DECIMAL, allowNull: false, }, + protocolNetwork: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + validate: { + is: caip2IdRegex, + }, + }, }, { sequelize, tableName: 'allocation_summaries' }, ) diff --git a/packages/indexer-common/src/rules.ts b/packages/indexer-common/src/rules.ts index b1a37d495..e03bfd40e 100644 --- a/packages/indexer-common/src/rules.ts +++ b/packages/indexer-common/src/rules.ts @@ -1,6 +1,7 @@ import { IndexingDecisionBasis, IndexingRuleAttributes } from './indexer-management' import { nullPassThrough, parseBoolean } from './utils' import { parseGRT } from '@graphprotocol/common-ts' +import { validateNetworkIdentifier } from './parsers' export const parseDecisionBasis = (s: string): IndexingDecisionBasis => { if (!['always', 'never', 'rules', 'offchain'].includes(s)) { @@ -34,6 +35,7 @@ const INDEXING_RULE_READABLE_TO_MODEL_PARSERS: Record< custom: nullPassThrough(JSON.parse), requireSupported: (x) => parseBoolean(x), safety: (x) => parseBoolean(x), + protocolNetwork: (x: string) => validateNetworkIdentifier(x), } export const parseIndexingRule = ( diff --git a/packages/indexer-common/src/subgraphs.ts b/packages/indexer-common/src/subgraphs.ts index 3cf2524d4..d9af8bdc4 100644 --- a/packages/indexer-common/src/subgraphs.ts +++ b/packages/indexer-common/src/subgraphs.ts @@ -141,7 +141,7 @@ export async function processIdentifier( // SUBGRAPH, // } -enum ActivationCriteria { +export enum ActivationCriteria { NA = 'na', NONE = 'none', ALWAYS = 'always', @@ -152,6 +152,7 @@ enum ActivationCriteria { NEVER = 'never', OFFCHAIN = 'offchain', INVALID_ALLOCATION_AMOUNT = 'invalid_allocation_amount', + L2_TRANSFER_SUPPORT = 'l2_transfer_support', } interface RuleMatch { @@ -163,12 +164,14 @@ export class AllocationDecision { declare deployment: SubgraphDeploymentID declare toAllocate: boolean declare ruleMatch: RuleMatch + declare protocolNetwork: string constructor( deployment: SubgraphDeploymentID, matchingRule: IndexingRuleAttributes | undefined, toAllocate: boolean, ruleActivator: ActivationCriteria, + protocolNetwork: string, ) { this.deployment = deployment this.toAllocate = toAllocate @@ -176,6 +179,7 @@ export class AllocationDecision { rule: matchingRule, activationCriteria: ruleActivator, } + this.protocolNetwork = protocolNetwork } public reasonString(): string { return `${this.ruleMatch.rule?.identifierType ?? 'none'}:${ @@ -223,6 +227,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, false, ActivationCriteria.INVALID_ALLOCATION_AMOUNT, + deployment.protocolNetwork, ) } @@ -233,6 +238,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, false, ActivationCriteria.UNSUPPORTED, + deployment.protocolNetwork, ) } @@ -243,13 +249,16 @@ export function isDeploymentWorthAllocatingTowards( undefined, false, ActivationCriteria.NA, + deployment.protocolNetwork, ) + case IndexingDecisionBasis.ALWAYS: return new AllocationDecision( deployment.id, deploymentRule, true, ActivationCriteria.ALWAYS, + deployment.protocolNetwork, ) case IndexingDecisionBasis.NEVER: return new AllocationDecision( @@ -257,6 +266,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, false, ActivationCriteria.NEVER, + deployment.protocolNetwork, ) case IndexingDecisionBasis.OFFCHAIN: return new AllocationDecision( @@ -264,6 +274,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, false, ActivationCriteria.OFFCHAIN, + deployment.protocolNetwork, ) case IndexingDecisionBasis.RULES: { const stakedTokens = BigNumber.from(deployment.stakedTokens) @@ -276,6 +287,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, true, ActivationCriteria.MIN_STAKE, + deployment.protocolNetwork, ) } else if ( deploymentRule.minSignal && @@ -286,6 +298,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, true, ActivationCriteria.SIGNAL_THRESHOLD, + deployment.protocolNetwork, ) } else if ( deploymentRule.minAverageQueryFees && @@ -296,6 +309,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, true, ActivationCriteria.MIN_AVG_QUERY_FEES, + deployment.protocolNetwork, ) } else { return new AllocationDecision( @@ -303,6 +317,7 @@ export function isDeploymentWorthAllocatingTowards( deploymentRule, false, ActivationCriteria.NONE, + deployment.protocolNetwork, ) } } diff --git a/packages/indexer-common/src/transactions.ts b/packages/indexer-common/src/transactions.ts index 2e27e45c1..decc8fbfb 100644 --- a/packages/indexer-common/src/transactions.ts +++ b/packages/indexer-common/src/transactions.ts @@ -17,6 +17,7 @@ import { toAddress, } from '@graphprotocol/common-ts' import delay from 'delay' +import { TransactionMonitoring } from './network-specification' import { IndexerError, indexerError, IndexerErrorCode } from './errors' import { TransactionConfig, TransactionType } from './types' import { NetworkSubgraph } from './network-subgraph' @@ -27,29 +28,28 @@ export class TransactionManager { wallet: Wallet paused: Eventual isOperator: Eventual - gasIncreaseTimeout: number - gasIncreaseFactor: BigNumber - baseFeePerGasMax: number - maxTransactionAttempts: number + specification: TransactionMonitoring + adjustedGasIncreaseFactor: BigNumber + adjustedBaseFeePerGasMax: number constructor( ethereum: providers.BaseProvider, wallet: Wallet, paused: Eventual, isOperator: Eventual, - gasIncreaseTimeout: number, - gasIncreaseFactor: number, - baseFeePerGasMax: number, - maxTransactionAttempts: number, + specification: TransactionMonitoring, ) { this.ethereum = ethereum this.wallet = wallet this.paused = paused this.isOperator = isOperator - this.gasIncreaseTimeout = gasIncreaseTimeout - this.gasIncreaseFactor = utils.parseUnits(gasIncreaseFactor.toString(), 3) - this.baseFeePerGasMax = baseFeePerGasMax - this.maxTransactionAttempts = maxTransactionAttempts + this.specification = specification + this.adjustedGasIncreaseFactor = utils.parseUnits( + specification.gasIncreaseFactor.toString(), + 3, + ) + this.adjustedBaseFeePerGasMax = + specification.baseFeePerGasMax || specification.gasPriceMax } async executeTransaction( @@ -80,7 +80,7 @@ export class TransactionManager { let txConfig: TransactionConfig = { attempt: 1, type: await this.transactionType(feeData), - gasBump: this.gasIncreaseFactor, + gasBump: this.adjustedGasIncreaseFactor, nonce: tx.nonce, maxFeePerGas: tx.maxFeePerGas, maxPriorityFeePerGas: tx.maxPriorityFeePerGas, @@ -92,8 +92,8 @@ export class TransactionManager { while (pending) { if ( - this.maxTransactionAttempts !== 0 && - txConfig.attempt > this.maxTransactionAttempts + this.specification.maxTransactionAttempts !== 0 && + txConfig.attempt > this.specification.maxTransactionAttempts ) { logger.warn('Transaction retry limit reached, giving up', { txConfig, @@ -127,7 +127,7 @@ export class TransactionManager { const receipt = await this.ethereum.waitForTransaction( tx.hash, 3, - this.gasIncreaseTimeout, + this.specification.gasIncreaseTimeout, ) if (receipt.status == 0) { @@ -270,15 +270,15 @@ export class TransactionManager { .maxFeePerGas! // eslint-disable-next-line @typescript-eslint/no-non-null-assertion .sub(feeData.maxPriorityFeePerGas!) .div(2) - if (baseFeePerGas.toNumber() >= this.baseFeePerGasMax) { + if (baseFeePerGas.toNumber() >= this.adjustedBaseFeePerGasMax) { if (attempt == 1) { logger.warning( `Max base fee per gas has been reached, waiting until the base fee falls below to resume transaction execution.`, - { maxBaseFeePerGas: this.baseFeePerGasMax, baseFeePerGas }, + { maxBaseFeePerGas: this.specification.baseFeePerGasMax, baseFeePerGas }, ) } else { logger.info(`Base gas fee per gas estimation still above max threshold`, { - maxBaseFeePerGas: this.baseFeePerGasMax, + maxBaseFeePerGas: this.specification.baseFeePerGasMax, baseFeePerGas, priceEstimateAttempt: attempt, }) @@ -292,18 +292,18 @@ export class TransactionManager { } else if (type === TransactionType.ZERO) { // Legacy transaction type // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if (feeData.gasPrice!.toNumber() >= this.baseFeePerGasMax) { + if (feeData.gasPrice!.toNumber() >= this.adjustedBaseFeePerGasMax) { if (attempt == 1) { logger.warning( `Max gas price has been reached, waiting until gas price estimates fall below to resume transaction execution.`, { - baseFeePerGasMax: this.baseFeePerGasMax, + baseFeePerGasMax: this.specification.baseFeePerGasMax, currentGasPriceEstimate: feeData.gasPrice, }, ) } else { logger.info(`Gas price estimation still above max threshold`, { - baseFeePerGasMax: this.baseFeePerGasMax, + baseFeePerGasMax: this.specification.baseFeePerGasMax, currentGasPriceEstimate: feeData.gasPrice, priceEstimateAttempt: attempt, }) diff --git a/packages/indexer-common/src/types.ts b/packages/indexer-common/src/types.ts index f6694b9df..356396573 100644 --- a/packages/indexer-common/src/types.ts +++ b/packages/indexer-common/src/types.ts @@ -58,7 +58,26 @@ export interface SubgraphDeployment { stakedTokens: BigNumber signalledTokens: BigNumber queryFeesAmount: BigNumber - activeAllocations: number + protocolNetwork: string +} + +// L1 Network Subgraph will always return `null` for the +// `transferredToL2*` set of fields +export interface TransferredSubgraphDeployment { + id: string + idOnL1: string + idOnL2: string + startedTransferToL2L: boolean + startedTransferToL2At: BigNumber + startedTransferToL2AtBlockNumber: BigNumber + startedTransferToL2AtTx: string + transferredToL2: boolean | null + transferredToL2At: BigNumber | null + transferredToL2AtTx: string | null + transferredToL2AtBlockNumber: BigNumber | null + ipfsHash: string + protocolNetwork: string + ready: boolean | null } export enum TransactionType { diff --git a/packages/indexer-common/src/utils.ts b/packages/indexer-common/src/utils.ts index 5efa58b52..d10289167 100644 --- a/packages/indexer-common/src/utils.ts +++ b/packages/indexer-common/src/utils.ts @@ -1,8 +1,11 @@ +import { Wallet, utils } from 'ethers' import { BaseProvider, JsonRpcProvider, getDefaultProvider, } from '@ethersproject/providers' +import { Logger, Metrics, timer } from '@graphprotocol/common-ts' +import { indexerError, IndexerErrorCode } from './errors' export const parseBoolean = ( val: string | boolean | number | undefined | null, @@ -23,3 +26,39 @@ export function getTestProvider(network: string): BaseProvider { return getDefaultProvider(network) } } + +const registerMetrics = (metrics: Metrics, networkIdentifier: string) => ({ + operatorEthBalance: new metrics.client.Gauge({ + name: `indexer_agent_operator_eth_balance_${networkIdentifier}`, + help: 'Amount of ETH in the operator wallet; a low amount could cause transactions to fail', + registers: [metrics.registry], + }), +}) + +export async function monitorEthBalance( + logger: Logger, + wallet: Wallet, + metrics: Metrics, + networkIdentifier: string, +): Promise { + logger = logger.child({ component: 'ETHBalanceMonitor' }) + + logger.info('Monitor operator ETH balance (refreshes every 120s)') + + const balanceMetrics = registerMetrics(metrics, networkIdentifier) + + timer(120_000).pipe(async () => { + try { + const balance = await wallet.getBalance() + const eth = parseFloat(utils.formatEther(balance)) + balanceMetrics.operatorEthBalance.set(eth) + logger.info('Current operator ETH balance', { + balance: eth, + }) + } catch (error) { + logger.warn(`Failed to check latest ETH balance`, { + err: indexerError(IndexerErrorCode.IE059), + }) + } + }) +} From c84176a53292042b91f3631b8af3e693b7244567 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:06:11 -0300 Subject: [PATCH 05/16] common: update Indexer Management Client for protocolNetwork field --- .../src/indexer-management/actions.ts | 188 +++++-- .../src/indexer-management/allocations.ts | 320 ++++------- .../src/indexer-management/client.ts | 198 +++---- .../src/indexer-management/index.ts | 1 - .../src/indexer-management/models/action.ts | 10 + .../models/indexing-rule.ts | 21 +- .../indexer-management/models/poi-dispute.ts | 19 + .../src/indexer-management/monitor.ts | 520 ++++++++++++++---- .../indexer-management/resolvers/actions.ts | 44 +- .../resolvers/allocations.ts | 245 +++++---- .../resolvers/cost-models.ts | 16 +- .../resolvers/indexer-status.ts | 246 +++++---- .../resolvers/indexing-rules.ts | 153 ++++-- .../resolvers/poi-disputes.ts | 83 ++- .../src/indexer-management/resolvers/utils.ts | 26 + .../src/indexer-management/rules.ts | 27 +- .../src/indexer-management/types.ts | 88 ++- 17 files changed, 1440 insertions(+), 765 deletions(-) create mode 100644 packages/indexer-common/src/indexer-management/resolvers/utils.ts diff --git a/packages/indexer-common/src/indexer-management/actions.ts b/packages/indexer-common/src/indexer-management/actions.ts index a6952affc..48e0aa98a 100644 --- a/packages/indexer-common/src/indexer-management/actions.ts +++ b/packages/indexer-common/src/indexer-management/actions.ts @@ -5,6 +5,7 @@ import { ActionParams, ActionStatus, ActionUpdateInput, + AllocationManager, AllocationManagementMode, AllocationResult, AllocationStatus, @@ -12,49 +13,91 @@ import { IndexerErrorCode, IndexerManagementModels, isActionFailure, - NetworkMonitor, + MultiNetworks, + NetworkMapped, + Network, OrderDirection, + GraphNode, } from '@graphprotocol/indexer-common' -import { AllocationManager } from './allocations' + import { Order, Transaction } from 'sequelize' import { Eventual, join, Logger, timer } from '@graphprotocol/common-ts' +import groupBy from 'lodash.groupby' export class ActionManager { - constructor( - public allocationManager: AllocationManager, - public networkMonitor: NetworkMonitor, - private logger: Logger, - private models: IndexerManagementModels, - private allocationManagementMode?: AllocationManagementMode, - private autoAllocationMinBatchSize?: number, - ) {} - - private async batchReady(approvedActions: Action[]): Promise { + declare multiNetworks: MultiNetworks + declare logger: Logger + declare models: IndexerManagementModels + declare allocationManagers: NetworkMapped + + static async create( + multiNetworks: MultiNetworks, + logger: Logger, + models: IndexerManagementModels, + graphNode: GraphNode, + ): Promise { + const actionManager = new ActionManager() + actionManager.multiNetworks = multiNetworks + actionManager.logger = logger.child({ component: 'ActionManager' }) + actionManager.models = models + actionManager.allocationManagers = await multiNetworks.map(async (network) => { + return new AllocationManager( + logger.child({ + component: 'AllocationManager', + protocolNetwork: network.specification.networkIdentifier, + }), + models, + graphNode, + network, + ) + }) + + logger.info('Begin monitoring the queue for approved actions to execute') + await actionManager.monitorQueue() + + return actionManager + } + + private async batchReady( + approvedActions: Action[], + network: Network, + logger: Logger, + ): Promise { + logger.info('Batch ready?', { + approvedActions, + network, + }) + if (approvedActions.length < 1) { + logger.info('Batch not ready: No approved actions found') return false } // In auto management mode the worker will execute the batch if: // 1) Number of approved actions >= minimum batch size // or 2) Oldest affected allocation will expiring after the current epoch - if (this.allocationManagementMode === AllocationManagementMode.AUTO) { + if ( + network.specification.indexerOptions.allocationManagementMode === + AllocationManagementMode.AUTO + ) { const meetsMinBatchSize = - approvedActions.length >= (this.autoAllocationMinBatchSize ?? 1) + approvedActions.length >= + (network.specification.indexerOptions.autoAllocationMinBatchSize ?? 1) const approvedDeploymentIDs = approvedActions.map((action) => action.deploymentID) const affectedAllocations = ( - await this.networkMonitor.allocations(AllocationStatus.ACTIVE) + await network.networkMonitor.allocations(AllocationStatus.ACTIVE) ).filter((a) => approvedDeploymentIDs.includes(a.subgraphDeployment.id.ipfsHash)) let affectedAllocationExpiring = false if (affectedAllocations.length) { - const currentEpoch = await this.networkMonitor.currentEpochNumber() - const maxAllocationEpoch = await this.networkMonitor.maxAllocationEpoch() + const currentEpoch = await network.networkMonitor.currentEpochNumber() + const maxAllocationEpoch = await network.networkMonitor.maxAllocationEpoch() // affectedAllocations are ordered by creation time so use index 0 for oldest allocation to check expiration affectedAllocationExpiring = currentEpoch >= affectedAllocations[0].createdAtEpoch + maxAllocationEpoch } - this.logger.debug( + logger.debug( 'Auto allocation management executes the batch if at least one requirement is met', { currentBatchSize: approvedActions.length, @@ -73,37 +116,86 @@ export class ActionManager { } async monitorQueue(): Promise { + const logger = this.logger.child({ component: 'QueueMonitor' }) const approvedActions: Eventual = timer(30_000).tryMap( - async () => - await ActionManager.fetchActions(this.models, { - status: ActionStatus.APPROVED, - }), + async () => { + logger.trace('Fetching approved actions') + let actions: Action[] = [] + try { + actions = await ActionManager.fetchActions(this.models, { + status: ActionStatus.APPROVED, + }) + logger.trace(`Fetched ${actions.length} approved actions`) + } catch (err) { + logger.warn('Failed to fetch approved actions from queue', { err }) + } + + return actions + }, { // eslint-disable-next-line @typescript-eslint/no-explicit-any onError: (err: any) => - this.logger.warn('Failed to fetch approved actions from queue', { err }), + logger.warn('Failed to fetch approved actions from queue', { err }), }, ) join({ approvedActions }).pipe(async ({ approvedActions }) => { - if (await this.batchReady(approvedActions)) { - this.logger.info('Executing batch of approved actions', { - actions: approvedActions, - note: 'If actions were approved very recently they may be missing from this list but will still be taken', - }) - - try { - const attemptedActions = await this.executeApprovedActions() + logger.debug('Approved actions found, evaluating batch') + const approvedActionsByNetwork: NetworkMapped = groupBy( + approvedActions, + (action: Action) => action.protocolNetwork, + ) - this.logger.trace('Attempted to execute all approved actions', { - actions: attemptedActions, + await this.multiNetworks.mapNetworkMapped( + approvedActionsByNetwork, + async (network: Network, approvedActions: Action[]) => { + const networkLogger = logger.child({ + protocolNetwork: network.specification.networkIdentifier, + indexer: network.specification.indexerOptions.address, + operator: network.transactionManager.wallet.address, }) - } catch (error) { - this.logger.error('Failed to execute batch of approved actions', { - error, - }) - } - } + + if (await this.batchReady(approvedActions, network, networkLogger)) { + const paused = await network.paused.value() + const isOperator = await network.isOperator.value() + logger.debug('Batch ready, preparing to execute', { + paused, + isOperator, + }) + // Do nothing else if the network is paused + if (paused) { + networkLogger.info( + `The network is currently paused, not doing anything until it resumes`, + ) + return + } + + // Do nothing if we're not authorized as an operator for the indexer + if (!isOperator) { + networkLogger.error(`Not authorized as an operator for the indexer`, { + err: indexerError(IndexerErrorCode.IE034), + }) + return + } + + networkLogger.info('Executing batch of approved actions', { + actions: approvedActions, + note: 'If actions were approved very recently they may be missing from this batch', + }) + + try { + const attemptedActions = await this.executeApprovedActions(network) + networkLogger.trace('Attempted to execute all approved actions', { + actions: attemptedActions, + }) + } catch (error) { + networkLogger.error('Failed to execute batch of approved actions', { + error, + }) + } + } + }, + ) }) } @@ -131,7 +223,7 @@ export class ActionManager { return updatedActions } - async executeApprovedActions(): Promise { + async executeApprovedActions(network: Network): Promise { let updatedActions: Action[] = [] // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -146,7 +238,10 @@ export class ActionManager { const actionTypePriority = ['unallocate', 'reallocate', 'allocate'] const approvedActions = ( await this.models.Action.findAll({ - where: { status: ActionStatus.APPROVED }, + where: { + status: ActionStatus.APPROVED, + protocolNetwork: network.specification.networkIdentifier, + }, order: [['priority', 'ASC']], transaction, lock: transaction.LOCK.UPDATE, @@ -155,9 +250,18 @@ export class ActionManager { return actionTypePriority.indexOf(a.type) - actionTypePriority.indexOf(b.type) }) + if (approvedActions.length === 0) { + this.logger.info('No approved actions were found for this network', { + protocolNetwork: network.specification.networkIdentifier, + }) + return [] + } + try { // This will return all results if successful, if failed it will return the failed actions - const results = await this.allocationManager.executeBatch(approvedActions) + const allocationManager = + this.allocationManagers[network.specification.networkIdentifier] + const results = await allocationManager.executeBatch(approvedActions) this.logger.debug('Completed batch action execution', { results, diff --git a/packages/indexer-common/src/indexer-management/allocations.ts b/packages/indexer-common/src/indexer-management/allocations.ts index f46aa29d5..73a7f3032 100644 --- a/packages/indexer-common/src/indexer-management/allocations.ts +++ b/packages/indexer-common/src/indexer-management/allocations.ts @@ -1,7 +1,6 @@ import { formatGRT, Logger, - NetworkContracts, parseGRT, SubgraphDeploymentID, toAddress, @@ -16,6 +15,7 @@ import { CloseAllocationResult, CreateAllocationResult, fetchIndexingRules, + GraphNode, indexerError, IndexerError, IndexerErrorCode, @@ -24,10 +24,9 @@ import { IndexingRuleAttributes, isActionFailure, isDeploymentWorthAllocatingTowards, + Network, ReallocateAllocationResult, - ReceiptCollector, SubgraphIdentifierType, - TransactionManager, uniqueAllocationID, upsertIndexingRule, } from '@graphprotocol/indexer-common' @@ -40,8 +39,7 @@ import { providers, utils, } from 'ethers' -import { NetworkMonitor } from './monitor' -import { SubgraphManager } from './subgraphs' + import { BytesLike } from '@ethersproject/bytes' import pMap from 'p-map' @@ -92,14 +90,10 @@ export type TransactionResult = export class AllocationManager { constructor( - private contracts: NetworkContracts, private logger: Logger, - private indexer: string, private models: IndexerManagementModels, - private networkMonitor: NetworkMonitor, - private receiptCollector: ReceiptCollector, - private subgraphManager: SubgraphManager, - private transactionManager: TransactionManager, + private graphNode: GraphNode, + private network: Network, ) {} async executeBatch(actions: Action[]): Promise { @@ -132,9 +126,10 @@ export class AllocationManager { .filter((tx: PopulatedTransaction) => !!tx.data) .map((tx) => tx.data as string) - return await this.transactionManager.executeTransaction( - async () => this.contracts.staking.estimateGas.multicall(callData), - async (gasLimit) => this.contracts.staking.multicall(callData, { gasLimit }), + return await this.network.transactionManager.executeTransaction( + async () => this.network.contracts.staking.estimateGas.multicall(callData), + async (gasLimit) => + this.network.contracts.staking.multicall(callData, { gasLimit }), this.logger.child({ actions: `${JSON.stringify(validatedActions.map((action) => action.id))}`, function: 'staking.multicall', @@ -166,6 +161,7 @@ export class AllocationManager { error instanceof IndexerError ? error.code : `Failed to confirm transactions: ${error.message}`, + protocolNetwork: action.protocolNetwork, } } }, @@ -199,6 +195,15 @@ export class AllocationManager { receipt: ContractReceipt | 'paused' | 'unauthorized', action: Action, ): Promise { + // Ensure we are handling an action for the same configured network + if (action.protocolNetwork !== this.network.specification.networkIdentifier) { + const errorMessage = `AllocationManager is configured for '${this.network.specification.networkIdentifier}' but got an Action targeting '${action.protocolNetwork}' ` + this.logger.crit(errorMessage, { + action, + }) + throw new Error(errorMessage) + } + switch (action.type) { case ActionType.ALLOCATE: return await this.confirmAllocate( @@ -210,11 +215,19 @@ export class AllocationManager { receipt, ) case ActionType.UNALLOCATE: - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return await this.confirmUnallocate(action.id, action.allocationID!, receipt) + return await this.confirmUnallocate( + action.id, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + action.allocationID!, + receipt, + ) case ActionType.REALLOCATE: - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return await this.confirmReallocate(action.id, action.allocationID!, receipt) + return await this.confirmReallocate( + action.id, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + action.allocationID!, + receipt, + ) } } @@ -272,6 +285,7 @@ export class AllocationManager { error instanceof IndexerError ? error.code : `Failed to prepare tx call data: ${error.message}`, + protocolNetwork: action.protocolNetwork, } } } @@ -287,7 +301,7 @@ export class AllocationManager { amount: amount.toString(), }) - const activeAllocations = await this.networkMonitor.allocations( + const activeAllocations = await this.network.networkMonitor.allocations( AllocationStatus.ACTIVE, ) const allocation = activeAllocations.find( @@ -315,22 +329,19 @@ export class AllocationManager { ) } - const currentEpoch = await this.contracts.epochManager.currentEpoch() + const currentEpoch = await this.network.contracts.epochManager.currentEpoch() // Ensure subgraph is deployed before allocating - await this.subgraphManager.ensure( - logger, - this.models, + await this.graphNode.ensure( `indexer-agent/${deployment.ipfsHash.slice(-10)}`, deployment, - indexNode, ) logger.debug('Obtain a unique Allocation ID') // Obtain a unique allocation ID const { allocationSigner, allocationId } = uniqueAllocationID( - this.transactionManager.wallet.mnemonic.phrase, + this.network.transactionManager.wallet.mnemonic.phrase, currentEpoch.toNumber(), deployment, activeAllocations.map((allocation) => allocation.id), @@ -343,10 +354,10 @@ export class AllocationManager { // enum AllocationState { Null, Active, Closed, Finalized, Claimed } // // in the contracts. - const state = await this.contracts.staking.getAllocationState(allocationId) + const state = await this.network.contracts.staking.getAllocationState(allocationId) if (state !== 0) { logger.debug(`Skipping allocation as it already exists onchain`, { - indexer: this.indexer, + indexer: this.network.specification.indexerOptions.address, allocation: allocationId, state, }) @@ -359,17 +370,21 @@ export class AllocationManager { logger.debug('Generating new allocation ID proof', { newAllocationSigner: allocationSigner, newAllocationID: allocationId, - indexerAddress: this.indexer, + indexerAddress: this.network.specification.indexerOptions.address, }) - const proof = await allocationIdProof(allocationSigner, this.indexer, allocationId) + const proof = await allocationIdProof( + allocationSigner, + this.network.specification.indexerOptions.address, + allocationId, + ) logger.debug('Successfully generated allocation ID proof', { allocationIDProof: proof, }) return { - indexer: this.indexer, + indexer: this.network.specification.indexerOptions.address, subgraphDeploymentID: deployment.bytes32, tokens: amount, allocationID: allocationId, @@ -398,7 +413,7 @@ export class AllocationManager { const createAllocationEventLogs = this.findEvent( 'AllocationCreated', - this.contracts.staking.interface, + this.network.contracts.staking.interface, 'subgraphDeploymentID', subgraphDeployment.bytes32, receipt, @@ -416,7 +431,7 @@ export class AllocationManager { }) // Remember allocation - await this.receiptCollector.rememberAllocations(actionID, [ + await this.network.receiptCollector.rememberAllocations(actionID, [ createAllocationEventLogs.allocationID, ]) @@ -431,6 +446,7 @@ export class AllocationManager { allocationAmount: amount, identifierType: SubgraphIdentifierType.DEPLOYMENT, decisionBasis: IndexingDecisionBasis.ALWAYS, + protocolNetwork: this.network.specification.networkIdentifier, } as Partial await upsertIndexingRule(logger, this.models, indexingRule) @@ -443,6 +459,7 @@ export class AllocationManager { deployment: deployment, allocation: createAllocationEventLogs.allocationID, allocatedTokens: amount, + protocolNetwork: this.network.specification.networkIdentifier, } } @@ -460,7 +477,7 @@ export class AllocationManager { allocation: params.allocationID, proof: params.proof, }) - return await this.contracts.staking.populateTransaction.allocateFrom( + return await this.network.contracts.staking.populateTransaction.allocateFrom( params.indexer, params.subgraphDeploymentID, params.tokens, @@ -470,65 +487,6 @@ export class AllocationManager { ) } - async allocate( - deployment: SubgraphDeploymentID, - amount: BigNumber, - indexNode: string | undefined, - ): Promise { - try { - const params = await this.prepareAllocateParams( - this.logger, - deployment, - amount, - indexNode, - ) - - this.logger.debug(`Sending allocateFrom transaction`, { - indexer: params.indexer, - subgraphDeployment: deployment.ipfsHash, - amount: formatGRT(params.tokens), - allocation: params.allocationID, - proof: params.proof, - }) - - const receipt = await this.transactionManager.executeTransaction( - async () => - this.contracts.staking.estimateGas.allocateFrom( - params.indexer, - params.subgraphDeploymentID, - params.tokens, - params.allocationID, - params.metadata, - params.proof, - ), - async (gasLimit) => - this.contracts.staking.allocateFrom( - params.indexer, - params.subgraphDeploymentID, - params.tokens, - params.allocationID, - params.metadata, - params.proof, - { gasLimit }, - ), - this.logger.child({ function: 'staking.allocate' }), - ) - - return await this.confirmAllocate( - 0, - deployment.ipfsHash, - amount.toString(), - receipt, - ) - } catch (error) { - this.logger.error(`Failed to allocate`, { - amount: formatGRT(amount), - error, - }) - throw error - } - } - async prepareUnallocateParams( logger: Logger, allocationID: string, @@ -539,9 +497,9 @@ export class AllocationManager { allocationID: allocationID, poi: poi || 'none provided', }) - const allocation = await this.networkMonitor.allocation(allocationID) + const allocation = await this.network.networkMonitor.allocation(allocationID) // Ensure allocation is old enough to close - const currentEpoch = await this.contracts.epochManager.currentEpoch() + const currentEpoch = await this.network.contracts.epochManager.currentEpoch() if (BigNumber.from(allocation.createdAtEpoch).eq(currentEpoch)) { throw indexerError( IndexerErrorCode.IE064, @@ -551,7 +509,7 @@ export class AllocationManager { ) } - poi = await this.networkMonitor.resolvePOI(allocation, poi, force) + poi = await this.network.networkMonitor.resolvePOI(allocation, poi, force) // Double-check whether the allocation is still active on chain, to // avoid unnecessary transactions. @@ -560,7 +518,7 @@ export class AllocationManager { // enum AllocationState { Null, Active, Closed, Finalized, Claimed } // // in the contracts. - const state = await this.contracts.staking.getAllocationState(allocation.id) + const state = await this.network.contracts.staking.getAllocationState(allocation.id) if (state !== 1) { throw indexerError(IndexerErrorCode.IE065) } @@ -589,7 +547,7 @@ export class AllocationManager { const closeAllocationEventLogs = this.findEvent( 'AllocationClosed', - this.contracts.staking.interface, + this.network.contracts.staking.interface, 'allocationID', allocationID, receipt, @@ -604,7 +562,7 @@ export class AllocationManager { const rewardsEventLogs = this.findEvent( 'RewardsAssigned', - this.contracts.rewardsManager.interface, + this.network.contracts.rewardsManager.interface, 'allocationID', allocationID, receipt, @@ -635,9 +593,9 @@ export class AllocationManager { logger.info('Identifying receipts worth collecting', { allocation: closeAllocationEventLogs.allocationID, }) - const allocation = await this.networkMonitor.allocation(allocationID) + const allocation = await this.network.networkMonitor.allocation(allocationID) // Collect query fees for this allocation - const isCollectingQueryFees = await this.receiptCollector.collectReceipts( + const isCollectingQueryFees = await this.network.receiptCollector.collectReceipts( actionID, allocation, ) @@ -648,6 +606,7 @@ export class AllocationManager { ) const offchainIndexingRule = { identifier: allocation.subgraphDeployment.id.ipfsHash, + protocolNetwork: this.network.specification.networkIdentifier, identifierType: SubgraphIdentifierType.DEPLOYMENT, decisionBasis: IndexingDecisionBasis.OFFCHAIN, } as Partial @@ -662,6 +621,7 @@ export class AllocationManager { allocatedTokens: formatGRT(closeAllocationEventLogs.tokens), indexingRewards: formatGRT(rewardsAssigned), receiptsWorthCollecting: isCollectingQueryFees, + protocolNetwork: this.network.specification.networkIdentifier, } } @@ -673,7 +633,7 @@ export class AllocationManager { allocationID: params.allocationID, POI: params.poi, }) - return await this.contracts.staking.populateTransaction.closeAllocation( + return await this.network.contracts.staking.populateTransaction.closeAllocation( params.allocationID, params.poi, ) @@ -689,46 +649,6 @@ export class AllocationManager { return await this.populateUnallocateTransaction(logger, params) } - async unallocate( - allocationID: string, - poi: string | undefined, - force: boolean, - ): Promise { - try { - const params = await this.prepareUnallocateParams( - this.logger, - allocationID, - poi, - force, - ) - const allocation = await this.networkMonitor.allocation(allocationID) - this.logger.debug('Sending closeAllocation transaction') - const receipt = await this.transactionManager.executeTransaction( - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - () => - this.contracts.staking.estimateGas.closeAllocation( - params.allocationID, - params.poi, - ), - (gasLimit) => - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.contracts.staking.closeAllocation(params.allocationID, params.poi, { - gasLimit, - }), - this.logger.child({ function: 'staking.closeAllocation' }), - ) - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return await this.confirmUnallocate(0, allocation.id!, receipt) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE015, error) - this.logger.warn(`Failed to close allocation`, { - err, - }) - throw err - } - } - async prepareReallocateParams( logger: Logger, allocationID: string, @@ -746,7 +666,7 @@ export class AllocationManager { /* Fetch all active allocations and search for our input parameter `allocationID`. * We don't call `fetchAllocations` here because all allocations will be required * later when generating a new `uniqueAllocationID`. */ - const activeAllocations = await this.networkMonitor.allocations( + const activeAllocations = await this.network.networkMonitor.allocations( AllocationStatus.ACTIVE, ) const allocationAddress = toAddress(allocationID) @@ -762,7 +682,7 @@ export class AllocationManager { } // Ensure allocation is old enough to close - const currentEpoch = await this.contracts.epochManager.currentEpoch() + const currentEpoch = await this.network.contracts.epochManager.currentEpoch() if (BigNumber.from(allocation.createdAtEpoch).eq(currentEpoch)) { throw indexerError( IndexerErrorCode.IE064, @@ -773,7 +693,11 @@ export class AllocationManager { } logger.debug('Resolving POI') - const allocationPOI = await this.networkMonitor.resolvePOI(allocation, poi, force) + const allocationPOI = await this.network.networkMonitor.resolvePOI( + allocation, + poi, + force, + ) logger.debug('POI resolved', { userProvidedPOI: poi, poi: allocationPOI, @@ -786,7 +710,7 @@ export class AllocationManager { // enum AllocationState { Null, Active, Closed, Finalized, Claimed } // // in the this.contracts. - const state = await this.contracts.staking.getAllocationState(allocation.id) + const state = await this.network.contracts.staking.getAllocationState(allocation.id) if (state !== 1) { logger.warn(`Allocation has already been closed`) throw indexerError(IndexerErrorCode.IE065, `Allocation has already been closed`) @@ -804,7 +728,7 @@ export class AllocationManager { logger.debug('Generating a new unique Allocation ID') const { allocationSigner, allocationId: newAllocationId } = uniqueAllocationID( - this.transactionManager.wallet.mnemonic.phrase, + this.network.transactionManager.wallet.mnemonic.phrase, currentEpoch.toNumber(), allocation.subgraphDeployment.id, activeAllocations.map((allocation) => allocation.id), @@ -822,12 +746,12 @@ export class AllocationManager { // enum AllocationState { Null, Active, Closed, Finalized, Claimed } // // in the this.contracts. - const newAllocationState = await this.contracts.staking.getAllocationState( + const newAllocationState = await this.network.contracts.staking.getAllocationState( newAllocationId, ) if (newAllocationState !== 0) { logger.warn(`Skipping Allocation as it already exists onchain`, { - indexer: this.indexer, + indexer: this.network.specification.indexerOptions.address, allocation: newAllocationId, newAllocationState, }) @@ -837,15 +761,19 @@ export class AllocationManager { logger.debug('Generating new allocation ID proof', { newAllocationSigner: allocationSigner, newAllocationID: newAllocationId, - indexerAddress: this.indexer, + indexerAddress: this.network.specification.indexerOptions.address, }) - const proof = await allocationIdProof(allocationSigner, this.indexer, newAllocationId) + const proof = await allocationIdProof( + allocationSigner, + this.network.specification.indexerOptions.address, + newAllocationId, + ) logger.debug('Successfully generated allocation ID proof', { allocationIDProof: proof, }) logger.info(`Prepared close and allocate multicall transaction`, { - indexer: this.indexer, + indexer: this.network.specification.indexerOptions.address, oldAllocationAmount: formatGRT(allocation.allocatedTokens), oldAllocation: allocation.id, newAllocation: newAllocationId, @@ -859,7 +787,7 @@ export class AllocationManager { return { closingAllocationID: allocation.id, poi: allocationPOI, - indexer: this.indexer, + indexer: this.network.specification.indexerOptions.address, subgraphDeploymentID: allocation.subgraphDeployment.id.bytes32, tokens: amount, newAllocationID: newAllocationId, @@ -886,7 +814,7 @@ export class AllocationManager { const closeAllocationEventLogs = this.findEvent( 'AllocationClosed', - this.contracts.staking.interface, + this.network.contracts.staking.interface, 'allocationID', allocationID, receipt, @@ -901,7 +829,7 @@ export class AllocationManager { const createAllocationEventLogs = this.findEvent( 'AllocationCreated', - this.contracts.staking.interface, + this.network.contracts.staking.interface, 'subgraphDeploymentID', closeAllocationEventLogs.subgraphDeploymentID, receipt, @@ -916,7 +844,7 @@ export class AllocationManager { const rewardsEventLogs = this.findEvent( 'RewardsAssigned', - this.contracts.rewardsManager.interface, + this.network.contracts.rewardsManager.interface, 'allocationID', allocationID, receipt, @@ -949,9 +877,9 @@ export class AllocationManager { logger.info('Identifying receipts worth collecting', { allocation: closeAllocationEventLogs.allocationID, }) - const allocation = await this.networkMonitor.allocation(allocationID) + const allocation = await this.network.networkMonitor.allocation(allocationID) // Collect query fees for this allocation - const isCollectingQueryFees = await this.receiptCollector.collectReceipts( + const isCollectingQueryFees = await this.network.receiptCollector.collectReceipts( actionID, allocation, ) @@ -966,6 +894,7 @@ export class AllocationManager { allocationAmount: formatGRT(createAllocationEventLogs.tokens), identifierType: SubgraphIdentifierType.DEPLOYMENT, decisionBasis: IndexingDecisionBasis.ALWAYS, + protocolNetwork: this.network.specification.networkIdentifier, } as Partial await upsertIndexingRule(logger, this.models, indexingRule) @@ -980,6 +909,7 @@ export class AllocationManager { receiptsWorthCollecting: isCollectingQueryFees, createdAllocation: createAllocationEventLogs.allocationID, createdAllocationStake: formatGRT(createAllocationEventLogs.tokens), + protocolNetwork: this.network.specification.networkIdentifier, } } @@ -997,12 +927,14 @@ export class AllocationManager { amount, force, ) + return [ - await this.contracts.staking.populateTransaction.closeAllocation( + await this.network.contracts.staking.populateTransaction.closeAllocation( params.closingAllocationID, params.poi, ), - await this.contracts.staking.populateTransaction.allocate( + await this.network.contracts.staking.populateTransaction.allocateFrom( + params.indexer, params.subgraphDeploymentID, params.tokens, params.newAllocationID, @@ -1012,66 +944,16 @@ export class AllocationManager { ] } - async reallocate( - allocationID: string, - poi: string | undefined, - amount: BigNumber, - force: boolean, - ): Promise { - try { - const params = await this.prepareReallocateParams( - this.logger, - allocationID, - poi, - amount, - force, - ) - - this.logger.info(`Sending close and allocate multicall transaction`, { - indexer: params.indexer, - oldAllocation: params.closingAllocationID, - newAllocation: params.newAllocationID, - newAllocationAmount: formatGRT(params.tokens), - deployment: params.subgraphDeploymentID, - poi: params.poi, - proof: params.proof, - }) - - const callData = [ - await this.contracts.staking.populateTransaction.closeAllocation( - params.closingAllocationID, - params.poi, - ), - await this.contracts.staking.populateTransaction.allocate( - params.subgraphDeploymentID, - params.tokens, - params.newAllocationID, - params.metadata, - params.proof, - ), - ].map((tx) => tx.data as string) - - const receipt = await this.transactionManager.executeTransaction( - async () => this.contracts.staking.estimateGas.multicall(callData), - async (gasLimit) => this.contracts.staking.multicall(callData, { gasLimit }), - this.logger.child({ - function: 'closeAndAllocate', - }), - ) - - return await this.confirmReallocate(0, allocationID, receipt) - } catch (error) { - this.logger.error(error.toString()) - throw error - } - } - async matchingRuleExists( logger: Logger, subgraphDeploymentID: SubgraphDeploymentID, ): Promise { - const indexingRules = await fetchIndexingRules(this.models, true) - const subgraphDeployment = await this.networkMonitor.subgraphDeployment( + const indexingRules = await fetchIndexingRules( + this.models, + true, + this.network.specification.networkIdentifier, + ) + const subgraphDeployment = await this.network.networkMonitor.subgraphDeployment( subgraphDeploymentID.ipfsHash, ) if (!subgraphDeployment) { @@ -1109,14 +991,14 @@ export class AllocationManager { } // Fetch the allocation on chain to inspect its amount - const allocation = await this.networkMonitor.allocation(action.allocationID) + const allocation = await this.network.networkMonitor.allocation(action.allocationID) // Accrue rewards, except for null or zeroed POI const zeroHexString = utils.hexlify(Array(32).fill(0)) rewards = !action.poi || action.poi === zeroHexString ? BigNumber.from(0) - : await this.contracts.rewardsManager.getRewards(action.allocationID) + : await this.network.contracts.rewardsManager.getRewards(action.allocationID) unallocates = unallocates.add(allocation.allocatedTokens) } @@ -1136,7 +1018,9 @@ export class AllocationManager { logger.debug(`Validating action batch`, { size: batch.length }) // Validate stake feasibility - const indexerFreeStake = await this.contracts.staking.getIndexerCapacity(this.indexer) + const indexerFreeStake = await this.network.contracts.staking.getIndexerCapacity( + this.network.specification.indexerOptions.address, + ) const actionsBatchStakeusageSummaries = await pMap(batch, async (action: Action) => this.stakeUsageSummary(action), ) diff --git a/packages/indexer-common/src/indexer-management/client.ts b/packages/indexer-common/src/indexer-management/client.ts index df375449a..9ca2d190a 100644 --- a/packages/indexer-common/src/indexer-management/client.ts +++ b/packages/indexer-common/src/indexer-management/client.ts @@ -2,15 +2,7 @@ import { buildSchema, print } from 'graphql' import gql from 'graphql-tag' import { executeExchange } from '@urql/exchange-execute' import { Client, ClientOptions } from '@urql/core' -import { - equal, - Eventual, - Logger, - mutable, - NetworkContracts, - WritableEventual, -} from '@graphprotocol/common-ts' -import { NetworkSubgraph } from '../network-subgraph' +import { equal, Logger, mutable, WritableEventual } from '@graphprotocol/common-ts' import { IndexerManagementModels, IndexingRuleCreationAttributes } from './models' @@ -20,38 +12,19 @@ import costModelResolvers from './resolvers/cost-models' import indexingRuleResolvers from './resolvers/indexing-rules' import poiDisputeResolvers from './resolvers/poi-disputes' import statusResolvers from './resolvers/indexer-status' -import { BigNumber, ethers } from 'ethers' +import { BigNumber } from 'ethers' import { Op, Sequelize } from 'sequelize' -import { IndexingStatusResolver } from '../indexing-status' -import { TransactionManager } from '../transactions' -import { SubgraphManager } from './subgraphs' -import { AllocationReceiptCollector } from '../allocations/query-fees' -import { - ActionManager, - AllocationManagementMode, - AllocationManager, - NetworkMonitor, -} from '@graphprotocol/indexer-common' - -export interface IndexerManagementFeatures { - injectDai: boolean -} +import { GraphNode } from '../graph-node' +import { ActionManager, MultiNetworks, Network } from '@graphprotocol/indexer-common' export interface IndexerManagementResolverContext { models: IndexerManagementModels - address: string - contracts: NetworkContracts - indexingStatusResolver: IndexingStatusResolver - subgraphManager: SubgraphManager - networkMonitor: NetworkMonitor - networkSubgraph: NetworkSubgraph + graphNode: GraphNode logger: Logger defaults: IndexerManagementDefaults - features: IndexerManagementFeatures - dai: Eventual - actionManager: ActionManager - transactionManager: TransactionManager - receiptCollector: AllocationReceiptCollector + actionManager: ActionManager | undefined + multiNetworks: MultiNetworks | undefined + dai: WritableEventual } const SCHEMA_SDL = gql` @@ -79,6 +52,7 @@ const SCHEMA_SDL = gql` status: String allocation: String subgraphDeployment: String + protocolNetwork: String } enum AllocationStatus { @@ -102,12 +76,14 @@ const SCHEMA_SDL = gql` signalledTokens: BigInt! stakedTokens: BigInt! status: AllocationStatus! + protocolNetwork: String! } type CreateAllocationResult { allocation: String! deployment: String! allocatedTokens: String! + protocolNetwork: String! } type CloseAllocationResult { @@ -115,6 +91,7 @@ const SCHEMA_SDL = gql` allocatedTokens: String! indexingRewards: String! receiptsWorthCollecting: Boolean! + protocolNetwork: String! } type ReallocateAllocationResult { @@ -123,6 +100,7 @@ const SCHEMA_SDL = gql` receiptsWorthCollecting: Boolean! createdAllocation: String! createdAllocationStake: String! + protocolNetwork: String! } enum ActionStatus { @@ -156,6 +134,7 @@ const SCHEMA_SDL = gql` failureReason: String createdAt: BigInt! updatedAt: BigInt + protocolNetwork: String! } input ActionInput { @@ -169,6 +148,7 @@ const SCHEMA_SDL = gql` source: String! reason: String! priority: Int! + protocolNetwork: String! } input ActionUpdateInput { @@ -198,6 +178,7 @@ const SCHEMA_SDL = gql` priority createdAt updatedAt + protocolNetwork } type ActionResult { @@ -214,16 +195,23 @@ const SCHEMA_SDL = gql` transaction: String failureReason: String priority: Int + protocolNetwork: String! } input ActionFilter { id: Int + protocolNetwork: String type: ActionType status: String source: String reason: String } + input POIDisputeIdentifier { + allocationID: String! + protocolNetwork: String! + } + type POIDispute { allocationID: String! subgraphDeploymentID: String! @@ -238,6 +226,7 @@ const SCHEMA_SDL = gql` previousEpochStartBlockNumber: Int! previousEpochReferenceProof: String status: String! + protocolNetwork: String! } input POIDisputeInput { @@ -254,6 +243,7 @@ const SCHEMA_SDL = gql` previousEpochStartBlockNumber: Int! previousEpochReferenceProof: String status: String! + protocolNetwork: String! } type IndexingRule { @@ -272,6 +262,7 @@ const SCHEMA_SDL = gql` decisionBasis: IndexingDecisionBasis! requireSupported: Boolean! safety: Boolean! + protocolNetwork: String! } input IndexingRuleInput { @@ -290,6 +281,12 @@ const SCHEMA_SDL = gql` decisionBasis: IndexingDecisionBasis requireSupported: Boolean safety: Boolean + protocolNetwork: String! + } + + input IndexingRuleIdentifier { + identifier: String! + protocolNetwork: String! } type GeoLocation { @@ -299,6 +296,7 @@ const SCHEMA_SDL = gql` type IndexerRegistration { url: String + protocolNetwork: String! address: String registered: Boolean! location: GeoLocation @@ -349,6 +347,7 @@ const SCHEMA_SDL = gql` type IndexerEndpoint { url: String healthy: Boolean! + protocolNetwork: String! tests: [IndexerEndpointTest!]! } @@ -370,19 +369,26 @@ const SCHEMA_SDL = gql` } type Query { - indexingRule(identifier: String!, merged: Boolean! = false): IndexingRule - indexingRules(merged: Boolean! = false): [IndexingRule!]! - indexerRegistration: IndexerRegistration! + indexingRule( + identifier: IndexingRuleIdentifier! + merged: Boolean! = false + ): IndexingRule + indexingRules(merged: Boolean! = false, protocolNetwork: String): [IndexingRule!]! + indexerRegistration(protocolNetwork: String!): IndexerRegistration! indexerDeployments: [IndexerDeployment]! - indexerAllocations: [IndexerAllocation]! - indexerEndpoints: IndexerEndpoints! + indexerAllocations(protocolNetwork: String!): [IndexerAllocation]! + indexerEndpoints(protocolNetwork: String!): [IndexerEndpoints!]! costModels(deployments: [String!]): [CostModel!]! costModel(deployment: String!): CostModel - dispute(allocationID: String!): POIDispute - disputes(status: String!, minClosedEpoch: Int!): [POIDispute]! - disputesClosedAfter(closedAfterBlock: BigInt!): [POIDispute]! + dispute(identifier: POIDisputeIdentifier!): POIDispute + disputes( + status: String! + minClosedEpoch: Int! + protocolNetwork: String + ): [POIDispute]! + disputesClosedAfter(closedAfterBlock: BigInt!, protocolNetwork: String): [POIDispute]! allocations(filter: AllocationFilter!): [Allocation!]! @@ -397,30 +403,33 @@ const SCHEMA_SDL = gql` type Mutation { setIndexingRule(rule: IndexingRuleInput!): IndexingRule! - deleteIndexingRule(identifier: String!): Boolean! - deleteIndexingRules(identifiers: [String!]!): Boolean! + deleteIndexingRule(identifier: IndexingRuleIdentifier!): Boolean! + deleteIndexingRules(identifiers: [IndexingRuleIdentifier!]!): Boolean! setCostModel(costModel: CostModelInput!): CostModel! deleteCostModels(deployments: [String!]!): Int! storeDisputes(disputes: [POIDisputeInput!]!): [POIDispute!] - deleteDisputes(allocationIDs: [String!]!): Int! + deleteDisputes(identifiers: [POIDisputeIdentifier!]!): Int! createAllocation( deployment: String! amount: String! indexNode: String + protocolNetwork: String! ): CreateAllocationResult! closeAllocation( allocation: String! poi: String force: Boolean + protocolNetwork: String! ): CloseAllocationResult! reallocateAllocation( allocation: String! poi: String amount: String! force: Boolean + protocolNetwork: String! ): ReallocateAllocationResult! updateAction(action: ActionInput!): Action! @@ -441,22 +450,15 @@ export interface IndexerManagementDefaults { } export interface IndexerManagementClientOptions { + logger: Logger models: IndexerManagementModels - address: string - contracts: NetworkContracts - indexingStatusResolver: IndexingStatusResolver + graphNode: GraphNode + // TODO:L2: Do we need this information? The GraphNode class auto-selects nodes based + // on availability. + // Ford: there were some edge cases where the GraphNode was not able to auto handle it on its own indexNodeIDs: string[] - deploymentManagementEndpoint: string - networkSubgraph: NetworkSubgraph - logger: Logger + multiNetworks: MultiNetworks | undefined defaults: IndexerManagementDefaults - features: IndexerManagementFeatures - ethereum?: ethers.providers.BaseProvider - transactionManager?: TransactionManager - receiptCollector?: AllocationReceiptCollector - networkMonitor?: NetworkMonitor - allocationManagementMode?: AllocationManagementMode - autoAllocationMinBatchSize?: number } export class IndexerManagementClient extends Client { @@ -503,26 +505,12 @@ export class IndexerManagementClient extends Client { } } +// TODO:L2: Put the IndexerManagementClient creation inside the Agent, and receive +// MultiNetworks from it export const createIndexerManagementClient = async ( options: IndexerManagementClientOptions, ): Promise => { - const { - models, - address, - contracts, - indexingStatusResolver, - indexNodeIDs, - deploymentManagementEndpoint, - networkSubgraph, - logger, - defaults, - features, - transactionManager, - receiptCollector, - networkMonitor, - allocationManagementMode, - autoAllocationMinBatchSize, - } = options + const { models, graphNode, indexNodeIDs, logger, defaults, multiNetworks } = options const schema = buildSchema(print(SCHEMA_SDL)) const resolvers = { ...indexingRuleResolvers, @@ -535,56 +523,24 @@ export const createIndexerManagementClient = async ( const dai: WritableEventual = mutable() - const subgraphManager = new SubgraphManager(deploymentManagementEndpoint, indexNodeIDs) - let allocationManager: AllocationManager | undefined = undefined - let actionManager: ActionManager | undefined = undefined - - if (transactionManager && networkMonitor) { - if (receiptCollector) { - // TODO: AllocationManager construction inside ActionManager - allocationManager = new AllocationManager( - contracts, - logger.child({ component: 'AllocationManager' }), - address, - models, - networkMonitor, - receiptCollector, - subgraphManager, - transactionManager, - ) - actionManager = new ActionManager( - allocationManager, - networkMonitor, - logger.child({ component: 'ActionManager' }), - models, - allocationManagementMode, - autoAllocationMinBatchSize, - ) - - logger.info('Begin monitoring the queue for approved actions to execute') - await actionManager.monitorQueue() - } + const actionManager = multiNetworks + ? await ActionManager.create(multiNetworks, logger, models, graphNode) + : undefined + + const context: IndexerManagementResolverContext = { + models, + graphNode, + defaults, + logger: logger.child({ component: 'IndexerManagementClient' }), + dai, + multiNetworks, + actionManager, } const exchange = executeExchange({ schema, rootValue: resolvers, - context: { - models, - address, - contracts, - indexingStatusResolver, - subgraphManager, - networkMonitor, - networkSubgraph, - logger: logger ? logger.child({ component: 'IndexerManagementClient' }) : undefined, - defaults, - features, - dai, - transactionManager, - actionManager, - receiptCollector, - }, + context, }) return new IndexerManagementClient({ url: 'no-op', exchanges: [exchange] }, options, { diff --git a/packages/indexer-common/src/indexer-management/index.ts b/packages/indexer-common/src/indexer-management/index.ts index dd2d8d55a..6990e72b3 100644 --- a/packages/indexer-common/src/indexer-management/index.ts +++ b/packages/indexer-common/src/indexer-management/index.ts @@ -4,6 +4,5 @@ export * from './client' export * from './models' export * from './monitor' export * from './server' -export * from './subgraphs' export * from './rules' export * from './types' diff --git a/packages/indexer-common/src/indexer-management/models/action.ts b/packages/indexer-common/src/indexer-management/models/action.ts index d2da5ccf5..642a9fbca 100644 --- a/packages/indexer-common/src/indexer-management/models/action.ts +++ b/packages/indexer-common/src/indexer-management/models/action.ts @@ -8,6 +8,7 @@ import { Model, Sequelize, } from 'sequelize' +import { caip2IdRegex } from '../../parsers' import { ActionStatus, ActionType } from '@graphprotocol/indexer-common' export class Action extends Model< @@ -34,6 +35,8 @@ export class Action extends Model< declare createdAt: CreationOptional declare updatedAt: CreationOptional + declare protocolNetwork: string + // eslint-disable-next-line @typescript-eslint/ban-types public toGraphQL(): object { return { ...this.toJSON(), __typename: 'Action' } @@ -141,6 +144,13 @@ export const defineActionModels = (sequelize: Sequelize): ActionModels => { allowNull: true, defaultValue: null, }, + protocolNetwork: { + type: DataTypes.STRING(50), + primaryKey: true, + validate: { + is: caip2IdRegex, + }, + }, }, { modelName: 'Action', diff --git a/packages/indexer-common/src/indexer-management/models/indexing-rule.ts b/packages/indexer-common/src/indexer-management/models/indexing-rule.ts index fc3ddf1bb..76ee2b265 100644 --- a/packages/indexer-common/src/indexer-management/models/indexing-rule.ts +++ b/packages/indexer-common/src/indexer-management/models/indexing-rule.ts @@ -2,6 +2,7 @@ import { DataTypes, Model, Optional, Sequelize } from 'sequelize' import { processIdentifier, SubgraphIdentifierType } from '../../subgraphs' +import { caip2IdRegex } from '../../parsers' export enum IndexingDecisionBasis { RULES = 'rules', @@ -29,6 +30,14 @@ export interface IndexingRuleAttributes { decisionBasis: IndexingDecisionBasis requireSupported: boolean safety: boolean + protocolNetwork: string +} + +// Unambiguously identify a Indexing Rule in the Database. +// This type should match the IndexingRules primary key columns. +export interface IndexingRuleIdentifier { + identifier: string + protocolNetwork: string } export interface IndexingRuleCreationAttributes @@ -50,6 +59,7 @@ export interface IndexingRuleCreationAttributes | 'decisionBasis' | 'requireSupported' | 'safety' + | 'protocolNetwork' > {} export class IndexingRule @@ -72,6 +82,7 @@ export class IndexingRule public decisionBasis!: IndexingDecisionBasis public requireSupported!: boolean public safety!: boolean + public protocolNetwork!: string public createdAt!: Date public updatedAt!: Date @@ -144,7 +155,7 @@ export const defineIndexingRuleModels = (sequelize: Sequelize): IndexingRuleMode identifier: { type: DataTypes.STRING, primaryKey: true, - unique: true, + unique: false, allowNull: false, validate: { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -248,6 +259,14 @@ export const defineIndexingRuleModels = (sequelize: Sequelize): IndexingRuleMode allowNull: false, defaultValue: true, }, + protocolNetwork: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + validate: { + is: caip2IdRegex, + }, + }, }, { modelName: 'IndexingRule', diff --git a/packages/indexer-common/src/indexer-management/models/poi-dispute.ts b/packages/indexer-common/src/indexer-management/models/poi-dispute.ts index 9308b897f..3be43835b 100644 --- a/packages/indexer-common/src/indexer-management/models/poi-dispute.ts +++ b/packages/indexer-common/src/indexer-management/models/poi-dispute.ts @@ -2,6 +2,7 @@ import { Optional, Model, DataTypes, Sequelize } from 'sequelize' import { utils } from 'ethers' +import { caip2IdRegex } from '../../parsers' export interface POIDisputeAttributes { allocationID: string @@ -17,6 +18,14 @@ export interface POIDisputeAttributes { previousEpochStartBlockHash: string previousEpochStartBlockNumber: number status: string + protocolNetwork: string +} + +// Unambiguously identify a POI Dispute in the Database. +// This type should match the POIDispute primary key columns. +export interface POIDisputeIdentifier { + allocationID: string + protocolNetwork: string } export interface POIDisputeCreationAttributes @@ -35,6 +44,7 @@ export interface POIDisputeCreationAttributes | 'previousEpochStartBlockHash' | 'previousEpochStartBlockNumber' | 'status' + | 'protocolNetwork' > {} export class POIDispute @@ -54,6 +64,7 @@ export class POIDispute public previousEpochStartBlockHash!: string public previousEpochStartBlockNumber!: number public status!: string + public protocolNetwork!: string public createdAt!: Date public updatedAt!: Date @@ -244,6 +255,14 @@ export const definePOIDisputeModels = (sequelize: Sequelize): POIDisputeModels = type: DataTypes.STRING, allowNull: false, }, + protocolNetwork: { + type: DataTypes.STRING, + primaryKey: true, + allowNull: false, + validate: { + is: caip2IdRegex, + }, + }, }, { modelName: 'POIDispute', diff --git a/packages/indexer-common/src/indexer-management/monitor.ts b/packages/indexer-common/src/indexer-management/monitor.ts index 2e8cc1223..a4dd163f7 100644 --- a/packages/indexer-common/src/indexer-management/monitor.ts +++ b/packages/indexer-common/src/indexer-management/monitor.ts @@ -5,7 +5,7 @@ import { INDEXER_ERROR_MESSAGES, indexerError, IndexerErrorCode, - IndexingStatusResolver, + GraphNode, NetworkSubgraph, parseGraphQLAllocation, parseGraphQLEpochs, @@ -18,6 +18,7 @@ import { BlockPointer, resolveChainId, resolveChainAlias, + TransferredSubgraphDeployment, } from '@graphprotocol/indexer-common' import { Address, @@ -28,19 +29,22 @@ import { SubgraphDeploymentID, timer, toAddress, + formatGRT, } from '@graphprotocol/common-ts' +import { BigNumber } from 'ethers' import gql from 'graphql-tag' import { providers, utils, Wallet } from 'ethers' import pRetry from 'p-retry' +import { IndexerOptions } from '../network-specification' // The new read only Network class export class NetworkMonitor { constructor( public networkCAIPID: string, private contracts: NetworkContracts, - private indexer: Address, + private indexerOptions: IndexerOptions, private logger: Logger, - private indexingStatusResolver: IndexingStatusResolver, + private graphNode: GraphNode, private networkSubgraph: NetworkSubgraph, private ethereum: providers.BaseProvider, private epochSubgraph: EpochSubgraph, @@ -89,11 +93,12 @@ export class NetworkMonitor { this.logger.warn(errorMessage) throw indexerError(IndexerErrorCode.IE063, errorMessage) } - return parseGraphQLAllocation(result.data.allocation) + return parseGraphQLAllocation(result.data.allocation, this.networkCAIPID) } async allocations(status: AllocationStatus): Promise { try { + this.logger.debug(`Fetch ${status} allocations`) const result = await this.networkSubgraph.query( gql` query allocations($indexer: String!, $status: AllocationStatus!) { @@ -121,7 +126,7 @@ export class NetworkMonitor { } `, { - indexer: this.indexer.toLocaleLowerCase(), + indexer: this.indexerOptions.address.toLocaleLowerCase(), status: status, }, ) @@ -138,7 +143,7 @@ export class NetworkMonitor { this.logger.warn( `No ${ AllocationStatus[status.toUpperCase() as keyof typeof AllocationStatus] - } allocations found for indexer '${this.indexer}'`, + } allocations found for indexer '${this.indexerOptions.address}'`, ) return [] } @@ -194,38 +199,37 @@ export class NetworkMonitor { range: number, ): Promise { try { + this.logger.debug('Fetch recently closed allocations') const result = await this.networkSubgraph.query( gql` query allocations($indexer: String!, $closedAtEpochThreshold: Int!) { - indexer(id: $indexer) { - allocations: totalAllocations( - where: { - indexer: $indexer - status: Closed - closedAtEpoch_gte: $closedAtEpochThreshold - } - first: 1000 - ) { + allocations( + where: { + indexer: $indexer + status: Closed + closedAtEpoch_gte: $closedAtEpochThreshold + } + first: 1000 + ) { + id + indexer { id - indexer { - id - } - allocatedTokens - createdAtEpoch - closedAtEpoch - createdAtBlockHash - subgraphDeployment { - id - stakedTokens - signalledTokens - queryFeesAmount - } + } + allocatedTokens + createdAtEpoch + closedAtEpoch + createdAtBlockHash + subgraphDeployment { + id + stakedTokens + signalledTokens + queryFeesAmount } } } `, { - indexer: this.indexer.toLocaleLowerCase(), + indexer: this.indexerOptions.address.toLocaleLowerCase(), closedAtEpochThreshold: currentEpoch - range, }, ) @@ -234,15 +238,18 @@ export class NetworkMonitor { throw result.error } - if (!result.data) { - throw new Error(`No data / indexer not found on chain`) - } - - if (!result.data.indexer) { - throw new Error(`Indexer not found on chain`) + if ( + !result.data.allocations || + result.data.length === 0 || + result.data.allocations.length === 0 + ) { + this.logger.warn( + `No recently closed allocations found for indexer '${this.indexerOptions.address}'`, + ) + return [] } - return result.data.indexer.allocations.map(parseGraphQLAllocation) + return result.data.allocations.map(parseGraphQLAllocation) } catch (error) { const err = indexerError(IndexerErrorCode.IE010, error) this.logger.error(`Failed to query indexer's recently closed allocations`, { @@ -259,38 +266,36 @@ export class NetworkMonitor { const result = await this.networkSubgraph.query( gql` query allocations($indexer: String!, $subgraphDeploymentId: String!) { - indexer(id: $indexer) { - allocations: totalAllocations( - where: { - indexer: $indexer - status: Closed - subgraphDeployment: $subgraphDeploymentId - } - first: 5 - orderBy: closedAtBlockNumber - orderDirection: desc - ) { + allocations( + where: { + indexer: $indexer + status: Closed + subgraphDeployment: $subgraphDeploymentId + } + first: 5 + orderBy: closedAtBlockNumber + orderDirection: desc + ) { + id + poi + indexer { id - poi - indexer { - id - } - allocatedTokens - createdAtEpoch - closedAtEpoch - createdAtBlockHash - subgraphDeployment { - id - stakedTokens - signalledTokens - queryFeesAmount - } + } + allocatedTokens + createdAtEpoch + closedAtEpoch + createdAtBlockHash + subgraphDeployment { + id + stakedTokens + signalledTokens + queryFeesAmount } } } `, { - indexer: this.indexer.toLocaleLowerCase(), + indexer: this.indexerOptions.address.toLocaleLowerCase(), subgraphDeploymentId: subgraphDeploymentId.display.bytes32, }, ) @@ -299,15 +304,19 @@ export class NetworkMonitor { throw result.error } - if (!result.data) { - throw new Error(`No data / indexer not found on chain`) - } - - if (!result.data.indexer) { - throw new Error(`Indexer not found on chain`) + if ( + !result.data.allocations || + result.data.length === 0 || + result.data.allocations.length === 0 + ) { + this.logger.warn('No closed allocations found for deployment', { + id: subgraphDeploymentId.display.bytes32, + ipfsHash: subgraphDeploymentId.display, + }) + return [] } - return result.data.indexer.allocations.map(parseGraphQLAllocation) + return result.data.allocations.map(parseGraphQLAllocation) } catch (error) { const err = indexerError(IndexerErrorCode.IE010, error) this.logger.error( @@ -326,7 +335,7 @@ export class NetworkMonitor { } let subgraphs: Subgraph[] = [] const queryProgress = { - lastId: '', + lastCreatedAt: 0, first: 20, fetched: 0, exhausted: false, @@ -342,14 +351,15 @@ export class NetworkMonitor { try { const result = await this.networkSubgraph.query( gql` - query subgraphs($first: Int!, $lastId: String!, $subgraphs: [String!]!) { + query subgraphs($first: Int!, $lastCreatedAt: Int!, $subgraphs: [String!]!) { subgraphs( - where: { id_gt: $lastId, id_in: $subgraphs } - orderBy: id + where: { id_gt: $lastCreatedAt, id_in: $subgraphs } + orderBy: createdAt orderDirection: asc first: $first ) { id + createdAt versionCount versions { version @@ -363,7 +373,7 @@ export class NetworkMonitor { `, { first: queryProgress.first, - lastId: queryProgress.lastId, + lastCreatedAt: queryProgress.lastCreatedAt, subgraphs: ids, }, ) @@ -399,7 +409,7 @@ export class NetworkMonitor { queryProgress.exhausted = results.length < queryProgress.first queryProgress.fetched += results.length - queryProgress.lastId = results[results.length - 1].id + queryProgress.lastCreatedAt = results[results.length - 1].createdAt subgraphs = subgraphs.concat(results) } catch (error) { @@ -464,7 +474,10 @@ export class NetworkMonitor { } // TODO: Make and use parseGraphqlDeployment() function - return parseGraphQLSubgraphDeployment(result.data.subgraphDeployments[0]) + return parseGraphQLSubgraphDeployment( + result.data.subgraphDeployments[0], + this.networkCAIPID, + ) } catch (error) { const err = indexerError(IndexerErrorCode.IE010, error) this.logger.error( @@ -477,10 +490,95 @@ export class NetworkMonitor { } } + async transferredDeployments(): Promise { + this.logger.debug('Querying the Network for transferred subgraph deployments') + try { + const result = await this.networkSubgraph.query( + // TODO: Consider querying for the same time range as the Agent's evaluation, limiting + // results to recent transfers. + gql` + { + subgraphs( + where: { startedTransferToL2: true } + orderBy: startedTransferToL2At + orderDirection: asc + ) { + id + idOnL1 + idOnL2 + startedTransferToL2 + startedTransferToL2At + startedTransferToL2AtBlockNumber + startedTransferToL2AtTx + transferredToL2 + transferredToL2At + transferredToL2AtBlockNumber + transferredToL2AtTx + versions { + subgraphDeployment { + ipfsHash + } + } + } + } + `, + ) + + if (result.error) { + throw result.error + } + + const transferredDeployments = result.data.subgraphs + + // There may be no transferred subgraphs, handle gracefully + if (transferredDeployments.length == 0) { + this.logger.warn( + 'Failed to query subgraph deployments transferred to L2: no deployments found', + ) + throw new Error('No transferred subgraph deployments returned') + } + + // Flatten multiple subgraphDeployment versions into a single `TransferredSubgraphDeployment` object + // TODO: We could use `zod` to parse GraphQL responses into the expected type + return transferredDeployments.flatMap((deployment: any) => { + return deployment.versions.map((version: any) => { + return { + id: deployment.id, + idOnL1: deployment.idOnL1, + idOnL2: deployment.idOnL2, + startedTransferToL2: deployment.startedTransferToL2, + startedTransferToL2At: BigNumber.from(deployment.startedTransferToL2At), + startedTransferToL2AtBlockNumber: BigNumber.from( + deployment.startedTransferToL2AtBlockNumber, + ), + startedTransferToL2AtTx: deployment.startedTransferToL2AtTx, + transferredToL2: deployment.transferredToL2, + transferredToL2At: deployment.transferredToL2At + ? BigNumber.from(deployment.transferredToL2At) + : null, + transferredToL2AtTx: deployment.transferredToL2AtTx, + transferredToL2AtBlockNumber: deployment.transferredToL2AtBlockNumber + ? BigNumber.from(deployment.transferredToL2AtBlockNumber) + : null, + ipfsHash: version.subgraphDeployment.ipfsHash, + protocolNetwork: this.networkCAIPID, + ready: null, + } + }) + }) + } catch (err) { + const error = indexerError(IndexerErrorCode.IE009, err.message) + this.logger.error(`Failed to query transferred subgraph deployments`, { + error, + }) + throw error + } + } + async subgraphDeployments(): Promise { const deployments = [] const queryProgress = { - lastId: '', + lastCreatedAt: 0, first: 10, fetched: 0, exhausted: false, @@ -495,28 +593,24 @@ export class NetworkMonitor { try { const result = await this.networkSubgraph.query( gql` - query subgraphDeployments($first: Int!, $lastId: String!) { + query subgraphDeployments($first: Int!, $lastCreatedAt: Int!) { subgraphDeployments( - where: { id_gt: $lastId } - orderBy: id + where: { createdAt_gt: $lastCreatedAt } + orderBy: createdAt orderDirection: asc first: $first ) { + createdAt id ipfsHash deniedAt stakedTokens signalledTokens queryFeesAmount - indexerAllocations { - indexer { - id - } - } } } `, - { first: queryProgress.first, lastId: queryProgress.lastId }, + { first: queryProgress.first, lastCreatedAt: queryProgress.lastCreatedAt }, ) if (result.error) { @@ -535,8 +629,14 @@ export class NetworkMonitor { queryProgress.exhausted = networkDeployments.length < queryProgress.first queryProgress.fetched += networkDeployments.length - queryProgress.lastId = networkDeployments[networkDeployments.length - 1].id - deployments.push(...networkDeployments.map(parseGraphQLSubgraphDeployment)) + queryProgress.lastCreatedAt = + networkDeployments[networkDeployments.length - 1].createdAt + deployments.push( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...networkDeployments.map((x: any) => + parseGraphQLSubgraphDeployment(x, this.networkCAIPID), + ), + ) } catch (err) { queryProgress.retriesRemaining-- this.logger.warn(`Failed to query subgraph deployments`, { @@ -667,7 +767,7 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n if (networkID == this.networkCAIPID) { startBlockHash = (await this.ethereum.getBlock(+validBlock.blockNumber)).hash } else { - startBlockHash = await this.indexingStatusResolver.blockHashFromNumber( + startBlockHash = await this.graphNode.blockHashFromNumber( networkAlias, +validBlock.blockNumber, ) @@ -717,9 +817,9 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n async fetchPOIBlockPointer(allocation: Allocation): Promise { try { - const deploymentIndexingStatuses = await this.indexingStatusResolver.indexingStatus( - [allocation.subgraphDeployment.id], - ) + const deploymentIndexingStatuses = await this.graphNode.indexingStatus([ + allocation.subgraphDeployment.id, + ]) if ( deploymentIndexingStatuses.length != 1 || deploymentIndexingStatuses[0].chains.length != 1 || @@ -774,7 +874,7 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n return poi! case false: return ( - (await this.indexingStatusResolver.proofOfIndexing( + (await this.graphNode.proofOfIndexing( allocation.subgraphDeployment.id, await this.fetchPOIBlockPointer(allocation), allocation.indexer, @@ -785,7 +885,7 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n case false: { const epochStartBlock = await this.fetchPOIBlockPointer(allocation) // Obtain the start block of the current epoch - const generatedPOI = await this.indexingStatusResolver.proofOfIndexing( + const generatedPOI = await this.graphNode.proofOfIndexing( allocation.subgraphDeployment.id, epochStartBlock, allocation.indexer, @@ -793,7 +893,7 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n switch (poi == generatedPOI) { case true: if (poi == undefined) { - const deploymentStatus = await this.indexingStatusResolver.indexingStatus([ + const deploymentStatus = await this.graphNode.indexingStatus([ allocation.subgraphDeployment.id, ]) throw indexerError( @@ -831,9 +931,14 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n contracts: NetworkContracts, networkSubgraph: NetworkSubgraph, ): Promise> { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const initialPauseValue = await contracts.controller.paused().catch((_) => { + return false + }) return timer(60_000) .reduce(async (currentlyPaused) => { try { + logger.debug('Query network subgraph isPaused state') const result = await networkSubgraph.query( gql` { @@ -863,7 +968,7 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n }) return currentlyPaused } - }, await contracts.controller.paused()) + }, initialPauseValue) .map((paused) => { logger.info(paused ? `Network paused` : `Network active`) return paused @@ -886,6 +991,7 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n return timer(60_000) .reduce(async (isOperator) => { try { + logger.debug('Check operator status') return await contracts.staking.isOperator(wallet.address, indexerAddress) } catch (err) { logger.warn( @@ -904,4 +1010,226 @@ Please submit an issue at https://github.com/graphprotocol/block-oracle/issues/n return isOperator }) } + + async claimableAllocations(disputableEpoch: number): Promise { + try { + this.logger.debug('Fetch claimable allocations', { + closedAtEpoch_lte: disputableEpoch, + queryFeesCollected_gte: this.indexerOptions.rebateClaimThreshold.toString(), + }) + const result = await this.networkSubgraph.query( + gql` + query allocations( + $indexer: String! + $disputableEpoch: Int! + $minimumQueryFeesCollected: BigInt! + ) { + allocations( + where: { + indexer: $indexer + closedAtEpoch_lte: $disputableEpoch + queryFeesCollected_gte: $minimumQueryFeesCollected + status: Closed + } + first: 1000 + ) { + id + indexer { + id + } + queryFeesCollected + allocatedTokens + createdAtEpoch + closedAtEpoch + createdAtBlockHash + closedAtBlockHash + subgraphDeployment { + id + stakedTokens + signalledTokens + queryFeesAmount + } + } + } + `, + { + indexer: this.indexerOptions.address.toLocaleLowerCase(), + disputableEpoch, + minimumQueryFeesCollected: this.indexerOptions.rebateClaimThreshold.toString(), + }, + ) + + if (result.error) { + throw result.error + } + + const totalFees: BigNumber = result.data.allocations.reduce( + (total: BigNumber, rawAlloc: { queryFeesCollected: string }) => { + return total.add(BigNumber.from(rawAlloc.queryFeesCollected)) + }, + BigNumber.from(0), + ) + + const parsedAllocs: Allocation[] = + result.data.allocations.map(parseGraphQLAllocation) + + // If the total fees claimable do not meet the minimum required for batching, return an empty array + if ( + parsedAllocs.length > 0 && + totalFees.lt(this.indexerOptions.rebateClaimBatchThreshold) + ) { + this.logger.info( + `Allocation rebate batch value does not meet minimum for claiming`, + { + batchValueGRT: formatGRT(totalFees), + rebateClaimBatchThreshold: formatGRT( + this.indexerOptions.rebateClaimBatchThreshold, + ), + rebateClaimMaxBatchSize: this.indexerOptions.rebateClaimMaxBatchSize, + batchSize: parsedAllocs.length, + allocations: parsedAllocs.map((allocation) => { + return { + allocation: allocation.id, + deployment: allocation.subgraphDeployment.id.display, + createdAtEpoch: allocation.createdAtEpoch, + closedAtEpoch: allocation.closedAtEpoch, + createdAtBlockHash: allocation.createdAtBlockHash, + } + }), + }, + ) + return [] + } + // Otherwise return the allos for claiming since the batch meets the minimum + return parsedAllocs + } catch (error) { + const err = indexerError(IndexerErrorCode.IE011, error) + this.logger.error(INDEXER_ERROR_MESSAGES[IndexerErrorCode.IE011], { + err, + }) + throw err + } + } + async disputableAllocations( + currentEpoch: number, + deployments: SubgraphDeploymentID[], + minimumAllocation: number, + ): Promise { + const logger = this.logger.child({ component: 'POI Monitor' }) + if (!this.indexerOptions.poiDisputeMonitoring) { + logger.trace('POI monitoring disabled, skipping') + return Promise.resolve([]) + } + + logger.debug('Query network for any potentially disputable allocations') + + let dataRemaining = true + let allocations: Allocation[] = [] + + try { + const zeroPOI = utils.hexlify(Array(32).fill(0)) + const disputableEpoch = currentEpoch - this.indexerOptions.poiDisputableEpochs + let lastCreatedAt = 0 + while (dataRemaining) { + const result = await this.networkSubgraph.query( + gql` + query allocations( + $deployments: [String!]! + $minimumAllocation: Int! + $disputableEpoch: Int! + $zeroPOI: String! + $createdAt: Int! + ) { + allocations( + where: { + createdAt_gt: $createdAt + subgraphDeployment_in: $deployments + allocatedTokens_gt: $minimumAllocation + closedAtEpoch_gte: $disputableEpoch + status: Closed + poi_not: $zeroPOI + } + first: 1000 + orderBy: createdAt + orderDirection: asc + ) { + id + createdAt + indexer { + id + } + poi + allocatedTokens + createdAtEpoch + closedAtEpoch + closedAtBlockHash + subgraphDeployment { + id + stakedTokens + signalledTokens + queryFeesAmount + } + } + } + `, + { + deployments: deployments.map((subgraph) => subgraph.bytes32), + minimumAllocation, + disputableEpoch, + createdAt: lastCreatedAt, + zeroPOI, + }, + ) + + if (result.error) { + throw result.error + } + if (result.data.allocations.length == 0) { + dataRemaining = false + } else { + lastCreatedAt = result.data.allocations.slice(-1)[0].createdAt + const parsedResult: Allocation[] = + result.data.allocations.map(parseGraphQLAllocation) + allocations = allocations.concat(parsedResult) + } + } + + // Get the unique set of dispute epochs to reduce the work fetching epoch start block hashes in the next step + let disputableEpochs = await this.epochs([ + ...allocations.reduce((epochNumbers: Set, allocation: Allocation) => { + epochNumbers.add(allocation.closedAtEpoch) + epochNumbers.add(allocation.closedAtEpoch - 1) + return epochNumbers + }, new Set()), + ]) + + disputableEpochs = await Promise.all( + disputableEpochs.map(async (epoch: Epoch): Promise => { + // TODO: May need to retry or skip epochs where obtaining start block fails + epoch.startBlockHash = (await this.ethereum.getBlock(epoch.startBlock))?.hash + return epoch + }), + ) + + return await Promise.all( + allocations.map(async (allocation) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + allocation.closedAtEpochStartBlockHash = disputableEpochs.find( + (epoch) => epoch.id == allocation.closedAtEpoch, + )!.startBlockHash + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + allocation.previousEpochStartBlockHash = disputableEpochs.find( + (epoch) => epoch.id == allocation.closedAtEpoch - 1, + )!.startBlockHash + return allocation + }), + ) + } catch (error) { + const err = indexerError(IndexerErrorCode.IE037, error) + logger.error(INDEXER_ERROR_MESSAGES.IE037, { + err, + }) + throw err + } + } } diff --git a/packages/indexer-common/src/indexer-management/resolvers/actions.ts b/packages/indexer-common/src/indexer-management/resolvers/actions.ts index 8f6eb0b26..ffb77b6ea 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/actions.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/actions.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/ban-types */ - import { IndexerManagementResolverContext } from '../client' import { Logger } from '@graphprotocol/common-ts' import { @@ -12,11 +11,15 @@ import { ActionType, ActionUpdateInput, IndexerManagementModels, + Network, + NetworkMapped, OrderDirection, validateActionInputs, + validateNetworkIdentifier, } from '@graphprotocol/indexer-common' import { literal, Op, Transaction } from 'sequelize' import { ActionManager } from '../actions' +import groupBy from 'lodash.groupby' // Perform insert, update, or no-op depending on existing queue data // INSERT - No item in the queue yet targeting this deploymentID @@ -51,6 +54,7 @@ async function executeQueueOperation( (a) => a.deploymentID === action.deploymentID, ) if (duplicateActions.length === 0) { + logger.trace('Inserting Action in database', { action }) return [ await models.Action.create(action, { validate: true, @@ -144,13 +148,31 @@ export default { queueActions: async ( { actions }: { actions: ActionInput[] }, - { actionManager, logger, networkMonitor, models }: IndexerManagementResolverContext, + { actionManager, logger, multiNetworks, models }: IndexerManagementResolverContext, ): Promise => { logger.debug(`Execute 'queueActions' mutation`, { actions, }) - await validateActionInputs(actions, actionManager, networkMonitor) + if (!actionManager || !multiNetworks) { + throw Error('IndexerManagementClient must be in `network` mode to modify actions') + } + + // Sanitize protocol network identifier + actions.forEach((action) => { + try { + action.protocolNetwork = validateNetworkIdentifier(action.protocolNetwork) + } catch (e) { + throw Error(`Invalid value for the field 'protocolNetwork'. ${e}`) + } + }) + + // Let Network Monitors validate actions based on their protocol networks + await multiNetworks.mapNetworkMapped( + groupBy(actions, (action) => action.protocolNetwork), + (network: Network, actions: ActionInput[]) => + validateActionInputs(actions, network.networkMonitor), + ) const alreadyQueuedActions = await ActionManager.fetchActions(models, { status: ActionStatus.QUEUED, @@ -317,14 +339,26 @@ export default { executeApprovedActions: async ( _: unknown, { logger, actionManager }: IndexerManagementResolverContext, - ): Promise => { + ): Promise => { logger.debug(`Execute 'executeApprovedActions' mutation`) - return await actionManager.executeApprovedActions() + if (!actionManager) { + throw Error('IndexerManagementClient must be in `network` mode to modify actions') + } + + const result: NetworkMapped = await actionManager.multiNetworks.map( + (network: Network) => actionManager.executeApprovedActions(network), + ) + return Object.values(result).flat() }, } // Helper function to assess equality among a enqueued and a proposed actions function compareActions(enqueued: Action, proposed: ActionInput): boolean { + // actions are not the same if they target different protocol networks + if (enqueued.protocolNetwork !== proposed.protocolNetwork) { + return false + } + // actions are not the same if they target different deployments if (enqueued.deploymentID !== proposed.deploymentID) { return false diff --git a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts index 45be6febc..8fad58f4b 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts @@ -1,4 +1,8 @@ -import { NetworkMonitor, epochElapsedBlocks } from '@graphprotocol/indexer-common' +import { + NetworkMonitor, + epochElapsedBlocks, + Network, +} from '@graphprotocol/indexer-common' /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ /* eslint-disable @typescript-eslint/ban-types */ @@ -10,7 +14,6 @@ import { Address, formatGRT, Logger, - NetworkContracts, parseGRT, SubgraphDeploymentID, toAddress, @@ -26,18 +29,19 @@ import { IndexerManagementResolverContext, IndexingDecisionBasis, IndexingRuleAttributes, - IndexingStatusResolver, + GraphNode, NetworkSubgraph, ReallocateAllocationResult, SubgraphIdentifierType, - TransactionManager, uniqueAllocationID, } from '@graphprotocol/indexer-common' +import { extractNetwork } from './utils' interface AllocationFilter { status: 'active' | 'closed' | 'claimable' allocation: string | null subgraphDeployment: string | null + protocolNetwork: string | null } enum AllocationQuery { @@ -64,6 +68,7 @@ interface AllocationInfo { indexingRewards: string queryFeesCollected: string status: string + protocolNetwork: string } const ALLOCATION_QUERIES = { @@ -180,7 +185,6 @@ const ALLOCATION_QUERIES = { async function queryAllocations( logger: Logger, networkSubgraph: NetworkSubgraph, - contracts: NetworkContracts, variables: { indexer: Address disputableEpoch: number @@ -194,6 +198,7 @@ async function queryAllocations( maxAllocationEpochs: number blocksPerEpoch: number avgBlockTime: number + protocolNetwork: string }, ): Promise { logger.trace('Query Allocations', { @@ -281,6 +286,7 @@ async function queryAllocations( indexingRewards: allocation.indexingRewards, queryFeesCollected: allocation.queryFeesCollected, status: allocation.status, + protocolNetwork: context.protocolNetwork, } }, ) @@ -288,8 +294,7 @@ async function queryAllocations( async function resolvePOI( networkMonitor: NetworkMonitor, - transactionManager: TransactionManager, - indexingStatusResolver: IndexingStatusResolver, + graphNode: GraphNode, allocation: Allocation, poi: string | undefined, force: boolean, @@ -306,7 +311,7 @@ async function resolvePOI( return poi! case false: return ( - (await indexingStatusResolver.proofOfIndexing( + (await graphNode.proofOfIndexing( allocation.subgraphDeployment.id, await networkMonitor.fetchPOIBlockPointer(allocation), allocation.indexer, @@ -316,7 +321,7 @@ async function resolvePOI( break case false: { const currentEpochStartBlock = await networkMonitor.fetchPOIBlockPointer(allocation) - const generatedPOI = await indexingStatusResolver.proofOfIndexing( + const generatedPOI = await graphNode.proofOfIndexing( allocation.subgraphDeployment.id, currentEpochStartBlock, allocation.indexer, @@ -324,7 +329,7 @@ async function resolvePOI( switch (poi == generatedPOI) { case true: if (poi == undefined) { - const deploymentStatus = await indexingStatusResolver.indexingStatus([ + const deploymentStatus = await graphNode.indexingStatus([ allocation.subgraphDeployment.id, ]) throw indexerError( @@ -348,8 +353,8 @@ async function resolvePOI( } throw indexerError( IndexerErrorCode.IE068, - `User provided POI does not match reference fetched from the graph-node. Use '--force' to bypass this POI accuracy check. - POI: ${poi}, + `User provided POI does not match reference fetched from the graph-node. Use '--force' to bypass this POI accuracy check. + POI: ${poi}, referencePOI: ${generatedPOI}`, ) } @@ -360,46 +365,73 @@ async function resolvePOI( export default { allocations: async ( { filter }: { filter: AllocationFilter }, - { - address, - contracts, - logger, - networkSubgraph, - networkMonitor, - }: IndexerManagementResolverContext, - ): Promise => { + { multiNetworks, logger }: IndexerManagementResolverContext, + ): Promise => { logger.debug('Execute allocations() query', { filter, }) - - const allocations: AllocationInfo[] = [] - - const currentEpoch = await networkMonitor.networkCurrentEpoch() - const disputeEpochs = await contracts.staking.channelDisputeEpochs() - const variables = { - indexer: toAddress(address), - disputableEpoch: currentEpoch.epochNumber - disputeEpochs, - allocation: filter.allocation - ? filter.allocation === 'all' - ? null - : toAddress(filter.allocation) - : null, - status: filter.status, - } - const context = { - currentEpoch: currentEpoch.epochNumber, - currentEpochStartBlock: currentEpoch.startBlockNumber, - currentEpochElapsedBlocks: epochElapsedBlocks(currentEpoch), - latestBlock: currentEpoch.latestBlock, - maxAllocationEpochs: await contracts.staking.maxAllocationEpochs(), - blocksPerEpoch: (await contracts.epochManager.epochLength()).toNumber(), - avgBlockTime: 13_000, + if (!multiNetworks) { + throw Error( + 'IndexerManagementClient must be in `network` mode to fetch allocations', + ) } - allocations.push( - ...(await queryAllocations(logger, networkSubgraph, contracts, variables, context)), + const allocationsByNetwork = await multiNetworks.map( + async (network: Network): Promise => { + // Return early if a different protocol network is specifically requested + if ( + filter.protocolNetwork && + filter.protocolNetwork !== network.specification.networkIdentifier + ) { + return [] + } + + const { + networkMonitor, + networkSubgraph, + contracts, + specification: { + indexerOptions: { address }, + }, + } = network + + const [currentEpoch, disputeEpochs, maxAllocationEpochs, epochLength] = + await Promise.all([ + networkMonitor.networkCurrentEpoch(), + contracts.staking.channelDisputeEpochs(), + contracts.staking.maxAllocationEpochs(), + contracts.epochManager.epochLength(), + ]) + + const allocation = filter.allocation + ? filter.allocation === 'all' + ? null + : toAddress(filter.allocation) + : null + + const variables = { + indexer: toAddress(address), + disputableEpoch: currentEpoch.epochNumber - disputeEpochs, + allocation, + status: filter.status, + } + + const context = { + currentEpoch: currentEpoch.epochNumber, + currentEpochStartBlock: currentEpoch.startBlockNumber, + currentEpochElapsedBlocks: epochElapsedBlocks(currentEpoch), + latestBlock: currentEpoch.latestBlock, + maxAllocationEpochs, + blocksPerEpoch: epochLength.toNumber(), + avgBlockTime: 13000, + protocolNetwork: network.specification.networkIdentifier, + } + + return queryAllocations(logger, networkSubgraph, variables, context) + }, ) - return allocations + + return Object.values(allocationsByNetwork).flat() }, createAllocation: async ( @@ -407,18 +439,30 @@ export default { deployment, amount, indexNode, - }: { deployment: string; amount: string; indexNode: string | undefined }, - { - address, - contracts, - subgraphManager, - logger, - models, - networkMonitor, - transactionManager, - }: IndexerManagementResolverContext, + protocolNetwork, + }: { + deployment: string + amount: string + indexNode: string | undefined + protocolNetwork: string + }, + { multiNetworks, graphNode, logger, models }: IndexerManagementResolverContext, ): Promise => { - logger.debug('Execute createAllocation() mutation', { deployment, amount }) + logger.debug('Execute createAllocation() mutation', { + deployment, + amount, + protocolNetwork, + }) + if (!multiNetworks) { + throw Error( + 'IndexerManagementClient must be in `network` mode to fetch allocations', + ) + } + const network = extractNetwork(protocolNetwork, multiNetworks) + const networkMonitor = network.networkMonitor + const contracts = network.contracts + const transactionManager = network.transactionManager + const address = network.specification.indexerOptions.address const allocationAmount = parseGRT(amount) const subgraphDeployment = new SubgraphDeploymentID(deployment) @@ -476,12 +520,9 @@ export default { } // Ensure subgraph is deployed before allocating - await subgraphManager.ensure( - logger, - models, + await graphNode.ensure( `indexer-agent/${subgraphDeployment.ipfsHash.slice(-10)}`, subgraphDeployment, - indexNode, ) logger.debug('Obtain a unique Allocation ID') @@ -532,6 +573,7 @@ export default { amount: formatGRT(allocationAmount), allocation: allocationId, proof, + protocolNetwork, }) const receipt = await transactionManager.executeTransaction( @@ -618,6 +660,7 @@ export default { deployment, allocation: createEvent.allocationID, allocatedTokens: formatGRT(allocationAmount.toString()), + protocolNetwork, } } catch (error) { logger.error(`Failed to allocate`, { @@ -633,21 +676,29 @@ export default { allocation, poi, force, - }: { allocation: string; poi: string | undefined; force: boolean }, - { - contracts, - indexingStatusResolver, - logger, - models, - networkMonitor, - transactionManager, - receiptCollector, - }: IndexerManagementResolverContext, + protocolNetwork, + }: { + allocation: string + poi: string | undefined + force: boolean + protocolNetwork: string + }, + { graphNode, logger, models, multiNetworks }: IndexerManagementResolverContext, ): Promise => { logger.debug('Execute closeAllocation() mutation', { allocationID: allocation, poi: poi || 'none provided', }) + if (!multiNetworks) { + throw Error( + 'IndexerManagementClient must be in `network` mode to fetch allocations', + ) + } + const network = extractNetwork(protocolNetwork, multiNetworks) + const networkMonitor = network.networkMonitor + const contracts = network.contracts + const transactionManager = network.transactionManager + const receiptCollector = network.receiptCollector const allocationData = await networkMonitor.allocation(allocation) @@ -665,14 +716,7 @@ export default { ) } - poi = await resolvePOI( - networkMonitor, - transactionManager, - indexingStatusResolver, - allocationData, - poi, - force, - ) + poi = await resolvePOI(networkMonitor, graphNode, allocationData, poi, force) // Double-check whether the allocation is still active on chain, to // avoid unnecessary transactions. @@ -795,6 +839,7 @@ export default { allocatedTokens: formatGRT(closeAllocationEventLogs.tokens), indexingRewards: formatGRT(rewardsAssigned), receiptsWorthCollecting: isCollectingQueryFees, + protocolNetwork: network.specification.networkIdentifier, } } catch (error) { logger.error(error.toString()) @@ -808,17 +853,15 @@ export default { poi, amount, force, - }: { allocation: string; poi: string | undefined; amount: string; force: boolean }, - { - address, - contracts, - indexingStatusResolver, - logger, - models, - networkMonitor, - transactionManager, - receiptCollector, - }: IndexerManagementResolverContext, + protocolNetwork, + }: { + allocation: string + poi: string | undefined + amount: string + force: boolean + protocolNetwork: string + }, + { graphNode, logger, models, multiNetworks }: IndexerManagementResolverContext, ): Promise => { logger = logger.child({ component: 'reallocateAllocationResolver', @@ -831,6 +874,20 @@ export default { force, }) + if (!multiNetworks) { + throw Error( + 'IndexerManagementClient must be in `network` mode to fetch allocations', + ) + } + + // Obtain the Network object and its associated components and data + const network = extractNetwork(protocolNetwork, multiNetworks) + const networkMonitor = network.networkMonitor + const contracts = network.contracts + const transactionManager = network.transactionManager + const receiptCollector = network.receiptCollector + const address = network.specification.indexerOptions.address + const allocationAmount = parseGRT(amount) const activeAllocations = await networkMonitor.allocations(AllocationStatus.ACTIVE) @@ -864,8 +921,7 @@ export default { logger.debug('Resolving POI') const allocationPOI = await resolvePOI( networkMonitor, - transactionManager, - indexingStatusResolver, + graphNode, allocationData, poi, force, @@ -984,7 +1040,8 @@ export default { allocationData.id, allocationPOI, ), - await contracts.staking.populateTransaction.allocate( + await contracts.staking.populateTransaction.allocateFrom( + address, allocationData.subgraphDeployment.id.bytes32, allocationAmount, newAllocationId, @@ -1096,6 +1153,7 @@ export default { allocationAmount: allocationAmount.toString(), identifierType: SubgraphIdentifierType.DEPLOYMENT, decisionBasis: IndexingDecisionBasis.ALWAYS, + protocolNetwork, } as Partial await models.IndexingRule.upsert(indexingRule) @@ -1118,6 +1176,7 @@ export default { receiptsWorthCollecting: isCollectingQueryFees, createdAllocation: createAllocationEventLogs.allocationID, createdAllocationStake: formatGRT(createAllocationEventLogs.tokens), + protocolNetwork, } } catch (error) { logger.error(error.toString()) diff --git a/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts b/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts index 0e3aad244..c8a9393d1 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/cost-models.ts @@ -95,8 +95,12 @@ export default { setCostModel: async ( { costModel }: { deployment: string; costModel: GraphQLCostModel }, - { models, features, dai }: IndexerManagementResolverContext, + { models, multiNetworks, dai }: IndexerManagementResolverContext, ): Promise => { + if (!multiNetworks) { + throw Error('IndexerManagementClient must be in `network` mode to set cost models') + } + const update = parseGraphQLCostModel(costModel) // Validate cost model @@ -107,7 +111,13 @@ export default { } catch (err) { throw new Error(`Invalid cost model or variables: ${err.message}`) } - + const network = multiNetworks.inner['eip155:1'] + if (!network) { + throw new Error( + `Can't set cost model: Indexer Agent does not have Ethereum Mainnet network configured.`, + ) + } + const injectDai = !!network.specification.dai.inject const [model] = await models.CostModel.findOrBuild({ where: { deployment: update.deployment }, }) @@ -120,7 +130,7 @@ export default { // Update the model variables (fall back to current value if unchanged) let variables = update.variables || model.variables - if (features.injectDai) { + if (injectDai) { const oldDai = getVariable(model.variables, 'DAI') const newDai = getVariable(update.variables, 'DAI') diff --git a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts index 23fb4d3a0..0e96333b3 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts @@ -4,8 +4,8 @@ import geohash from 'ngeohash' import gql from 'graphql-tag' import { IndexerManagementResolverContext } from '../client' import { SubgraphDeploymentID } from '@graphprotocol/common-ts' -import { indexerError, IndexerErrorCode } from '@graphprotocol/indexer-common' - +import { indexerError, IndexerErrorCode, Network } from '@graphprotocol/indexer-common' +import { extractNetwork } from './utils' interface Test { test: (url: string) => string run: (url: string) => Promise @@ -57,15 +57,26 @@ const URL_VALIDATION_TEST: Test = { export default { indexerRegistration: async ( - _: {}, - { address, contracts }: IndexerManagementResolverContext, + { protocolNetwork: unvalidateProtocolNetwork }: { protocolNetwork: string }, + { multiNetworks }: IndexerManagementResolverContext, ): Promise => { + if (!multiNetworks) { + throw Error( + 'IndexerManagementClient must be in `network` mode to fetch indexer registration information', + ) + } + + const network = extractNetwork(unvalidateProtocolNetwork, multiNetworks) + const protocolNetwork = network.specification.networkIdentifier + const address = network.specification.indexerOptions.address + const contracts = network.contracts const registered = await contracts.serviceRegistry.isRegistered(address) if (registered) { const service = await contracts.serviceRegistry.services(address) return { address, + protocolNetwork, url: service.url, location: geohash.decode(service.geohash), registered, @@ -76,6 +87,7 @@ export default { address, url: null, registered, + protocolNetwork, location: null, __typename: 'IndexerRegistration', } @@ -84,9 +96,9 @@ export default { indexerDeployments: async ( _: {}, - { indexingStatusResolver }: IndexerManagementResolverContext, + { graphNode }: IndexerManagementResolverContext, ): Promise => { - const result = await indexingStatusResolver.indexingStatus([]) + const result = await graphNode.indexingStatus([]) return result.map((status) => ({ ...status, subgraphDeployment: status.subgraphDeployment.ipfsHash, @@ -94,11 +106,20 @@ export default { }, indexerAllocations: async ( - _: {}, - { address, networkSubgraph, logger }: IndexerManagementResolverContext, + { protocolNetwork }: { protocolNetwork: string }, + { multiNetworks, logger }: IndexerManagementResolverContext, ): Promise => { + if (!multiNetworks) { + throw Error( + 'IndexerManagementClient must be in `network` mode to fetch indexer allocations', + ) + } + + const network = extractNetwork(protocolNetwork, multiNetworks) + const address = network.specification.indexerOptions.address + try { - const result = await networkSubgraph.query( + const result = await network.networkSubgraph.query( gql` query allocations($indexer: String!) { allocations( @@ -130,6 +151,7 @@ export default { .ipfsHash, signalledTokens: allocation.subgraphDeployment.signalledTokens, stakedTokens: allocation.subgraphDeployment.stakedTokens, + protocolNetwork: network.specification.networkIdentifier, })) } catch (error) { const err = indexerError(IndexerErrorCode.IE010, error) @@ -141,98 +163,130 @@ export default { }, indexerEndpoints: async ( - _: {}, - { address, contracts, logger }: IndexerManagementResolverContext, - ): Promise => { - const endpoints = { - service: { - url: null as string | null, - healthy: false, - tests: [] as TestResult[], - }, - status: { - url: null as string | null, - healthy: false, - tests: [] as TestResult[], - }, + { protocolNetwork }: { protocolNetwork: string }, + { multiNetworks, logger }: IndexerManagementResolverContext, + ): Promise => { + if (!multiNetworks) { + throw Error( + 'IndexerManagementClient must be in `network` mode to fetch indexer endpoints', + ) } + const endpoints: Endpoints[] = [] + await multiNetworks.map(async (network: Network) => { + try { + const networkEndpoints = await endpointForNetwork(network) + endpoints.push(networkEndpoints) + } catch (err) { + // Ignore endpoints for this network + logger?.warn(`Failed to detect service endpoints for network`, { + err, + protocolNetwork: network.specification.networkIdentifier, + }) + } + }) + return endpoints + }, +} - try { - const service = await contracts.serviceRegistry.services(address) +interface Endpoint { + url: string | null + healthy: boolean + protocolNetwork: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any + tests: any[] +} - if (service) { - { - const { url, tests, ok } = await testURL(service.url, [ - URL_VALIDATION_TEST, - { - test: (url) => `http get ${url}`, - run: async (url) => { - const response = await fetch(url) - if (!response.ok) { - throw new Error( - `Returned status ${response.status}: ${ - response.body ? response.body.toString() : 'No data returned' - }`, - ) - } - }, - possibleActions: (url) => [ - `Make sure ${url} can be resolved and reached from this machine`, - `Make sure the port of ${url} is set up correctly`, - `Make sure the test command returns an HTTP status code < 400`, - ], - }, - ]) - - endpoints.service.url = url - endpoints.service.healthy = ok - endpoints.service.tests = tests - } +interface Endpoints { + service: Endpoint + status: Endpoint +} + +function defaultEndpoint(protocolNetwork: string): Endpoint { + return { + url: null as string | null, + healthy: false, + protocolNetwork, + tests: [] as TestResult[], + } +} +function defaultEndpoints(protocolNetwork: string): Endpoints { + return { + service: defaultEndpoint(protocolNetwork), + status: defaultEndpoint(protocolNetwork), + } +} +async function endpointForNetwork(network: Network): Promise { + const contracts = network.contracts + const address = network.specification.indexerOptions.address + const endpoints = defaultEndpoints(network.specification.networkIdentifier) + const service = await contracts.serviceRegistry.services(address) + if (service) { + { + const { url, tests, ok } = await testURL(service.url, [ + URL_VALIDATION_TEST, { - const statusURL = endpoints.service.url.endsWith('/') - ? endpoints.service.url.substring(0, endpoints.service.url.length - 1) + - '/status' - : endpoints.service.url + '/status' - - const { url, tests, ok } = await testURL(statusURL, [ - URL_VALIDATION_TEST, - { - test: (url) => `http post ${url} query="{ indexingStatuses { subgraph } }"`, - run: async (url) => { - const response = await fetch(url, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ query: '{ indexingStatuses { subgraph } }' }), - }) - if (!response.ok) { - throw new Error( - `Returned status ${response.status}: ${ - response.body ? response.body.toString() : 'No data returned' - }`, - ) - } - }, - possibleActions: (url) => [ - `Make sure ${url} can be reached from this machine`, - `Make sure the port of ${url} is set up correctly`, - `Make sure ${url} is the /status endpoint of indexer-service`, - `Make sure the test command returns an HTTP status code < 400`, - `Make sure the test command returns a valid GraphQL response`, - ], - }, - ]) - - endpoints.status.url = url - endpoints.status.healthy = ok - endpoints.status.tests = tests - } - } - } catch (err) { - // Return empty endpoints - logger?.warn(`Failed to detect service endpoints`, { err }) + test: (url) => `http get ${url}`, + run: async (url) => { + const response = await fetch(url) + if (!response.ok) { + throw new Error( + `Returned status ${response.status}: ${ + response.body ? response.body.toString() : 'No data returned' + }`, + ) + } + }, + possibleActions: (url) => [ + `Make sure ${url} can be resolved and reached from this machine`, + `Make sure the port of ${url} is set up correctly`, + `Make sure the test command returns an HTTP status code < 400`, + ], + }, + ]) + + endpoints.service.url = url + endpoints.service.healthy = ok + endpoints.service.tests = tests } - return endpoints - }, + { + const statusURL = endpoints.service.url.endsWith('/') + ? endpoints.service.url.substring(0, endpoints.service.url.length - 1) + '/status' + : endpoints.service.url + '/status' + + const { url, tests, ok } = await testURL(statusURL, [ + URL_VALIDATION_TEST, + { + test: (url) => `http post ${url} query="{ indexingStatuses { subgraph } }"`, + run: async (url) => { + const response = await fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ query: '{ indexingStatuses { subgraph } }' }), + }) + if (!response.ok) { + throw new Error( + `Returned status ${response.status}: ${ + response.body ? response.body.toString() : 'No data returned' + }`, + ) + } + }, + possibleActions: (url) => [ + `Make sure ${url} can be reached from this machine`, + `Make sure the port of ${url} is set up correctly`, + `Make sure ${url} is the /status endpoint of indexer-service`, + `Make sure the test command returns an HTTP status code < 400`, + `Make sure the test command returns a valid GraphQL response`, + ], + }, + ]) + + endpoints.status.url = url + endpoints.status.healthy = ok + endpoints.status.tests = tests + } + } + return endpoints } diff --git a/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts b/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts index b333c3804..f9d348efd 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/indexing-rules.ts @@ -3,14 +3,18 @@ import { IndexerManagementModels, INDEXING_RULE_GLOBAL, + IndexingRuleIdentifier, IndexingRuleCreationAttributes, } from '../models' import { IndexerManagementDefaults, IndexerManagementResolverContext } from '../client' import { Transaction } from 'sequelize/types' -import { fetchIndexingRules, processIdentifier } from '@graphprotocol/indexer-common' +import { fetchIndexingRules } from '../rules' +import { processIdentifier } from '../../' +import { validateNetworkIdentifier } from '../../parsers' +import groupBy from 'lodash.groupby' const resetGlobalRule = async ( - identifier: string, + ruleIdentifier: IndexingRuleIdentifier, defaults: IndexerManagementDefaults['globalIndexingRule'], models: IndexerManagementModels, transaction: Transaction, @@ -18,8 +22,8 @@ const resetGlobalRule = async ( await models.IndexingRule.upsert( { ...defaults, + ...ruleIdentifier, allocationAmount: defaults.allocationAmount.toString(), - identifier, }, { transaction }, ) @@ -27,12 +31,24 @@ const resetGlobalRule = async ( export default { indexingRule: async ( - { identifier, merged }: { identifier: string; merged: boolean }, + { + identifier: indexingRuleIdentifier, + merged, + }: { identifier: IndexingRuleIdentifier; merged: boolean }, { models }: IndexerManagementResolverContext, ): Promise => { - ;[identifier] = await processIdentifier(identifier, { all: false, global: true }) + const [identifier] = await processIdentifier(indexingRuleIdentifier.identifier, { + all: false, + global: true, + }) + + // Sanitize protocol network identifier + const protocolNetwork = validateNetworkIdentifier( + indexingRuleIdentifier.protocolNetwork, + ) + const rule = await models.IndexingRule.findOne({ - where: { identifier }, + where: { identifier, protocolNetwork }, }) if (rule && merged) { return rule.mergeToGraphQL( @@ -46,10 +62,17 @@ export default { }, indexingRules: async ( - { merged }: { merged: boolean }, + { + merged, + protocolNetwork: uncheckedProtocolNetwork, + }: { merged: boolean; protocolNetwork: string | undefined }, { models }: IndexerManagementResolverContext, ): Promise => { - return await fetchIndexingRules(models, merged) + // Convert the input `protocolNetwork` value to a CAIP2-ID + const protocolNetwork = uncheckedProtocolNetwork + ? validateNetworkIdentifier(uncheckedProtocolNetwork) + : undefined + return await fetchIndexingRules(models, merged, protocolNetwork) }, setIndexingRule: async ( @@ -60,40 +83,57 @@ export default { throw Error('Cannot set indexingRule without identifier') } + if (!rule.protocolNetwork) { + throw Error("Cannot set an indexing rule without the field 'protocolNetwork'") + } else { + try { + rule.protocolNetwork = validateNetworkIdentifier(rule.protocolNetwork) + } catch (e) { + throw Error(`Invalid value for the field 'protocolNetwork'. ${e}`) + } + } + const [identifier] = await processIdentifier(rule.identifier, { all: false, global: true, }) rule.identifier = identifier - await models.IndexingRule.upsert(rule) - - // Since upsert succeeded, we _must_ have a rule - const updatedRule = await models.IndexingRule.findOne({ - where: { identifier: rule.identifier }, - }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return updatedRule!.toGraphQL() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [updatedRule, _created] = await models.IndexingRule.upsert(rule) + return updatedRule.toGraphQL() }, deleteIndexingRule: async ( - { identifier }: { identifier: string }, + { identifier: indexingRuleIdentifier }: { identifier: IndexingRuleIdentifier }, { models, defaults }: IndexerManagementResolverContext, ): Promise => { - ;[identifier] = await processIdentifier(identifier, { all: false, global: true }) + const [identifier] = await processIdentifier(indexingRuleIdentifier.identifier, { + all: false, + global: true, + }) + + // Sanitize protocol network identifier + const protocolNetwork = validateNetworkIdentifier( + indexingRuleIdentifier.protocolNetwork, + ) + + const validatedRuleIdentifier = { + protocolNetwork, + identifier, + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return await models.IndexingRule.sequelize!.transaction(async (transaction) => { const numDeleted = await models.IndexingRule.destroy({ - where: { - identifier, - }, + where: validatedRuleIdentifier, transaction, }) // Reset the global rule - if (identifier === 'global') { + if (validatedRuleIdentifier.identifier === 'global') { await resetGlobalRule( - identifier, + validatedRuleIdentifier, defaults.globalIndexingRule, models, transaction, @@ -105,32 +145,55 @@ export default { }, deleteIndexingRules: async ( - { identifiers }: { identifiers: string[] }, + { identifiers: indexingRuleIdentifiers }: { identifiers: IndexingRuleIdentifier[] }, { models, defaults }: IndexerManagementResolverContext, ): Promise => { - identifiers = await Promise.all( - identifiers.map( - async (identifier) => - ( - await processIdentifier(identifier, { all: false, global: true }) - )[0], - ), - ) + let totalNumDeleted = 0 - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return await models.IndexingRule.sequelize!.transaction(async (transaction) => { - const numDeleted = await models.IndexingRule.destroy({ - where: { - identifier: identifiers, - }, - transaction, - }) + // Sanitize protocol network identifiers + for (const identifier of indexingRuleIdentifiers) { + identifier.protocolNetwork = validateNetworkIdentifier(identifier.protocolNetwork) + } - if (identifiers.includes('global')) { - await resetGlobalRule('global', defaults.globalIndexingRule, models, transaction) - } + // Batch deletions by the `IndexingRuleIdentifier.protocolNetwork` attribute . + const batches = groupBy( + indexingRuleIdentifiers, + (x: IndexingRuleIdentifier) => x.protocolNetwork, + ) - return numDeleted > 0 - }) + for (const protocolNetwork in batches) { + const batch = batches[protocolNetwork] + const identifiers = await Promise.all( + batch.map( + async ({ identifier }: IndexingRuleIdentifier) => + ( + await processIdentifier(identifier, { all: false, global: true }) + )[0], + ), + ) + // Execute deletion batch + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + await models.IndexingRule.sequelize!.transaction(async (transaction) => { + const numDeleted = await models.IndexingRule.destroy({ + where: { + identifier: identifiers, + protocolNetwork: protocolNetwork, + }, + transaction, + }) + + if (identifiers.includes('global')) { + const globalIdentifier = { identifier: 'global', protocolNetwork } + await resetGlobalRule( + globalIdentifier, + defaults.globalIndexingRule, + models, + transaction, + ) + } + totalNumDeleted += numDeleted + }) + } + return totalNumDeleted > 0 }, } diff --git a/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts b/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts index 0556858a3..5c916d183 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/poi-disputes.ts @@ -1,35 +1,47 @@ /* eslint-disable @typescript-eslint/ban-types */ -import { POIDispute, POIDisputeCreationAttributes } from '../models' +import { POIDispute, POIDisputeIdentifier, POIDisputeCreationAttributes } from '../models' import { IndexerManagementResolverContext } from '../client' -import { Op } from 'sequelize' +import { validateNetworkIdentifier } from '../../parsers' +import { Op, WhereOptions } from 'sequelize' +import groupBy from 'lodash.groupby' export default { dispute: async ( - { allocationID }: { allocationID: number }, + { identifier }: { identifier: POIDisputeIdentifier }, { models }: IndexerManagementResolverContext, ): Promise => { const dispute = await models.POIDispute.findOne({ - where: { allocationID }, + where: { ...identifier }, }) return dispute?.toGraphQL() || dispute }, disputes: async ( - { status, minClosedEpoch }: { status: string; minClosedEpoch: number }, + { + status, + minClosedEpoch, + protocolNetwork: uncheckedProtocolNetwork, + }: { status: string; minClosedEpoch: number; protocolNetwork: string | undefined }, { models }: IndexerManagementResolverContext, ): Promise => { + // Sanitize protocol network identifier + const protocolNetwork = uncheckedProtocolNetwork + ? validateNetworkIdentifier(uncheckedProtocolNetwork) + : undefined + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const sqlAndExpression: WhereOptions = [ + { status }, + { closedEpoch: { [Op.gte]: minClosedEpoch } }, + ] + + if (protocolNetwork) { + sqlAndExpression.push({ protocolNetwork }) + } + const disputes = await models.POIDispute.findAll({ - where: { - [Op.and]: [ - { status }, - { - closedEpoch: { - [Op.gte]: minClosedEpoch, - }, - }, - ], - }, + where: { [Op.and]: sqlAndExpression }, order: [['allocationAmount', 'DESC']], }) return disputes.map((dispute) => dispute.toGraphQL()) @@ -39,6 +51,14 @@ export default { { disputes }: { disputes: POIDisputeCreationAttributes[] }, { models }: IndexerManagementResolverContext, ): Promise => { + // Sanitize protocol network identifiers + for (const dispute of disputes) { + if (!dispute.protocolNetwork) { + throw new Error(`Dispute is missing the attribute 'protocolNetwork'`) + } + dispute.protocolNetwork = validateNetworkIdentifier(dispute.protocolNetwork) + } + const createdDisputes = await models.POIDispute.bulkCreate(disputes, { returning: true, validate: true, @@ -52,15 +72,32 @@ export default { }, deleteDisputes: async ( - { allocationIDs }: { allocationIDs: string[] }, + { identifiers }: { identifiers: POIDisputeIdentifier[] }, { models }: IndexerManagementResolverContext, ): Promise => { - const numDeleted = await models.POIDispute.destroy({ - where: { - allocationID: allocationIDs, - }, - force: true, - }) - return numDeleted + let totalNumDeleted = 0 + + // Sanitize protocol network identifiers + for (const identifier of identifiers) { + if (!identifier.protocolNetwork) { + throw new Error(`Dispute is missing the attribute 'protocolNetwork'`) + } + identifier.protocolNetwork = validateNetworkIdentifier(identifier.protocolNetwork) + } + + // Batch by protocolNetwork + const batches = groupBy(identifiers, (x: POIDisputeIdentifier) => x.protocolNetwork) + + for (const protocolNetwork in batches) { + const batch = batches[protocolNetwork] + const numDeleted = await models.POIDispute.destroy({ + where: { + allocationID: batch.map((x) => x.allocationID), + }, + force: true, + }) + totalNumDeleted += numDeleted + } + return totalNumDeleted }, } diff --git a/packages/indexer-common/src/indexer-management/resolvers/utils.ts b/packages/indexer-common/src/indexer-management/resolvers/utils.ts new file mode 100644 index 000000000..baa061fdf --- /dev/null +++ b/packages/indexer-common/src/indexer-management/resolvers/utils.ts @@ -0,0 +1,26 @@ +import { + MultiNetworks, + Network, + validateNetworkIdentifier, +} from '@graphprotocol/indexer-common' + +export function extractNetwork( + unvalidatedNetworkIdentifier: string, + multiNetworks: MultiNetworks, +): Network { + let networkIdentifier: string + try { + networkIdentifier = validateNetworkIdentifier(unvalidatedNetworkIdentifier) + } catch (parseError) { + throw new Error( + `Invalid protocol network identifier: '${unvalidatedNetworkIdentifier}'. Error: ${parseError}`, + ) + } + const network = multiNetworks.inner[networkIdentifier] + if (!network) { + throw new Error( + `Could not find a configured protocol network named ${networkIdentifier}`, + ) + } + return network +} diff --git a/packages/indexer-common/src/indexer-management/rules.ts b/packages/indexer-common/src/indexer-management/rules.ts index 4020b1567..656dd532b 100644 --- a/packages/indexer-common/src/indexer-management/rules.ts +++ b/packages/indexer-common/src/indexer-management/rules.ts @@ -6,22 +6,33 @@ import { IndexingRuleAttributes, } from '@graphprotocol/indexer-common' import { parseIndexingRule } from '../rules' +import groupBy from 'lodash.groupby' export const fetchIndexingRules = async ( models: IndexerManagementModels, merged: boolean, + protocolNetwork?: string, ): Promise => { + // If unspecified, select indexing rules from all protocol networks + const whereClause = protocolNetwork ? { protocolNetwork } : {} const rules = await models.IndexingRule.findAll({ + where: whereClause, order: [ ['identifierType', 'DESC'], ['identifier', 'ASC'], ], }) if (merged) { - const global = await models.IndexingRule.findOne({ - where: { identifier: INDEXING_RULE_GLOBAL }, - }) - return rules.map((rule) => rule.mergeGlobal(global)) + // Merge rules by protocol network + return Object.entries(groupBy(rules, (rule) => rule.protocolNetwork)) + .map(([protocolNetwork, rules]) => { + const global = rules.find((rule) => rule.identifier === INDEXING_RULE_GLOBAL) + if (!global) { + throw Error(`Could not find global rule for network '${protocolNetwork}'`) + } + return rules.map((rule) => rule.mergeGlobal(global)) + }) + .flat() } else { return rules } @@ -33,12 +44,8 @@ export const upsertIndexingRule = async ( newRule: Partial, ): Promise => { const indexingRule = parseIndexingRule(newRule) - await models.IndexingRule.upsert(indexingRule) - - // Since upsert succeeded, we _must_ have a rule - const updatedRule = await models.IndexingRule.findOne({ - where: { identifier: indexingRule.identifier }, - }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [updatedRule, _created] = await models.IndexingRule.upsert(indexingRule) logger.debug( `DecisionBasis.${indexingRule.decisionBasis} rule merged into indexing rules`, diff --git a/packages/indexer-common/src/indexer-management/types.ts b/packages/indexer-common/src/indexer-management/types.ts index b0ba2bdcb..5630b06f2 100644 --- a/packages/indexer-common/src/indexer-management/types.ts +++ b/packages/indexer-common/src/indexer-management/types.ts @@ -1,6 +1,12 @@ -import { Address, SubgraphDeploymentID, toAddress } from '@graphprotocol/common-ts' +import { + Address, + Logger, + SubgraphDeploymentID, + toAddress, +} from '@graphprotocol/common-ts' import { BigNumber } from 'ethers' import { Allocation } from '../allocations' +import { GraphNode } from '../graph-node' import { SubgraphDeployment } from '../types' export interface CreateAllocationResult { @@ -10,6 +16,7 @@ export interface CreateAllocationResult { allocation: string deployment: string allocatedTokens: string + protocolNetwork: string } export interface CloseAllocationResult { @@ -20,6 +27,7 @@ export interface CloseAllocationResult { allocatedTokens: string indexingRewards: string receiptsWorthCollecting: boolean + protocolNetwork: string } export interface ReallocateAllocationResult { @@ -31,12 +39,14 @@ export interface ReallocateAllocationResult { receiptsWorthCollecting: boolean createdAllocation: string createdAllocationStake: string + protocolNetwork: string } export interface ActionFailure { actionID: number transactionID?: string failureReason: string + protocolNetwork: string } /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -52,17 +62,21 @@ export type AllocationResult = /* eslint-disable @typescript-eslint/no-explicit-any */ export const parseGraphQLSubgraphDeployment = ( subgraphDeployment: any, + protocolNetwork: string, ): SubgraphDeployment => ({ id: new SubgraphDeploymentID(subgraphDeployment.id), deniedAt: subgraphDeployment.deniedAt, stakedTokens: BigNumber.from(subgraphDeployment.stakedTokens), signalledTokens: BigNumber.from(subgraphDeployment.signalledTokens), queryFeesAmount: BigNumber.from(subgraphDeployment.queryFeesAmount), - activeAllocations: subgraphDeployment.indexerAllocations.length, + protocolNetwork, }) /* eslint-disable @typescript-eslint/no-explicit-any */ -export const parseGraphQLAllocation = (allocation: any): Allocation => ({ +export const parseGraphQLAllocation = ( + allocation: any, + protocolNetwork: string, +): Allocation => ({ // Ensure the allocation ID (an address) is checksummed id: toAddress(allocation.id), status: allocation.status, @@ -72,9 +86,7 @@ export const parseGraphQLAllocation = (allocation: any): Allocation => ({ stakedTokens: BigNumber.from(allocation.subgraphDeployment.stakedTokens), signalledTokens: BigNumber.from(allocation.subgraphDeployment.signalledTokens), queryFeesAmount: BigNumber.from(allocation.subgraphDeployment.queryFeesAmount), - activeAllocations: allocation.subgraphDeployment.indexerAllocations - ? allocation.subgraphDeployment.indexerAllocations.length - : 0, + protocolNetwork, }, indexer: toAddress(allocation.indexer.id), allocatedTokens: BigNumber.from(allocation.allocatedTokens), @@ -154,7 +166,7 @@ export function epochElapsedBlocks(networkEpoch: NetworkEpoch): number { return networkEpoch.startBlockNumber - networkEpoch.latestBlock } -const Caip2ByChainAlias: { [key: string]: string } = { +export const Caip2ByChainAlias: { [key: string]: string } = { mainnet: 'eip155:1', goerli: 'eip155:5', gnosis: 'eip155:100', @@ -168,7 +180,7 @@ const Caip2ByChainAlias: { [key: string]: string } = { fantom: 'eip155:250', } -const Caip2ByChainId: { [key: number]: string } = { +export const Caip2ByChainId: { [key: number]: string } = { 1: 'eip155:1', 5: 'eip155:5', 100: 'eip155:100', @@ -191,9 +203,14 @@ export function resolveChainId(key: number | string): string { if (chainId !== undefined) { return chainId } - } else { - // If chain is a string, it must be a chain alias - const chainId = Caip2ByChainAlias[key] + } else if (typeof key === 'string') { + const splitKey = key.split(':') + let chainId + if (splitKey.length === 2) { + chainId = Caip2ByChainId[+splitKey[1]] + } else { + chainId = Caip2ByChainAlias[key] + } if (chainId !== undefined) { return chainId } @@ -217,3 +234,52 @@ export function resolveChainAlias(id: string): string { ) } } + +// Compares the CAIP-2 chain ID between the Ethereum provider and the Network Subgraph and requires +// they are equal. +export async function validateProviderNetworkIdentifier( + providerNetworkIdentifier: string, + networkSubgraphDeploymentIpfsHash: string, + graphNode: GraphNode, + logger: Logger, +) { + const subgraphNetworkId = new SubgraphDeploymentID(networkSubgraphDeploymentIpfsHash) + const { network: subgraphNetworkChainName } = await graphNode.subgraphFeatures( + subgraphNetworkId, + ) + + if (!subgraphNetworkChainName) { + // This is unlikely to happen because we expect that the Network Subgraph manifest is valid. + const errorMsg = 'Failed to fetch the networkId for the Network Subgraph' + logger.error(errorMsg, { networkSubgraphDeploymentIpfsHash }) + throw new Error(errorMsg) + } + + const providerChainId = resolveChainId(providerNetworkIdentifier) + const networkSubgraphChainId = resolveChainId(subgraphNetworkChainName) + if (providerChainId !== networkSubgraphChainId) { + const errorMsg = + 'The configured provider and the Network Subgraph have different CAIP-2 chain IDs. ' + + 'Please ensure that both Network Subgraph and the Ethereum provider are correctly configured.' + logger.error(errorMsg, { + networkSubgraphDeploymentIpfsHash, + networkSubgraphChainId, + providerChainId, + }) + throw new Error(errorMsg) + } +} + +// Convenience function to check if a given network identifier is a supported Layer-1 protocol network +export function networkIsL1(networkIdentifier: string): boolean { + // Normalize network identifier + networkIdentifier = resolveChainId(networkIdentifier) + return networkIdentifier === 'eip155:1' || networkIdentifier === 'eip155:5' +} + +// Convenience function to check if a given network identifier is a supported Layer-2 protocol network +export function networkIsL2(networkIdentifier: string): boolean { + // Normalize network identifier + networkIdentifier = resolveChainId(networkIdentifier) + return networkIdentifier === 'eip155:42161' || networkIdentifier === 'eip155:421613' +} From 9798ba1f61a66e34115cd18f8c10ea92547bf7c1 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:07:16 -0300 Subject: [PATCH 06/16] common: unit tests --- .../invalid-base58.yml | 34 ++ .../invalid-epoch-subgraph.yml | 35 ++ .../invalid-extra-field.yml | 36 ++ .../invalid-missing-field.yml | 35 ++ .../invalid-network-identifier.yml | 35 ++ .../valid-missing.yml | 30 ++ .../network-specification-files/valid.yml | 34 ++ .../__tests__/network-specification.test.ts | 96 ++++++ .../__tests__/allocations.test.ts | 169 +++++++++ .../__tests__/{helpers.ts => helpers.test.ts} | 85 ++++- .../resolvers/{actions.ts => actions.test.ts} | 326 +++--------------- .../{cost-models.ts => cost-models.test.ts} | 106 ++---- ...dexing-rules.ts => indexing-rules.test.ts} | 260 +++++++++----- .../{poi-disputes.ts => poi-disputes.test.ts} | 192 ++++++----- .../src/indexer-management/__tests__/util.ts | 183 ++++++++++ 15 files changed, 1117 insertions(+), 539 deletions(-) create mode 100644 packages/indexer-common/src/__tests__/network-specification-files/invalid-base58.yml create mode 100644 packages/indexer-common/src/__tests__/network-specification-files/invalid-epoch-subgraph.yml create mode 100644 packages/indexer-common/src/__tests__/network-specification-files/invalid-extra-field.yml create mode 100644 packages/indexer-common/src/__tests__/network-specification-files/invalid-missing-field.yml create mode 100644 packages/indexer-common/src/__tests__/network-specification-files/invalid-network-identifier.yml create mode 100644 packages/indexer-common/src/__tests__/network-specification-files/valid-missing.yml create mode 100644 packages/indexer-common/src/__tests__/network-specification-files/valid.yml create mode 100644 packages/indexer-common/src/__tests__/network-specification.test.ts create mode 100644 packages/indexer-common/src/indexer-management/__tests__/allocations.test.ts rename packages/indexer-common/src/indexer-management/__tests__/{helpers.ts => helpers.test.ts} (79%) rename packages/indexer-common/src/indexer-management/__tests__/resolvers/{actions.ts => actions.test.ts} (74%) rename packages/indexer-common/src/indexer-management/__tests__/resolvers/{cost-models.ts => cost-models.test.ts} (91%) rename packages/indexer-common/src/indexer-management/__tests__/resolvers/{indexing-rules.ts => indexing-rules.test.ts} (76%) rename packages/indexer-common/src/indexer-management/__tests__/resolvers/{poi-disputes.ts => poi-disputes.test.ts} (69%) create mode 100644 packages/indexer-common/src/indexer-management/__tests__/util.ts diff --git a/packages/indexer-common/src/__tests__/network-specification-files/invalid-base58.yml b/packages/indexer-common/src/__tests__/network-specification-files/invalid-base58.yml new file mode 100644 index 000000000..9252faabb --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification-files/invalid-base58.yml @@ -0,0 +1,34 @@ +networkIdentifier: mainnet +gateway: + url: http://gateway +indexerOptions: + address: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" + mnemonic: word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport + url: http://indexer + geoCoordinates: [25.1, -71.2] + restakeRewards: true + rebateClaimThreshold: 400 + rebateClaimBatchThreshold: 5000 + rebateClaimMaxBatchSize: 10 + poiDisputeMonitoring: false + poiDisputableEpochs: 5 + defaultAllocationAmount: 0.05 + voucherRedemptionThreshold: 2 + voucherRedemptionBatchThreshold: 2000 + voucherRedemptionMaxBatchSize: 15 + allocationManagementMode: "auto" + autoAllocationMinBatchSize: 20 +transactionMonitoring: + gasIncreaseTimeout: 10 + gasIncreaseFactor: 10 + baseFeePerGasMax: 10 + maxTransactionAttempts: 10 +subgraphs: + networkSubgraph: + deployment: abcdefg # <-- invalid base58 field + epochSubgraph: + url: http://subgraph +networkProvider: + url: http://provider +dai: + contractAddress: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" diff --git a/packages/indexer-common/src/__tests__/network-specification-files/invalid-epoch-subgraph.yml b/packages/indexer-common/src/__tests__/network-specification-files/invalid-epoch-subgraph.yml new file mode 100644 index 000000000..8120d9563 --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification-files/invalid-epoch-subgraph.yml @@ -0,0 +1,35 @@ +networkIdentifier: mainnet +gateway: + url: http://gateway +indexerOptions: + address: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" + mnemonic: word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport + url: http://indexer + geoCoordinates: [25.1, -71.2] + restakeRewards: true + rebateClaimThreshold: 400 + rebateClaimBatchThreshold: 5000 + rebateClaimMaxBatchSize: 10 + poiDisputeMonitoring: false + poiDisputableEpochs: 5 + defaultAllocationAmount: 0.05 + voucherRedemptionThreshold: 2 + voucherRedemptionBatchThreshold: 2000 + voucherRedemptionMaxBatchSize: 15 + allocationManagementMode: "auto" + autoAllocationMinBatchSize: 20 +transactionMonitoring: + gasIncreaseTimeout: 10 + gasIncreaseFactor: 10 + baseFeePerGasMax: 10 + maxTransactionAttempts: 10 +subgraphs: + networkSubgraph: + url: http://subgraph + epochSubgraph: + deployment: QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB + # <-- Missing `url` field +networkProvider: + url: http://provider +dai: + contractAddress: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" diff --git a/packages/indexer-common/src/__tests__/network-specification-files/invalid-extra-field.yml b/packages/indexer-common/src/__tests__/network-specification-files/invalid-extra-field.yml new file mode 100644 index 000000000..cbdf0ae1d --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification-files/invalid-extra-field.yml @@ -0,0 +1,36 @@ +networkIdentifier: mainnet +gateway: + url: http://gateway +indexerOptions: + address: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" + mnemonic: word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport + url: http://indexer + geoCoordinates: [25.1, -71.2] + restakeRewards: true + rebateClaimThreshold: 400 + rebateClaimBatchThreshold: 5000 + rebateClaimMaxBatchSize: 10 + poiDisputeMonitoring: false + poiDisputableEpochs: 5 + defaultAllocationAmount: 0.05 + voucherRedemptionThreshold: 2 + voucherRedemptionBatchThreshold: 2000 + voucherRedemptionMaxBatchSize: 15 + allocationManagementMode: "auto" + autoAllocationMinBatchSize: 20 + invalidExtraField: abcd # <-- invalid extra field +transactionMonitoring: + gasIncreaseTimeout: 10 + gasIncreaseFactor: 10 + baseFeePerGasMax: 10 + maxTransactionAttempts: 10 +subgraphs: + networkSubgraph: + url: http://subgraph + deployment: QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB + epochSubgraph: + url: http://subgraph +networkProvider: + url: http://provider +dai: + contractAddress: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" diff --git a/packages/indexer-common/src/__tests__/network-specification-files/invalid-missing-field.yml b/packages/indexer-common/src/__tests__/network-specification-files/invalid-missing-field.yml new file mode 100644 index 000000000..435558a5c --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification-files/invalid-missing-field.yml @@ -0,0 +1,35 @@ +networkIdentifier: mainnet +gateway: + url: http://gateway +indexerOptions: + # missing indexer address field + mnemonic: word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport + url: http://indexer + geoCoordinates: [25.1, -71.2] + restakeRewards: true + rebateClaimThreshold: 400 + rebateClaimBatchThreshold: 5000 + rebateClaimMaxBatchSize: 10 + poiDisputeMonitoring: false + poiDisputableEpochs: 5 + defaultAllocationAmount: 0.05 + voucherRedemptionThreshold: 2 + voucherRedemptionBatchThreshold: 2000 + voucherRedemptionMaxBatchSize: 15 + allocationManagementMode: "auto" + autoAllocationMinBatchSize: 20 +transactionMonitoring: + gasIncreaseTimeout: 10 + gasIncreaseFactor: 10 + baseFeePerGasMax: 10 + maxTransactionAttempts: 10 +subgraphs: + networkSubgraph: + url: http://subgraph + deployment: QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB + epochSubgraph: + url: http://subgraph +networkProvider: + url: http://provider +dai: + contractAddress: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" diff --git a/packages/indexer-common/src/__tests__/network-specification-files/invalid-network-identifier.yml b/packages/indexer-common/src/__tests__/network-specification-files/invalid-network-identifier.yml new file mode 100644 index 000000000..74eca8c09 --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification-files/invalid-network-identifier.yml @@ -0,0 +1,35 @@ +networkIdentifier: invalid # <-- invalid network identifier +gateway: + url: http://gateway +indexerOptions: + address: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" + mnemonic: word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport + url: http://indexer + geoCoordinates: [25.1, -71.2] + restakeRewards: true + rebateClaimThreshold: 400 + rebateClaimBatchThreshold: 5000 + rebateClaimMaxBatchSize: 10 + poiDisputeMonitoring: false + poiDisputableEpochs: 5 + defaultAllocationAmount: 0.05 + voucherRedemptionThreshold: 2 + voucherRedemptionBatchThreshold: 2000 + voucherRedemptionMaxBatchSize: 15 + allocationManagementMode: "auto" + autoAllocationMinBatchSize: 20 +transactionMonitoring: + gasIncreaseTimeout: 10 + gasIncreaseFactor: 10 + baseFeePerGasMax: 10 + maxTransactionAttempts: 10 +subgraphs: + networkSubgraph: + url: http://subgraph + deployment: QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB + epochSubgraph: + url: http://subgraph +networkProvider: + url: http://provider +dai: + contractAddress: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" diff --git a/packages/indexer-common/src/__tests__/network-specification-files/valid-missing.yml b/packages/indexer-common/src/__tests__/network-specification-files/valid-missing.yml new file mode 100644 index 000000000..6092351c3 --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification-files/valid-missing.yml @@ -0,0 +1,30 @@ +# this file is missing the whole `transactionMonitoring` entry, and is still valid +networkIdentifier: mainnet +gateway: + url: http://gateway +indexerOptions: + address: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" + mnemonic: word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport + url: http://indexer + geoCoordinates: [25.1, -71.2] + restakeRewards: true + rebateClaimThreshold: 400 + rebateClaimBatchThreshold: 5000 + rebateClaimMaxBatchSize: 10 + poiDisputeMonitoring: false + poiDisputableEpochs: 5 + defaultAllocationAmount: 0.05 + voucherRedemptionThreshold: 2 + voucherRedemptionBatchThreshold: 2000 + voucherRedemptionMaxBatchSize: 15 + allocationManagementMode: "auto" + autoAllocationMinBatchSize: 20 +subgraphs: + networkSubgraph: + deployment: QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB + epochSubgraph: + url: http://subgraph +networkProvider: + url: http://provider +dai: + contractAddress: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" diff --git a/packages/indexer-common/src/__tests__/network-specification-files/valid.yml b/packages/indexer-common/src/__tests__/network-specification-files/valid.yml new file mode 100644 index 000000000..756ac2f4e --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification-files/valid.yml @@ -0,0 +1,34 @@ +networkIdentifier: mainnet +gateway: + url: http://gateway +indexerOptions: + address: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" + mnemonic: word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport + url: http://indexer + geoCoordinates: [25.1, -71.2] + restakeRewards: true + rebateClaimThreshold: 400 + rebateClaimBatchThreshold: 5000 + rebateClaimMaxBatchSize: 10 + poiDisputeMonitoring: false + poiDisputableEpochs: 5 + defaultAllocationAmount: 0.05 + voucherRedemptionThreshold: 2 + voucherRedemptionBatchThreshold: 2000 + voucherRedemptionMaxBatchSize: 15 + allocationManagementMode: "auto" + autoAllocationMinBatchSize: 20 +transactionMonitoring: + gasIncreaseTimeout: 10 + gasIncreaseFactor: 10 + baseFeePerGasMax: 10 + maxTransactionAttempts: 10 +subgraphs: + networkSubgraph: + deployment: QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB + epochSubgraph: + url: http://subgraph +networkProvider: + url: http://provider +dai: + contractAddress: "0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448" diff --git a/packages/indexer-common/src/__tests__/network-specification.test.ts b/packages/indexer-common/src/__tests__/network-specification.test.ts new file mode 100644 index 000000000..bdbabd33a --- /dev/null +++ b/packages/indexer-common/src/__tests__/network-specification.test.ts @@ -0,0 +1,96 @@ +import * as fs from 'fs' +import * as path from 'path' +import * as YAML from 'yaml' +import { + NetworkSpecification, + IndexerOptions, + TransactionMonitoring, +} from '../network-specification' + +function readYamlFile(p: string): string { + const filePath = path.join(__dirname, 'network-specification-files', p) + const text = fs.readFileSync(filePath, 'utf8') + return YAML.parse(text) +} + +describe('Network Specification deserialization', () => { + describe('Successful deserialization', () => { + test('Valid specification file', () => { + const validFile = readYamlFile('valid.yml') + NetworkSpecification.parse(validFile) + }) + }) + + describe('Successful deserialization with missing defaults', () => { + test('Valid specification file', () => { + const validFile = readYamlFile('valid-missing.yml') + const parsed = NetworkSpecification.parse(validFile) + const expectedDefaults = TransactionMonitoring.parse({}) + expect(expectedDefaults).not.toEqual({}) // Ensures default is not an empty object + expect(parsed.transactionMonitoring).toStrictEqual(expectedDefaults) + }) + }) +}) + +interface FailedDeserializationTest { + file: string + path: string[] + message: string +} + +describe('Failed deserialization', () => { + const failedTests: FailedDeserializationTest[] = [ + { + file: 'invalid-epoch-subgraph.yml', + path: ['subgraphs', 'epochSubgraph', 'url'], + message: 'Epoch Subgraph endpoint must be defined', + }, + { + file: 'invalid-missing-field.yml', + path: ['indexerOptions', 'address'], + message: 'Required', + }, + { + file: 'invalid-extra-field.yml', + path: ['indexerOptions'], + message: "Unrecognized key(s) in object: 'invalidExtraField'", + }, + { + file: 'invalid-network-identifier.yml', + path: ['networkIdentifier'], + message: 'Invalid network identifier', + }, + { + file: 'invalid-base58.yml', + path: ['subgraphs', 'networkSubgraph', 'deployment'], + message: 'Invalid IPFS hash', + }, + ] + + test.each(failedTests)( + 'Validation should fail for $file', + (t: FailedDeserializationTest) => { + const invalidFile = readYamlFile(t.file) + const result = NetworkSpecification.safeParse(invalidFile) + expect(result.success).toBe(false) + if (result.success === false) { + const issue = result.error.issues[0] + expect(issue.path).toStrictEqual(t.path) + expect(issue.message).toStrictEqual(t.message) + } else { + fail('This deserialization test should have failed') + } + }, + ) +}) + +describe('Specificaiton parts parsing', () => { + test('Valid Indexer Options should parse successfully', () => { + IndexerOptions.parse({ + address: '0xdf9CAc44924C21a6c874ee3C727b1c9Ccd5b58cc', + mnemonic: 'any valid string can work', + url: 'http://example.com', + geoCoordinates: [60.16952, 24.93545], // Must be numbers + }) + }) +}) diff --git a/packages/indexer-common/src/indexer-management/__tests__/allocations.test.ts b/packages/indexer-common/src/indexer-management/__tests__/allocations.test.ts new file mode 100644 index 000000000..12454f584 --- /dev/null +++ b/packages/indexer-common/src/indexer-management/__tests__/allocations.test.ts @@ -0,0 +1,169 @@ +import { + Action, + ActionType, + AllocationManager, + defineIndexerManagementModels, + defineQueryFeeModels, + GraphNode, + IndexerManagementModels, + Network, + QueryFeeModels, +} from '@graphprotocol/indexer-common' +import { + connectDatabase, + createLogger, + createMetrics, + Logger, + Metrics, + parseGRT, +} from '@graphprotocol/common-ts' +import { + invalidReallocateAction, + invalidUnallocateAction, + queuedAllocateAction, + testNetworkSpecification, +} from './util' +import { Sequelize } from 'sequelize' + +// Make global Jest variables available +// eslint-disable-next-line @typescript-eslint/no-explicit-any +declare const __DATABASE__: any +declare const __LOG_LEVEL__: never + +let allocationManager: AllocationManager +let logger: Logger +let managementModels: IndexerManagementModels +let metrics: Metrics +let queryFeeModels: QueryFeeModels +let sequelize: Sequelize + +const setup = async () => { + logger = createLogger({ + name: 'Indexer API Client', + async: false, + level: __LOG_LEVEL__ ?? 'error', + }) + metrics = createMetrics() + // Clearing the registry prevents duplicate metric registration in the default registry. + metrics.registry.clear() + sequelize = await connectDatabase(__DATABASE__) + managementModels = defineIndexerManagementModels(sequelize) + queryFeeModels = defineQueryFeeModels(sequelize) + sequelize = await sequelize.sync({ force: true }) + + const graphNode = new GraphNode( + logger, + 'https://test-admin-endpoint.xyz', + 'https://test-query-endpoint.xyz', + 'https://test-status-endpoint.xyz', + [], + ) + + const network = await Network.create( + logger, + testNetworkSpecification, + queryFeeModels, + graphNode, + metrics, + ) + // TODO: Can we expose AllocationManager from client so we don't need to build this separately? + + allocationManager = new AllocationManager( + logger.child({ protocolNetwork: network.specification.networkIdentifier }), + managementModels, + graphNode, + network, + ) +} + +const setupEach = async () => { + sequelize = await sequelize.sync({ force: true }) +} +const teardownEach = async () => { + // Clear out query fee model tables + await queryFeeModels.allocationReceipts.truncate({ cascade: true }) + await queryFeeModels.vouchers.truncate({ cascade: true }) + await queryFeeModels.transferReceipts.truncate({ cascade: true }) + await queryFeeModels.transfers.truncate({ cascade: true }) + await queryFeeModels.allocationSummaries.truncate({ cascade: true }) + + // Clear out indexer management models + await managementModels.Action.truncate({ cascade: true }) + await managementModels.CostModel.truncate({ cascade: true }) + await managementModels.IndexingRule.truncate({ cascade: true }) + await managementModels.POIDispute.truncate({ cascade: true }) +} + +const teardownAll = async () => { + await sequelize.drop({}) +} + +describe('Allocation Manager', () => { + beforeAll(setup) + beforeEach(setupEach) + afterEach(teardownEach) + afterAll(teardownAll) + + // We have been rate-limited on CI as this test uses RPC providers, + // so we set its timeout to a higher value than usual. + jest.setTimeout(30_000) + + // Reuse an existing allocation with 25 sextillion allocated GRT + const allocationID = '0x96737b6a31f40edaf96c567efbb98935aa906ab9' + + // Redefine test actions to use that allocation ID + const unallocateAction = { + ...invalidUnallocateAction, + poi: '0x1', // non-zero POI + allocationID, + } + const reallocateAction = { + ...invalidReallocateAction, + amount: '10000', + allocationID, + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: Mocking the Action type for this test + const actions = [queuedAllocateAction, unallocateAction, reallocateAction] as Action[] + + test('stakeUsageSummary() correctly calculates token balances for array of actions', async () => { + const balances = await Promise.all( + actions.map((action: Action) => allocationManager.stakeUsageSummary(action)), + ) + + const allocate = balances[0] + const unallocate = balances[1] + const reallocate = balances[2] + + // Allocate test action + expect(allocate.action.type).toBe(ActionType.ALLOCATE) + expect(allocate.allocates).toStrictEqual(parseGRT('10000')) + expect(allocate.rewards.isZero()).toBeTruthy() + expect(allocate.unallocates.isZero()).toBeTruthy() + expect(allocate.balance).toStrictEqual(parseGRT('10000')) + + // Unallocate test action + expect(unallocate.action.type).toBe(ActionType.UNALLOCATE) + expect(unallocate.allocates.isZero()).toBeTruthy() + expect(unallocate.rewards.isZero()).toBeFalsy() + expect(unallocate.unallocates).toStrictEqual(parseGRT('25000')) + expect(unallocate.balance).toStrictEqual( + unallocate.allocates.sub(unallocate.unallocates).sub(unallocate.rewards), + ) + + // This Reallocate test Action intentionally uses a null or zeroed POI, so it should not accrue rewards. + expect(reallocate.action.type).toBe(ActionType.REALLOCATE) + expect(reallocate.allocates).toStrictEqual(parseGRT('10000')) + expect(reallocate.rewards.isZero()).toBeTruthy() + expect(reallocate.unallocates).toStrictEqual(parseGRT('25000')) + expect(reallocate.balance).toStrictEqual(parseGRT('-15000')) + }) + + test('validateActionBatchFeasibility() validates and correctly sorts actions based on net token balance', async () => { + const reordered = await allocationManager.validateActionBatchFeasibilty(actions) + expect(reordered[0]).toStrictEqual(unallocateAction) + expect(reordered[1]).toStrictEqual(reallocateAction) + expect(reordered[2]).toStrictEqual(queuedAllocateAction) + }) +}) diff --git a/packages/indexer-common/src/indexer-management/__tests__/helpers.ts b/packages/indexer-common/src/indexer-management/__tests__/helpers.test.ts similarity index 79% rename from packages/indexer-common/src/indexer-management/__tests__/helpers.ts rename to packages/indexer-common/src/indexer-management/__tests__/helpers.test.ts index b3cc7084b..7ab97e4c0 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/helpers.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/helpers.test.ts @@ -13,6 +13,8 @@ import { IndexingDecisionBasis, IndexingRuleAttributes, } from '../models' +import { defineQueryFeeModels, specification as spec } from '../../index' +import { networkIsL1, networkIsL2 } from '../types' import { fetchIndexingRules, upsertIndexingRule } from '../rules' import { SubgraphIdentifierType } from '../../subgraphs' import { ActionManager } from '../actions' @@ -24,7 +26,7 @@ import { EpochSubgraph, indexerError, IndexerErrorCode, - IndexingStatusResolver, + GraphNode, NetworkMonitor, NetworkSubgraph, resolveChainAlias, @@ -44,7 +46,7 @@ let sequelize: Sequelize let models: IndexerManagementModels let ethereum: ethers.providers.BaseProvider let contracts: NetworkContracts -let indexingStatusResolver: IndexingStatusResolver +let graphNode: GraphNode let networkSubgraph: NetworkSubgraph let epochSubgraph: EpochSubgraph let networkMonitor: NetworkMonitor @@ -56,7 +58,8 @@ const setupModels = async () => { // Spin up db sequelize = await connectDatabase(__DATABASE__) models = defineIndexerManagementModels(sequelize) - await sequelize.sync({ force: true }) + defineQueryFeeModels(sequelize) + sequelize = await sequelize.sync({ force: true }) } const setupMonitor = async () => { @@ -78,16 +81,27 @@ const setupMonitor = async () => { epochSubgraph = await EpochSubgraph.create( 'https://api.thegraph.com/subgraphs/name/graphprotocol/goerli-epoch-block-oracle', ) - indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, + graphNode = new GraphNode( + logger, + 'http://test-admin-endpoint.xyz', + 'https://test-query-endpoint.xyz', statusEndpoint, + [], + ) + + const indexerOptions = spec.IndexerOptions.parse({ + address: '0xc61127cdfb5380df4214b0200b9a07c7c49d34f9', + mnemonic: + 'word ivory whale diesel slab pelican voyage oxygen chat find tobacco sport', + url: 'http://test-url.xyz', }) + networkMonitor = new NetworkMonitor( resolveChainId('goerli'), contracts, - toAddress('0xc61127cdfb5380df4214b0200b9a07c7c49d34f9'), + indexerOptions, logger, - indexingStatusResolver, + graphNode, networkSubgraph, ethereum, epochSubgraph, @@ -101,7 +115,6 @@ const createMockAllocation = (): Allocation => { stakedTokens: BigNumber.from(50000), signalledTokens: BigNumber.from(100000), queryFeesAmount: BigNumber.from(0), - activeAllocations: 2, } as SubgraphDeployment const mockAllocation = { id: toAddress('0xbAd8935f75903A1eF5ea62199d98Fd7c3c1ab20C'), @@ -138,8 +151,8 @@ describe('Indexing Rules', () => { allocationAmount: '5000', identifierType: SubgraphIdentifierType.DEPLOYMENT, decisionBasis: IndexingDecisionBasis.ALWAYS, + protocolNetwork: 'goerli', } as Partial - const setIndexingRuleResult = await upsertIndexingRule(logger, models, indexingRule) expect(setIndexingRuleResult).toHaveProperty( 'allocationAmount', @@ -155,7 +168,8 @@ describe('Indexing Rules', () => { IndexingDecisionBasis.ALWAYS, ) - await expect(fetchIndexingRules(models, false)).resolves.toHaveLength(1) + // When reading directly to the database, `protocolNetwork` must be in the CAIP2-ID format. + await expect(fetchIndexingRules(models, false, 'eip155:5')).resolves.toHaveLength(1) }) }) @@ -204,6 +218,8 @@ describe('Actions', () => { source: 'indexerAgent', reason: 'indexingRule', priority: 0, + // When writing directly to the database, `protocolNetwork` must be in the CAIP2-ID format. + protocolNetwork: 'eip155:5', } await models.Action.upsert(action) @@ -315,3 +331,52 @@ describe.skip('Monitor', () => { ).rejects.toEqual(indexerError(IndexerErrorCode.IE018, `Could not resolve POI`)) }) }) + +describe('Network layer detection', () => { + interface NetworkLayer { + name: string + l1: boolean + l2: boolean + } + + // Should be true for L1 and false for L2 + const l1Networks: NetworkLayer[] = ['mainnet', 'eip155:1', 'goerli', 'eip155:5'].map( + (name: string) => ({ name, l1: true, l2: false }), + ) + + // Should be false for L1 and true for L2 + const l2Networks: NetworkLayer[] = [ + 'arbitrum-one', + 'eip155:42161', + 'arbitrum-goerli', + 'eip155:421613', + ].map((name: string) => ({ name, l1: false, l2: true })) + + // Those will be false for L1 and L2 + const nonProtocolNetworks: NetworkLayer[] = [ + 'fantom', + 'eip155:250', + 'hardhat', + 'eip155:1337', + 'matic', + 'eip155:137', + 'gnosis', + 'eip155:100', + ].map((name: string) => ({ name, l1: false, l2: false })) + + const testCases = [...l1Networks, ...l2Networks, ...nonProtocolNetworks] + + test.each(testCases)('Can detect network layer [$name]', (network) => { + expect(networkIsL1(network.name)).toStrictEqual(network.l1) + expect(networkIsL2(network.name)).toStrictEqual(network.l2) + }) + + const invalidTProtocolNetworkNames = ['invalid-name', 'eip155:9999'] + + test.each(invalidTProtocolNetworkNames)( + 'Throws error when protocol network is unknown [%s]', + (invalidProtocolNetworkName) => { + expect(() => networkIsL1(invalidProtocolNetworkName)).toThrow() + }, + ) +}) diff --git a/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.ts b/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.test.ts similarity index 74% rename from packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.ts rename to packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.test.ts index 593e79190..dc7df5ef4 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/resolvers/actions.test.ts @@ -2,51 +2,42 @@ import { Sequelize } from 'sequelize' import gql from 'graphql-tag' -import { BigNumber, ethers, Wallet } from 'ethers' import { - connectContracts, connectDatabase, createLogger, - createMetrics, Logger, Metrics, - mutable, - NetworkContracts, - parseGRT, - toAddress, + createMetrics, } from '@graphprotocol/common-ts' -import { - createIndexerManagementClient, - IndexerManagementClient, - IndexerManagementDefaults, -} from '../../client' +import { IndexerManagementClient } from '../../client' import { Action, defineIndexerManagementModels, IndexerManagementModels, } from '../../models' import { - defineQueryFeeModels, ActionInput, ActionParams, ActionStatus, ActionType, - AllocationReceiptCollector, - IndexingStatusResolver, - NetworkSubgraph, + defineQueryFeeModels, OrderDirection, QueryFeeModels, - TransactionManager, - NetworkMonitor, - EpochSubgraph, - resolveChainId, - AllocationManager, - SubgraphManager, - getTestProvider, } from '@graphprotocol/indexer-common' import { CombinedError } from '@urql/core' import { GraphQLError } from 'graphql' +import { + allocateToNotPublishedDeployment, + createTestManagementClient, + invalidReallocateAction, + invalidUnallocateAction, + queuedAllocateAction, + subgraphDeployment1, + subgraphDeployment2, + subgraphDeployment3, + notPublishedSubgraphDeployment, +} from '../util' const QUEUE_ACTIONS_MUTATION = gql` mutation queueActions($actions: [ActionInput!]!) { @@ -64,6 +55,7 @@ const QUEUE_ACTIONS_MUTATION = gql` transaction failureReason status + protocolNetwork } } ` @@ -84,6 +76,7 @@ const APPROVE_ACTIONS_MUTATION = gql` transaction failureReason status + protocolNetwork } } ` @@ -104,6 +97,7 @@ const CANCEL_ACTIONS_MUTATION = gql` transaction failureReason status + protocolNetwork } } ` @@ -124,6 +118,7 @@ const UPDATE_ACTIONS_MUTATION = gql` transaction failureReason status + protocolNetwork } } ` @@ -148,6 +143,7 @@ const ACTIONS_QUERY = gql` transaction failureReason status + protocolNetwork } } ` @@ -157,13 +153,12 @@ const DELETE_ACTIONS_MUTATION = gql` deleteActions(actionIDs: $actionIDs) } ` +type ActionTestInput = Record async function actionInputToExpected( input: ActionInput, id: number, - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): Promise<{ [key: string]: any }> { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const expected: Record = { ...input } +): Promise { + const expected: ActionTestInput = { ...input } expected.id = id for (const actionKey in Action.getAttributes()) { @@ -171,103 +166,21 @@ async function actionInputToExpected( expected[actionKey] = null } } - return expected -} -const defaults: IndexerManagementDefaults = { - globalIndexingRule: { - allocationAmount: parseGRT('100'), - parallelAllocations: 1, - requireSupported: true, - safety: true, - }, + // We expect the protocol network to be transformed to it's CAIP2-ID + // form for all inputs + if (input.protocolNetwork === 'goerli') { + expected.protocolNetwork = 'eip155:5' + } + + return expected } -const subgraphDeployment1 = 'Qmew9PZUJCoDzXqqU6vGyTENTKHrrN4dy5h94kertfudqy' -const subgraphDeployment2 = 'QmWq1pmnhEvx25qxpYYj9Yp6E1xMKMVoUjXVQBxUJmreSe' -const subgraphDeployment3 = 'QmRhH2nhNibDVPZmYqq3TUZZARZ77vgjYCvPNiGBCogtgM' -const notPublishedSubgraphDeployment = 'QmeqJ6hsdyk9dVbo1tvRgAxWrVS3rkERiEMsxzPShKLco6' - -const queuedAllocateAction = { - status: ActionStatus.QUEUED, - type: ActionType.ALLOCATE, - deploymentID: subgraphDeployment1, - amount: '10000', - force: false, - source: 'indexerAgent', - reason: 'indexingRule', - priority: 0, -} as ActionInput - -const allocateToNotPublishedDeployment = { - status: ActionStatus.QUEUED, - type: ActionType.ALLOCATE, - deploymentID: notPublishedSubgraphDeployment, - amount: '10000', - force: false, - source: 'indexerAgent', - reason: 'indexingRule', - priority: 0, -} as ActionInput - -const invalidUnallocateAction = { - status: ActionStatus.QUEUED, - type: ActionType.UNALLOCATE, - allocationID: '0x8f63930129e585c69482b56390a09b6b176f4a4c', - deploymentID: subgraphDeployment1, - amount: undefined, - poi: undefined, - force: false, - source: 'indexerAgent', - reason: 'indexingRule', - priority: 0, -} as ActionInput - -// const queuedReallocateAction = { -// status: ActionStatus.QUEUED, -// type: ActionType.REALLOCATE, -// allocationID: '0x8f63930129e585c69482b56390a09b6b176f4a4c', -// deploymentID: subgraphDeployment1, -// poi: undefined, -// amount: '27000', -// force: false, -// source: 'indexerAgent', -// reason: 'indexingRule', -// priority: 0, -// } as ActionInput - -const invalidReallocateAction = { - status: ActionStatus.QUEUED, - type: ActionType.REALLOCATE, - allocationID: '0x8f63930129e585c69482b56390a09b6b176f4a4c', - deploymentID: subgraphDeployment1, - poi: undefined, - amount: undefined, - force: false, - source: 'indexerAgent', - reason: 'indexingRule', - priority: 0, -} as ActionInput - -const indexNodeIDs = ['node_1'] - -let ethereum: ethers.providers.BaseProvider let sequelize: Sequelize let managementModels: IndexerManagementModels let queryFeeModels: QueryFeeModels -let address: string -let contracts: NetworkContracts let logger: Logger -let indexingStatusResolver: IndexingStatusResolver -let networkSubgraph: NetworkSubgraph let client: IndexerManagementClient -let transactionManager: TransactionManager -let wallet: Wallet -let epochSubgraph: EpochSubgraph -let networkMonitor: NetworkMonitor -let receiptCollector: AllocationReceiptCollector -let mockedSubgraphManager: SubgraphManager -let allocationManager: AllocationManager let metrics: Metrics // Make global Jest variables available @@ -276,100 +189,19 @@ declare const __DATABASE__: any declare const __LOG_LEVEL__: never const setup = async () => { - const statusEndpoint = 'http://localhost:8030/graphql' - const deploymentManagementEndpoint = 'http://localhost:8020/' - address = '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1' - sequelize = await connectDatabase(__DATABASE__) queryFeeModels = defineQueryFeeModels(sequelize) managementModels = defineIndexerManagementModels(sequelize) sequelize = await sequelize.sync({ force: true }) - ethereum = getTestProvider('goerli') - wallet = Wallet.createRandom() - contracts = await connectContracts(ethereum, 5) + metrics = createMetrics() + // Clearing the registry prevents duplicate metric registration in the default registry. + metrics.registry.clear() logger = createLogger({ name: 'Indexer API Client', async: false, level: __LOG_LEVEL__ ?? 'error', }) - - indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, - statusEndpoint, - }) - networkSubgraph = await NetworkSubgraph.create({ - logger, - endpoint: - 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli', - deployment: undefined, - }) - transactionManager = new TransactionManager( - ethereum, - wallet, - mutable(false), - mutable(true), - 240000, - 1.2, - 100 * 10 ** 9, - 0, - ) - - epochSubgraph = await EpochSubgraph.create( - 'https://api.thegraph.com/subgraphs/name/graphprotocol/goerli-epoch-block-oracle', - ) - - metrics = createMetrics() - receiptCollector = new AllocationReceiptCollector({ - logger, - metrics, - transactionManager: transactionManager, - models: queryFeeModels, - allocationExchange: contracts.allocationExchange, - collectEndpoint: 'http://localhost:8030/', - voucherRedemptionThreshold: BigNumber.from(200), - voucherRedemptionBatchThreshold: BigNumber.from(2000), - voucherRedemptionMaxBatchSize: 100, - }) - - networkMonitor = new NetworkMonitor( - resolveChainId('goerli'), - contracts, - toAddress('0xc61127cdfb5380df4214b0200b9a07c7c49d34f9'), - logger, - indexingStatusResolver, - networkSubgraph, - ethereum, - epochSubgraph, - ) - - client = await createIndexerManagementClient({ - models: managementModels, - address, - contracts, - indexingStatusResolver, - indexNodeIDs, - deploymentManagementEndpoint, - networkSubgraph, - receiptCollector, - transactionManager, - networkMonitor, - logger, - defaults, - features: { - injectDai: true, - }, - }) - mockedSubgraphManager = new SubgraphManager('fake endpoint', ['fake node id']) - allocationManager = new AllocationManager( - contracts, - logger, - address, - managementModels, - networkMonitor, - receiptCollector, - mockedSubgraphManager, - transactionManager, - ) + client = await createTestManagementClient(__DATABASE__, logger, true, metrics) } const setupEach = async () => { @@ -391,11 +223,11 @@ const teardownEach = async () => { } const teardownAll = async () => { - metrics.registry.clear() await sequelize.drop({}) } describe('Actions', () => { + jest.setTimeout(60_000) beforeAll(setup) beforeEach(setupEach) afterEach(teardownEach) @@ -662,6 +494,7 @@ describe('Actions', () => { ['source', 'String'], ['reason', 'String'], ['priority', 'Int'], + ['protocolNetwork', 'String'], ] const graphQLErrors = expectedFieldNamesAndTypes.map( ([fieldName, fieldType]) => @@ -678,7 +511,8 @@ describe('Actions', () => { test('Reject action with invalid params for action type', async () => { const inputAction = invalidReallocateAction - + const expected = { ...inputAction, protocolNetwork: 'eip155:5' } + const fields = JSON.stringify(expected) await expect( client.mutation(QUEUE_ACTIONS_MUTATION, { actions: [inputAction] }).toPromise(), ).resolves.toHaveProperty( @@ -686,7 +520,7 @@ describe('Actions', () => { new CombinedError({ graphQLErrors: [ new GraphQLError( - 'Failed to queue action: Invalid action input, actionInput: {"status":"queued","type":"reallocate","deploymentID":"Qmew9PZUJCoDzXqqU6vGyTENTKHrrN4dy5h94kertfudqy","allocationID":"0x8f63930129e585c69482b56390a09b6b176f4a4c","force":false,"source":"indexerAgent","reason":"indexingRule","priority":0}', + `Failed to queue action: Invalid action input, actionInput: ${fields}`, ), ], }), @@ -893,6 +727,8 @@ describe('Actions', () => { source: 'indexerAgent', reason: 'indexingRule', priority: 0, + // When writing directly to the database, `protocolNetwork` must be in the CAIP2-ID format. + protocolNetwork: 'eip155:5', } as ActionInput const proposedAction = { @@ -903,6 +739,7 @@ describe('Actions', () => { source: 'indexerAgent', reason: 'indexingRule', priority: 0, + protocolNetwork: 'goerli', } as ActionInput await managementModels.Action.create(failedAction, { @@ -910,9 +747,11 @@ describe('Actions', () => { returning: true, }) - await expect( - client.mutation(QUEUE_ACTIONS_MUTATION, { actions: [proposedAction] }).toPromise(), - ).resolves.toHaveProperty( + const result = await client + .mutation(QUEUE_ACTIONS_MUTATION, { actions: [proposedAction] }) + .toPromise() + + expect(result).toHaveProperty( 'error', new CombinedError({ graphQLErrors: [ @@ -943,6 +782,8 @@ describe('Actions', () => { source: 'indexerAgent', reason: 'indexingRule', priority: 0, + // When writing directly to the database, `protocolNetwork` must be in the CAIP2-ID format. + protocolNetwork: 'eip155:5', } as ActionInput const proposedAction = { @@ -953,6 +794,7 @@ describe('Actions', () => { source: 'indexerAgent', reason: 'indexingRule', priority: 0, + protocolNetwork: 'goerli', } as ActionInput await managementModels.Action.create(successfulAction, { @@ -993,6 +835,8 @@ describe('Actions', () => { source: 'indexerAgent', reason: 'indexingRule', priority: 0, + // When writing directly to the database, `protocolNetwork` must be in the CAIP2-ID format. + protocolNetwork: 'eip155:5', } as ActionInput const queuedAllocateAction = { @@ -1004,6 +848,7 @@ describe('Actions', () => { source: 'indexerAgent', reason: 'indexingRule', priority: 0, + protocolNetwork: 'goerli', } as ActionInput await managementModels.Action.create(queuedUnallocateAction, { @@ -1045,70 +890,3 @@ describe('Actions', () => { ).resolves.toHaveProperty('data.updateActions', updatedExpecteds) }) }) - -describe('Allocation Manager', () => { - beforeAll(setup) - beforeEach(setupEach) - afterEach(teardownEach) - afterAll(teardownAll) - - // We have been rate-limited on CI as this test uses RPC providers, - // so we set its timeout to a higher value than usual. - jest.setTimeout(30_000) - - // Reuse an existing allocation with 25 sextillion allocated GRT - const allocationID = '0x96737b6a31f40edaf96c567efbb98935aa906ab9' - - // Redefine test actions to use that allocation ID - const unallocateAction = { - ...invalidUnallocateAction, - poi: '0x1', // non-zero POI - allocationID, - } - const reallocateAction = { - ...invalidReallocateAction, - amount: '10000', - allocationID, - } - - const actions = [queuedAllocateAction, unallocateAction, reallocateAction] as Action[] - - test('stakeUsageSummary() correctly calculates token balances for array of actions', async () => { - const balances = await Promise.all( - actions.map((action) => allocationManager.stakeUsageSummary(action)), - ) - const allocate = balances[0] - const unallocate = balances[1] - const reallocate = balances[2] - - // Allocate test action - expect(allocate.action.type).toBe(ActionType.ALLOCATE) - expect(allocate.allocates).toStrictEqual(parseGRT('10000')) - expect(allocate.rewards.isZero()).toBeTruthy() - expect(allocate.unallocates.isZero()).toBeTruthy() - expect(allocate.balance).toStrictEqual(parseGRT('10000')) - - // Unallocate test action - expect(unallocate.action.type).toBe(ActionType.UNALLOCATE) - expect(unallocate.allocates.isZero()).toBeTruthy() - expect(unallocate.rewards.isZero()).toBeFalsy() - expect(unallocate.unallocates).toStrictEqual(parseGRT('25000')) - expect(unallocate.balance).toStrictEqual( - unallocate.allocates.sub(unallocate.unallocates).sub(unallocate.rewards), - ) - - // This Reallocate test Action intentionally uses a null or zeroed POI, so it should not accrue rewards. - expect(reallocate.action.type).toBe(ActionType.REALLOCATE) - expect(reallocate.allocates).toStrictEqual(parseGRT('10000')) - expect(reallocate.rewards.isZero()).toBeTruthy() - expect(reallocate.unallocates).toStrictEqual(parseGRT('25000')) - expect(reallocate.balance).toStrictEqual(parseGRT('-15000')) - }) - - test('validateActionBatchFeasibility() validates and correctly sorts actions based on net token balance', async () => { - const reordered = await allocationManager.validateActionBatchFeasibilty(actions) - expect(reordered[0]).toStrictEqual(unallocateAction) - expect(reordered[1]).toStrictEqual(reallocateAction) - expect(reordered[2]).toStrictEqual(queuedAllocateAction) - }) -}) diff --git a/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.ts b/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.test.ts similarity index 91% rename from packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.ts rename to packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.test.ts index aed72f63a..dd4e96187 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.test.ts @@ -1,27 +1,17 @@ import { Sequelize } from 'sequelize' import gql from 'graphql-tag' import { - connectDatabase, - connectContracts, createLogger, Logger, - NetworkContracts, - parseGRT, + connectDatabase, + createMetrics, } from '@graphprotocol/common-ts' - -import { - createIndexerManagementClient, - IndexerManagementClient, - IndexerManagementDefaults, -} from '../../client' +import { IndexerManagementClient } from '../../client' import { defineIndexerManagementModels, IndexerManagementModels } from '../../models' import { CombinedError } from '@urql/core' import { GraphQLError } from 'graphql' -import { - IndexingStatusResolver, - NetworkSubgraph, - getTestProvider, -} from '@graphprotocol/indexer-common' +import { createTestManagementClient } from '../util' +import { defineQueryFeeModels } from '../../../query-fees/models' // Make global Jest variable available // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -80,61 +70,29 @@ const GET_COST_MODELS_DEPLOYMENTS_QUERY = gql` let sequelize: Sequelize let models: IndexerManagementModels -let address: string -let contracts: NetworkContracts let logger: Logger -let indexNodeIDs: string[] -let statusEndpoint: string -let indexingStatusResolver: IndexingStatusResolver -let networkSubgraph: NetworkSubgraph let client: IndexerManagementClient - -const defaults: IndexerManagementDefaults = { - globalIndexingRule: { - allocationAmount: parseGRT('100'), - parallelAllocations: 1, - }, -} +const metrics = createMetrics() const setupAll = async () => { + logger = createLogger({ + name: 'Indexer API Client', + async: false, + level: __LOG_LEVEL__ ?? 'error', + }) + // Spin up db sequelize = await connectDatabase(__DATABASE__) models = defineIndexerManagementModels(sequelize) - address = '0xtest' - contracts = await connectContracts(getTestProvider('goerli'), 5) + defineQueryFeeModels(sequelize) sequelize = await sequelize.sync({ force: true }) logger = createLogger({ name: 'Indexer API Client', async: false, level: __LOG_LEVEL__ ?? 'error', }) - indexNodeIDs = ['node_1'] - statusEndpoint = 'http://localhost:8030/graphql' - indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, - statusEndpoint, - }) - networkSubgraph = await NetworkSubgraph.create({ - logger, - endpoint: - 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli', - deployment: undefined, - }) - client = await createIndexerManagementClient({ - models, - address, - contracts, - indexingStatusResolver, - indexNodeIDs, - deploymentManagementEndpoint: statusEndpoint, - networkSubgraph, - logger, - defaults, - features: { - injectDai: true, - }, - }) + client = await createTestManagementClient(__DATABASE__, logger, true, metrics) } const teardownAll = async () => { @@ -153,6 +111,7 @@ const teardownEach = async () => { } describe('Cost models', () => { + jest.setTimeout(60_000) beforeAll(setupAll) beforeEach(setupEach) afterEach(teardownEach) @@ -754,37 +713,32 @@ describe('Feature: Inject $DAI variable', () => { }) test('If feature is disabled, $DAI variable is not preserved', async () => { - // Recreate client with features.injectDai = false - client = await createIndexerManagementClient({ - models, - address, - contracts, - indexingStatusResolver, - indexNodeIDs, - deploymentManagementEndpoint: statusEndpoint, - networkSubgraph, + const clientNoInjectDai = await createTestManagementClient( + __DATABASE__, logger, - defaults, - features: { - injectDai: false, - }, - }) + false, + metrics, + ) + const initial = { deployment: '0x0000000000000000000000000000000000000000000000000000000000000000', model: 'query { votes } => 10 * $n;', variables: JSON.stringify({ n: 5, DAI: '10.0' }), } - await client.mutation(SET_COST_MODEL_MUTATION, { costModel: initial }).toPromise() + await clientNoInjectDai + .mutation(SET_COST_MODEL_MUTATION, { costModel: initial }) + .toPromise() const update = { deployment: '0x0000000000000000000000000000000000000000000000000000000000000000', model: initial.model, variables: JSON.stringify({}), } - await client.mutation(SET_COST_MODEL_MUTATION, { costModel: update }).toPromise() - await expect(client.query(GET_COST_MODELS_QUERY).toPromise()).resolves.toHaveProperty( - 'data.costModels', - [update], - ) + await clientNoInjectDai + .mutation(SET_COST_MODEL_MUTATION, { costModel: update }) + .toPromise() + await expect( + clientNoInjectDai.query(GET_COST_MODELS_QUERY).toPromise(), + ).resolves.toHaveProperty('data.costModels', [update]) }) }) diff --git a/packages/indexer-common/src/indexer-management/__tests__/resolvers/indexing-rules.ts b/packages/indexer-common/src/indexer-management/__tests__/resolvers/indexing-rules.test.ts similarity index 76% rename from packages/indexer-common/src/indexer-management/__tests__/resolvers/indexing-rules.ts rename to packages/indexer-common/src/indexer-management/__tests__/resolvers/indexing-rules.test.ts index 1dc1eb84b..7de22746b 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/resolvers/indexing-rules.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/resolvers/indexing-rules.test.ts @@ -2,18 +2,11 @@ import { Sequelize } from 'sequelize' import gql from 'graphql-tag' import { connectDatabase, - connectContracts, createLogger, Logger, - NetworkContracts, - parseGRT, + createMetrics, } from '@graphprotocol/common-ts' - -import { - createIndexerManagementClient, - IndexerManagementClient, - IndexerManagementDefaults, -} from '../../client' +import { IndexerManagementClient } from '../../client' import { defineIndexerManagementModels, IndexerManagementModels, @@ -21,12 +14,12 @@ import { INDEXING_RULE_GLOBAL, } from '../../models' import { - IndexingStatusResolver, - NetworkSubgraph, SubgraphIdentifierType, - getTestProvider, + defineQueryFeeModels, } from '@graphprotocol/indexer-common' +import { createTestManagementClient, defaults } from '../util' + // Make global Jest variable available // eslint-disable-next-line @typescript-eslint/no-explicit-any declare const __DATABASE__: any @@ -51,24 +44,25 @@ const SET_INDEXING_RULE_MUTATION = gql` decisionBasis requireSupported safety + protocolNetwork } } ` const DELETE_INDEXING_RULE_MUTATION = gql` - mutation deleteIndexingRule($identifier: String!) { + mutation deleteIndexingRule($identifier: IndexingRuleIdentifier!) { deleteIndexingRule(identifier: $identifier) } ` const DELETE_INDEXING_RULES_MUTATION = gql` - mutation deleteIndexingRules($identifiers: [String!]!) { + mutation deleteIndexingRules($identifiers: [IndexingRuleIdentifier!]!) { deleteIndexingRules(identifiers: $identifiers) } ` const INDEXING_RULE_QUERY = gql` - query indexingRule($identifier: String!, $merged: Boolean!) { + query indexingRule($identifier: IndexingRuleIdentifier!, $merged: Boolean!) { indexingRule(identifier: $identifier, merged: $merged) { identifier identifierType @@ -85,13 +79,14 @@ const INDEXING_RULE_QUERY = gql` decisionBasis requireSupported safety + protocolNetwork } } ` const INDEXING_RULES_QUERY = gql` - query indexingRules($merged: Boolean!) { - indexingRules(merged: $merged) { + query indexingRules($merged: Boolean!, $protocolNetwork: String!) { + indexingRules(merged: $merged, protocolNetwork: $protocolNetwork) { identifier identifierType allocationAmount @@ -107,66 +102,29 @@ const INDEXING_RULES_QUERY = gql` decisionBasis requireSupported safety + protocolNetwork } } ` let sequelize: Sequelize let models: IndexerManagementModels -let address: string -let contracts: NetworkContracts let logger: Logger -let indexingStatusResolver: IndexingStatusResolver -let networkSubgraph: NetworkSubgraph let client: IndexerManagementClient - -const defaults: IndexerManagementDefaults = { - globalIndexingRule: { - allocationAmount: parseGRT('100'), - parallelAllocations: 1, - requireSupported: true, - safety: true, - }, -} +const metrics = createMetrics() const setupAll = async () => { - // Spin up db sequelize = await connectDatabase(__DATABASE__) models = defineIndexerManagementModels(sequelize) - address = '0xtest' - contracts = await connectContracts(getTestProvider('goerli'), 5) + defineQueryFeeModels(sequelize) await sequelize.sync({ force: true }) + logger = createLogger({ name: 'Indexer API Client', async: false, level: __LOG_LEVEL__ ?? 'error', }) - const statusEndpoint = 'http://localhost:8030/graphql' - indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, - statusEndpoint, - }) - networkSubgraph = await NetworkSubgraph.create({ - logger, - endpoint: - 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli', - deployment: undefined, - }) - const indexNodeIDs = ['node_1'] - client = await createIndexerManagementClient({ - models, - address, - contracts, - indexingStatusResolver, - indexNodeIDs, - deploymentManagementEndpoint: statusEndpoint, - networkSubgraph, - logger, - defaults, - features: { - injectDai: true, - }, - }) + client = await createTestManagementClient(__DATABASE__, logger, true, metrics) } const teardownAll = async () => { @@ -185,6 +143,7 @@ const teardownEach = async () => { } describe('Indexing rules', () => { + jest.setTimeout(60_000) beforeAll(setupAll) beforeEach(setupEach) afterEach(teardownEach) @@ -195,6 +154,7 @@ describe('Indexing rules', () => { identifier: INDEXING_RULE_GLOBAL, identifierType: SubgraphIdentifierType.GROUP, allocationAmount: '1000', + protocolNetwork: 'goerli', } const expected = { @@ -211,6 +171,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.RULES, requireSupported: true, safety: true, + protocolNetwork: 'eip155:5', } // Update the rule and ensure the right data is returned @@ -219,11 +180,12 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', expected) // Query the rule to make sure it's updated in the db - await expect( - client - .query(INDEXING_RULE_QUERY, { identifier: INDEXING_RULE_GLOBAL, merged: false }) - .toPromise(), - ).resolves.toHaveProperty('data.indexingRule', expected) + const ruleIdentifier = { identifier: INDEXING_RULE_GLOBAL, protocolNetwork: 'goerli' } + + const result = await client + .query(INDEXING_RULE_QUERY, { identifier: ruleIdentifier, merged: false }) + .toPromise() + expect(result).toHaveProperty('data.indexingRule', expected) }) test('Set and get global rule (complete)', async () => { @@ -243,10 +205,12 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.RULES, requireSupported: true, safety: true, + protocolNetwork: 'goerli', } const expected = { ...input, + protocolNetwork: 'eip155:5', } // Update the rule @@ -255,9 +219,10 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', expected) // Query the rule to make sure it's updated in the db + const ruleIdentifier = { identifier: INDEXING_RULE_GLOBAL, protocolNetwork: 'goerli' } await expect( client - .query(INDEXING_RULE_QUERY, { identifier: INDEXING_RULE_GLOBAL, merged: false }) + .query(INDEXING_RULE_QUERY, { identifier: ruleIdentifier, merged: false }) .toPromise(), ).resolves.toHaveProperty('data.indexingRule', expected) }) @@ -268,6 +233,7 @@ describe('Indexing rules', () => { identifierType: SubgraphIdentifierType.GROUP, allocationAmount: '1', minSignal: '2', + protocolNetwork: 'goerli', } const original = { @@ -283,6 +249,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.RULES, requireSupported: true, safety: true, + protocolNetwork: 'eip155:5', } // Write the original @@ -298,11 +265,13 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.OFFCHAIN, autoRenewal: true, safety: false, + protocolNetwork: 'goerli', } const expected = { ...original, ...update, + protocolNetwork: 'eip155:5', } // Update the rule @@ -311,20 +280,23 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', expected) // Query the rule to make sure it's updated in the db + const ruleIdentifier = { identifier: INDEXING_RULE_GLOBAL, protocolNetwork: 'goerli' } await expect( client - .query(INDEXING_RULE_QUERY, { identifier: INDEXING_RULE_GLOBAL, merged: false }) + .query(INDEXING_RULE_QUERY, { identifier: ruleIdentifier, merged: false }) .toPromise(), ).resolves.toHaveProperty('data.indexingRule', expected) }) test('Set and get deployment rule (partial update)', async () => { + const originalIdentifier = 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC' const originalInput = { - identifier: 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC', + identifier: originalIdentifier, identifierType: SubgraphIdentifierType.DEPLOYMENT, allocationAmount: '1', minSignal: '2', decisionBasis: IndexingDecisionBasis.OFFCHAIN, + protocolNetwork: 'goerli', } const original = { @@ -340,6 +312,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.OFFCHAIN, requireSupported: true, safety: true, + protocolNetwork: 'eip155:5', } // Write the original @@ -348,7 +321,7 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', original) const update = { - identifier: 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC', + identifier: originalIdentifier, identifierType: SubgraphIdentifierType.DEPLOYMENT, allocationAmount: null, maxSignal: '3', @@ -357,11 +330,13 @@ describe('Indexing rules', () => { autoRenewal: false, requireSupported: false, safety: false, + protocolNetwork: 'goerli', } const expected = { ...original, ...update, + protocolNetwork: 'eip155:5', } // Update the rule @@ -370,10 +345,14 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', expected) // Query the rule to make sure it's updated in the db + const ruleIdentifier = { + identifier: update.identifier, + protocolNetwork: update.protocolNetwork, + } await expect( client .query(INDEXING_RULE_QUERY, { - identifier: 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC', + identifier: ruleIdentifier, merged: false, }) .toPromise(), @@ -385,14 +364,16 @@ describe('Indexing rules', () => { allocationLifetime: null, decisionBasis: IndexingDecisionBasis.NEVER, autoRenewal: true, + protocolNetwork: 'goerli', } const expectedAgain = { ...original, ...update, ...updateAgain, + protocolNetwork: 'eip155:5', } - expectedAgain.identifier = 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC' + expectedAgain.identifier = originalIdentifier // Update the rule await expect( @@ -400,10 +381,14 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', expectedAgain) // Query the rule to make sure it's updated in the db + const ruleIdentifierAgain = { + identifier: originalIdentifier, + protocolNetwork: updateAgain.protocolNetwork, + } await expect( client .query(INDEXING_RULE_QUERY, { - identifier: 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC', + identifier: ruleIdentifierAgain, merged: false, }) .toPromise(), @@ -417,6 +402,7 @@ describe('Indexing rules', () => { allocationAmount: '1', minSignal: '1', decisionBasis: IndexingDecisionBasis.NEVER, + protocolNetwork: 'goerli', } const deploymentInput = { @@ -428,6 +414,7 @@ describe('Indexing rules', () => { requireSupported: false, autoRenewal: false, safety: true, + protocolNetwork: 'goerli', } const globalExpected = { @@ -443,6 +430,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.NEVER, requireSupported: true, safety: true, + protocolNetwork: 'eip155:5', } const deploymentExpected = { @@ -458,6 +446,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.OFFCHAIN, requireSupported: false, safety: true, + protocolNetwork: 'eip155:5', } deploymentExpected.identifier = 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC' @@ -470,21 +459,28 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', deploymentExpected) // Query the global rule + const globalRuleIdentifier = { + identifier: INDEXING_RULE_GLOBAL, + protocolNetwork: 'goerli', + } await expect( client .query(INDEXING_RULE_QUERY, { - identifier: INDEXING_RULE_GLOBAL, + identifier: globalRuleIdentifier, merged: false, }) .toPromise(), ).resolves.toHaveProperty('data.indexingRule', globalExpected) // Query the rule for the deployment + const deploymentRuleIdentifier = { + identifier: '0xa4e311bfa7edabed7b31d93e0b3e751659669852ef46adbedd44dc2454db4bf3', + protocolNetwork: 'goerli', + } await expect( client .query(INDEXING_RULE_QUERY, { - identifier: - '0xa4e311bfa7edabed7b31d93e0b3e751659669852ef46adbedd44dc2454db4bf3', + identifier: deploymentRuleIdentifier, merged: false, }) .toPromise(), @@ -492,7 +488,9 @@ describe('Indexing rules', () => { // Query all rules together await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [globalExpected, deploymentExpected]) }) @@ -504,6 +502,7 @@ describe('Indexing rules', () => { minSignal: '2', allocationLifetime: 20, autoRenewal: false, + protocolNetwork: 'goerli', } const expected = { @@ -519,6 +518,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.RULES, requireSupported: true, safety: true, + protocolNetwork: 'eip155:5', } // Write the rule @@ -528,19 +528,26 @@ describe('Indexing rules', () => { // Query all rules await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [expected]) // Delete the rule + const ruleIdentifier = { identifier: expected.identifier, protocolNetwork: 'goerli' } await expect( client - .mutation(DELETE_INDEXING_RULE_MUTATION, { identifier: expected.identifier }) + .mutation(DELETE_INDEXING_RULE_MUTATION, { + identifier: ruleIdentifier, + }) .toPromise(), ).resolves.toHaveProperty('data.deleteIndexingRule', true) // Query all rules together await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', []) }) @@ -551,6 +558,7 @@ describe('Indexing rules', () => { allocationAmount: '1', requireSupported: true, safety: true, + protocolNetwork: 'goerli', } const expectedBefore = { @@ -565,6 +573,7 @@ describe('Indexing rules', () => { minAverageQueryFees: null, custom: null, decisionBasis: IndexingDecisionBasis.RULES, + protocolNetwork: 'eip155:5', } // Write the rule @@ -574,7 +583,9 @@ describe('Indexing rules', () => { // Query all rules await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [expectedBefore]) // Clear the allocationAmount field @@ -591,7 +602,9 @@ describe('Indexing rules', () => { // Query the rules again to see that the update went through await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [ { ...expectedBefore, allocationAmount: null }, ]) @@ -609,6 +622,7 @@ describe('Indexing rules', () => { requireSupported: true, autoRenewal: true, safety: false, + protocolNetwork: 'goerli', } const deploymentInput = { @@ -621,6 +635,7 @@ describe('Indexing rules', () => { autoRenewal: false, requireSupported: false, safety: true, + protocolNetwork: 'goerli', } const globalExpected = { @@ -635,6 +650,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.NEVER, requireSupported: true, safety: false, + protocolNetwork: 'eip155:5', } const deploymentExpected = { @@ -650,6 +666,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.OFFCHAIN, requireSupported: false, safety: true, + protocolNetwork: 'eip155:5', } const deploymentMergedExpected = { @@ -665,6 +682,7 @@ describe('Indexing rules', () => { decisionBasis: IndexingDecisionBasis.OFFCHAIN, requireSupported: false, safety: true, + protocolNetwork: 'eip155:5', } // Write the orginals @@ -676,20 +694,28 @@ describe('Indexing rules', () => { ).resolves.toHaveProperty('data.setIndexingRule', deploymentExpected) // Query the global rule + const globalRuleIdentifier = { + identifier: INDEXING_RULE_GLOBAL, + protocolNetwork: 'goerli', + } await expect( client .query(INDEXING_RULE_QUERY, { - identifier: INDEXING_RULE_GLOBAL, + identifier: globalRuleIdentifier, merged: false, }) .toPromise(), ).resolves.toHaveProperty('data.indexingRule', globalExpected) // Query the rule for the deployment merged with the global rule + const ruleIdentifier = { + identifier: 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC', + protocolNetwork: 'goerli', + } await expect( client .query(INDEXING_RULE_QUERY, { - identifier: 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC', + identifier: ruleIdentifier, merged: true, }) .toPromise(), @@ -697,12 +723,16 @@ describe('Indexing rules', () => { // Query all rules together (without merging) await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [globalExpected, deploymentExpected]) // Query all rules together (with merging) await expect( - client.query(INDEXING_RULES_QUERY, { merged: true }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: true, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [ globalExpected, deploymentMergedExpected, @@ -717,27 +747,34 @@ describe('Indexing rules', () => { minSignal: '1', decisionBasis: IndexingDecisionBasis.NEVER, minAverageQueryFees: '1', + protocolNetwork: 'goerli', } await client.mutation(SET_INDEXING_RULE_MUTATION, { rule: globalInput }).toPromise() + const globalRuleIdentifier = { + identifier: INDEXING_RULE_GLOBAL, + protocolNetwork: 'goerli', + } await expect( client .mutation(DELETE_INDEXING_RULE_MUTATION, { - identifier: 'global', + identifier: globalRuleIdentifier, }) .toPromise(), ).resolves.toHaveProperty('data.deleteIndexingRule', true) await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [ { ...defaults.globalIndexingRule, allocationAmount: defaults.globalIndexingRule.allocationAmount.toString(), custom: null, decisionBasis: 'rules', - identifier: 'global', + identifier: INDEXING_RULE_GLOBAL, identifierType: SubgraphIdentifierType.GROUP, allocationLifetime: null, autoRenewal: true, @@ -746,6 +783,7 @@ describe('Indexing rules', () => { minAverageQueryFees: null, minSignal: null, minStake: null, + protocolNetwork: 'eip155:5', }, ]) }) @@ -760,6 +798,7 @@ describe('Indexing rules', () => { minAverageQueryFees: '1', requireSupported: false, safety: false, + protocolNetwork: 'goerli', } const deploymentInput = { @@ -769,6 +808,7 @@ describe('Indexing rules', () => { minSignal: '2', requireSupported: true, safety: true, + protocolNetwork: 'goerli', } await client.mutation(SET_INDEXING_RULE_MUTATION, { rule: globalInput }).toPromise() @@ -776,26 +816,34 @@ describe('Indexing rules', () => { .mutation(SET_INDEXING_RULE_MUTATION, { rule: deploymentInput }) .toPromise() + const globalRuleIdentifier = { + identifier: INDEXING_RULE_GLOBAL, + protocolNetwork: 'goerli', + } + const deploymentRuleIdentifier = { + identifier: '0xa4e311bfa7edabed7b31d93e0b3e751659669852ef46adbedd44dc2454db4bf3', + protocolNetwork: 'goerli', + } + await expect( client .mutation(DELETE_INDEXING_RULES_MUTATION, { - identifiers: [ - 'global', - '0xa4e311bfa7edabed7b31d93e0b3e751659669852ef46adbedd44dc2454db4bf3', - ], + identifiers: [globalRuleIdentifier, deploymentRuleIdentifier], }) .toPromise(), ).resolves.toHaveProperty('data.deleteIndexingRules', true) await expect( - client.query(INDEXING_RULES_QUERY, { merged: false }).toPromise(), + client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise(), ).resolves.toHaveProperty('data.indexingRules', [ { ...defaults.globalIndexingRule, allocationAmount: defaults.globalIndexingRule.allocationAmount.toString(), custom: null, decisionBasis: 'rules', - identifier: 'global', + identifier: INDEXING_RULE_GLOBAL, identifierType: SubgraphIdentifierType.GROUP, allocationLifetime: null, autoRenewal: true, @@ -806,7 +854,33 @@ describe('Indexing rules', () => { minStake: null, requireSupported: true, safety: true, + protocolNetwork: 'eip155:5', }, ]) }) + test('Invalid protocolNetwork value prevents rule creation', async () => { + const deploymentInput = { + identifier: 'QmZSJPm74tvhgr8uzhqvyQm2J6YSbUEj4nF6j8WxxUQLsC', + identifierType: SubgraphIdentifierType.DEPLOYMENT, + allocationAmount: '1', + minSignal: '2', + requireSupported: true, + safety: true, + protocolNetwork: 'unsupported', + } + + const result = await client + .mutation(SET_INDEXING_RULE_MUTATION, { rule: deploymentInput }) + .toPromise() + + // Mutation must not succeed + expect(result).toHaveProperty('data', null) + + // Must not create any Rule in the database + + const rows = await client + .query(INDEXING_RULES_QUERY, { merged: false, protocolNetwork: 'goerli' }) + .toPromise() + expect(rows.data.indexingRules).toEqual([]) + }) }) diff --git a/packages/indexer-common/src/indexer-management/__tests__/resolvers/poi-disputes.ts b/packages/indexer-common/src/indexer-management/__tests__/resolvers/poi-disputes.test.ts similarity index 69% rename from packages/indexer-common/src/indexer-management/__tests__/resolvers/poi-disputes.ts rename to packages/indexer-common/src/indexer-management/__tests__/resolvers/poi-disputes.test.ts index 4305addf8..86932d136 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/resolvers/poi-disputes.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/resolvers/poi-disputes.test.ts @@ -1,29 +1,19 @@ import { Sequelize } from 'sequelize' import gql from 'graphql-tag' import { + createMetrics, connectDatabase, - connectContracts, createLogger, Logger, - NetworkContracts, - parseGRT, } from '@graphprotocol/common-ts' - -import { - createIndexerManagementClient, - IndexerManagementClient, - IndexerManagementDefaults, -} from '../../client' +import { IndexerManagementClient } from '../../client' import { defineIndexerManagementModels, IndexerManagementModels, POIDisputeAttributes, } from '../../models' -import { - IndexingStatusResolver, - NetworkSubgraph, - getTestProvider, -} from '@graphprotocol/indexer-common' +import { createTestManagementClient } from '../util' +import { defineQueryFeeModels } from '../../../query-fees/models' // Make global Jest variable available // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -47,13 +37,14 @@ const STORE_POI_DISPUTES_MUTATION = gql` previousEpochStartBlockNumber previousEpochReferenceProof status + protocolNetwork } } ` const GET_POI_DISPUTE_QUERY = gql` - query dispute($allocationID: String!) { - dispute(allocationID: $allocationID) { + query dispute($identifier: POIDisputeIdentifier!) { + dispute(identifier: $identifier) { allocationID subgraphDeploymentID allocationIndexer @@ -67,13 +58,18 @@ const GET_POI_DISPUTE_QUERY = gql` previousEpochStartBlockNumber previousEpochReferenceProof status + protocolNetwork } } ` const GET_POI_DISPUTES_QUERY = gql` - query disputes($status: String!, $minClosedEpoch: Int!) { - disputes(status: $status, minClosedEpoch: $minClosedEpoch) { + query disputes($status: String!, $minClosedEpoch: Int!, $protocolNetwork: String!) { + disputes( + status: $status + minClosedEpoch: $minClosedEpoch + protocolNetwork: $protocolNetwork + ) { allocationID subgraphDeploymentID allocationIndexer @@ -87,13 +83,14 @@ const GET_POI_DISPUTES_QUERY = gql` previousEpochStartBlockNumber previousEpochReferenceProof status + protocolNetwork } } ` const DELETE_POI_DISPUTES_QUERY = gql` - mutation deleteDisputes($allocationIDs: [String!]!) { - deleteDisputes(allocationIDs: $allocationIDs) + mutation deleteDisputes($identifiers: [POIDisputeIdentifier!]!) { + deleteDisputes(identifiers: $identifiers) } ` @@ -115,6 +112,7 @@ const TEST_DISPUTE_1: POIDisputeAttributes = { previousEpochReferenceProof: '0xd04b5601739a1638719696d0735c92439267a89248c6fd21388d9600f5c942f6', status: 'potential', + protocolNetwork: 'goerli', } const TEST_DISPUTE_2: POIDisputeAttributes = { allocationID: '0x085fd2ADc1B96c26c266DecAb6A3098EA0eda619', @@ -134,6 +132,7 @@ const TEST_DISPUTE_2: POIDisputeAttributes = { previousEpochReferenceProof: '0xd04b5601739a1638719696d0735c92439267a89248c6fd21388d9600f5c942f6', status: 'potential', + protocolNetwork: 'goerli', } const TEST_DISPUTE_3: POIDisputeAttributes = { @@ -154,6 +153,7 @@ const TEST_DISPUTE_3: POIDisputeAttributes = { previousEpochReferenceProof: '0xd04b5601739a1638719696d0735c92439267a89248c6fd21388d9600f5c942f6', status: 'potential', + protocolNetwork: 'goerli', } const TEST_DISPUTES_ARRAY = [TEST_DISPUTE_1, TEST_DISPUTE_2] @@ -163,6 +163,9 @@ function toObject(dispute: POIDisputeAttributes): Record { // eslint-disable-next-line @typescript-eslint/no-explicit-any const expected: Record = Object.assign({}, dispute) expected.allocationAmount = expected.allocationAmount.toString() + if (expected.protocolNetwork === 'goerli') { + expected.protocolNetwork = 'eip155:5' + } return expected } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -177,57 +180,23 @@ function toObjectArray(disputes: POIDisputeAttributes[]): Record[] let sequelize: Sequelize let models: IndexerManagementModels -let address: string -let contracts: NetworkContracts let logger: Logger -let indexingStatusResolver: IndexingStatusResolver -let networkSubgraph: NetworkSubgraph let client: IndexerManagementClient - -const defaults = { - globalIndexingRule: { - allocationAmount: parseGRT('100'), - }, -} as IndexerManagementDefaults +const metrics = createMetrics() const setupAll = async () => { // Spin up db sequelize = await connectDatabase(__DATABASE__) models = defineIndexerManagementModels(sequelize) - address = '0xtest' - contracts = await connectContracts(getTestProvider('goerli'), 5) - await sequelize.sync({ force: true }) + defineQueryFeeModels(sequelize) + sequelize = await sequelize.sync({ force: true }) logger = createLogger({ - name: 'POI dispute tests', + name: 'Indexer API Client', async: false, level: __LOG_LEVEL__ ?? 'error', }) - const statusEndpoint = 'http://localhost:8030/graphql' - indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, - statusEndpoint, - }) - networkSubgraph = await NetworkSubgraph.create({ - logger, - endpoint: - 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-testnet', - deployment: undefined, - }) - const indexNodeIDs = ['node_1'] - client = await createIndexerManagementClient({ - models, - address, - contracts, - indexingStatusResolver, - indexNodeIDs, - networkSubgraph, - deploymentManagementEndpoint: statusEndpoint, - logger, - defaults, - features: { - injectDai: true, - }, - }) + client = await createTestManagementClient(__DATABASE__, logger, true, metrics) + logger.info('Finished setting up Test Indexer Management Client') } const setupEach = async () => { @@ -246,6 +215,7 @@ const teardownAll = async () => { } describe('POI disputes', () => { + jest.setTimeout(60_000) beforeAll(setupAll) beforeEach(setupEach) afterEach(teardownEach) @@ -265,12 +235,12 @@ describe('POI disputes', () => { }) test('Get non-existent dispute', async () => { + const identifier = { + allocationID: '0x0000000000000000000000000000000000000001', + protocolNetwork: 'goerli', + } await expect( - client - .query(GET_POI_DISPUTE_QUERY, { - allocationID: '0x0000000000000000000000000000000000000001', - }) - .toPromise(), + client.query(GET_POI_DISPUTE_QUERY, { identifier }).toPromise(), ).resolves.toHaveProperty('data.dispute', null) }) @@ -280,11 +250,14 @@ describe('POI disputes', () => { await client.mutation(STORE_POI_DISPUTES_MUTATION, { disputes: disputes }).toPromise() for (const dispute of disputes) { + const identifier = { + allocationID: dispute.allocationID, + protocolNetwork: 'eip155:5', + } + const expected = { ...dispute, protocolNetwork: 'eip155:5' } await expect( - client - .query(GET_POI_DISPUTE_QUERY, { allocationID: dispute.allocationID }) - .toPromise(), - ).resolves.toHaveProperty('data.dispute', dispute) + client.query(GET_POI_DISPUTE_QUERY, { identifier }).toPromise(), + ).resolves.toHaveProperty('data.dispute', expected) } }) @@ -293,11 +266,21 @@ describe('POI disputes', () => { await client.mutation(STORE_POI_DISPUTES_MUTATION, { disputes: disputes }).toPromise() + // Once persisted, the protocol network identifier assumes the CAIP2-ID format + const expected = disputes.map((dispute) => ({ + ...dispute, + protocolNetwork: 'eip155:5', + })) + await expect( client - .query(GET_POI_DISPUTES_QUERY, { status: 'potential', minClosedEpoch: 0 }) + .query(GET_POI_DISPUTES_QUERY, { + status: 'potential', + minClosedEpoch: 0, + protocolNetwork: 'goerli', + }) .toPromise(), - ).resolves.toHaveProperty('data.disputes', disputes) + ).resolves.toHaveProperty('data.disputes', expected) }) test('Get disputes with closed epoch greater than', async () => { @@ -305,11 +288,18 @@ describe('POI disputes', () => { await client.mutation(STORE_POI_DISPUTES_MUTATION, { disputes: disputes }).toPromise() + // Once persisted, the protocol network identifier assumes the CAIP2-ID format + const expected = [{ ...TEST_DISPUTE_2, protocolNetwork: 'eip155:5' }] + await expect( client - .query(GET_POI_DISPUTES_QUERY, { status: 'potential', minClosedEpoch: 205 }) + .query(GET_POI_DISPUTES_QUERY, { + status: 'potential', + minClosedEpoch: 205, + protocolNetwork: 'goerli', + }) .toPromise(), - ).resolves.toHaveProperty('data.disputes', [TEST_DISPUTE_2]) + ).resolves.toHaveProperty('data.disputes', expected) }) test('Remove dispute from store', async () => { @@ -317,20 +307,36 @@ describe('POI disputes', () => { await client.mutation(STORE_POI_DISPUTES_MUTATION, { disputes: disputes }).toPromise() + const identifiers = [ + { + allocationID: '0xbAd8935f75903A1eF5ea62199d98Fd7c3c1ab20C', + protocolNetwork: 'goerli', + }, + ] await expect( client .mutation(DELETE_POI_DISPUTES_QUERY, { - allocationIDs: ['0xbAd8935f75903A1eF5ea62199d98Fd7c3c1ab20C'], + identifiers, }) .toPromise(), ).resolves.toHaveProperty('data.deleteDisputes', 1) disputes.splice(0, 1) + // Once persisted, the protocol network identifier assumes the CAIP2-ID format + const expected = disputes.map((dispute) => ({ + ...dispute, + protocolNetwork: 'eip155:5', + })) + await expect( client - .query(GET_POI_DISPUTES_QUERY, { status: 'potential', minClosedEpoch: 0 }) + .query(GET_POI_DISPUTES_QUERY, { + status: 'potential', + minClosedEpoch: 0, + protocolNetwork: 'goerli', + }) .toPromise(), - ).resolves.toHaveProperty('data.disputes', disputes) + ).resolves.toHaveProperty('data.disputes', expected) }) test('Remove multiple disputes from store', async () => { @@ -338,22 +344,36 @@ describe('POI disputes', () => { await client.mutation(STORE_POI_DISPUTES_MUTATION, { disputes: disputes }).toPromise() + const identifiers = [ + { + allocationID: '0xbAd8935f75903A1eF5ea62199d98Fd7c3c1ab20C', + protocolNetwork: 'goerli', + }, + { + allocationID: '0x085fd2ADc1B96c26c266DecAb6A3098EA0eda619', + protocolNetwork: 'goerli', + }, + ] + await expect( - client - .mutation(DELETE_POI_DISPUTES_QUERY, { - allocationIDs: [ - '0xbAd8935f75903A1eF5ea62199d98Fd7c3c1ab20C', - '0x085fd2ADc1B96c26c266DecAb6A3098EA0eda619', - ], - }) - .toPromise(), + client.mutation(DELETE_POI_DISPUTES_QUERY, { identifiers }).toPromise(), ).resolves.toHaveProperty('data.deleteDisputes', 2) disputes.splice(0, 2) + // Once persisted, the protocol network identifier assumes the CAIP2-ID format + const expected = disputes.map((dispute) => ({ + ...dispute, + protocolNetwork: 'eip155:5', + })) + await expect( client - .query(GET_POI_DISPUTES_QUERY, { status: 'potential', minClosedEpoch: 0 }) + .query(GET_POI_DISPUTES_QUERY, { + status: 'potential', + minClosedEpoch: 0, + protocolNetwork: 'goerli', + }) .toPromise(), - ).resolves.toHaveProperty('data.disputes', disputes) + ).resolves.toHaveProperty('data.disputes', expected) }) }) diff --git a/packages/indexer-common/src/indexer-management/__tests__/util.ts b/packages/indexer-common/src/indexer-management/__tests__/util.ts new file mode 100644 index 000000000..974cfca16 --- /dev/null +++ b/packages/indexer-common/src/indexer-management/__tests__/util.ts @@ -0,0 +1,183 @@ +import { + ActionInput, + ActionStatus, + ActionType, + createIndexerManagementClient, + defineIndexerManagementModels, + defineQueryFeeModels, + GraphNode, + IndexerManagementClient, + IndexerManagementDefaults, + MultiNetworks, + Network, + specification, +} from '@graphprotocol/indexer-common' +import { connectDatabase, Metrics, Logger, parseGRT } from '@graphprotocol/common-ts' + +const PUBLIC_JSON_RPC_ENDPOINT = 'https://ethereum-goerli.publicnode.com' + +const testProviderUrl = + process.env.INDEXER_TEST_JRPC_PROVIDER_URL ?? PUBLIC_JSON_RPC_ENDPOINT + +export const testNetworkSpecification: specification.NetworkSpecification = + specification.NetworkSpecification.parse({ + networkIdentifier: 'goerli', + gateway: { + url: 'http://localhost:8030/', + }, + networkProvider: { + url: testProviderUrl, + }, + indexerOptions: { + address: '0xf56b5d582920E4527A818FBDd801C0D80A394CB8', + mnemonic: + 'famous aspect index polar tornado zero wedding electric floor chalk tenant junk', + url: 'http://test-indexer.xyz', + }, + subgraphs: { + networkSubgraph: { + url: 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli', + }, + epochSubgraph: { + url: 'http://test-url.xyz', + }, + }, + transactionMonitoring: { + gasIncreaseTimeout: 240000, + gasIncreaseFactor: 1.2, + baseFeePerGasMax: 100 * 10 ** 9, + maxTransactionAttempts: 0, + }, + dai: { + contractAddress: '0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448', + inject: false, + }, + }) + +export const createTestManagementClient = async ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + databaseOptions: any, + logger: Logger, + injectDai: boolean, + metrics: Metrics, +): Promise => { + // Clearing the registry prevents duplicate metric registration in the default registry. + metrics.registry.clear() + + // Spin up db + let sequelize = await connectDatabase(databaseOptions) + const queryFeeModels = defineQueryFeeModels(sequelize) + const managementModels = defineIndexerManagementModels(sequelize) + sequelize = await sequelize.sync({ force: true }) + const statusEndpoint = 'http://localhost:8030/graphql' + const graphNode = new GraphNode( + logger, + 'http://test-admin-endpoint.xyz', + 'https://test-query-endpoint.xyz', + statusEndpoint, + [], + ) + const indexNodeIDs = ['node_1'] + + const networkSpecification = { ...testNetworkSpecification } + networkSpecification.dai.inject = injectDai + + const defaults: IndexerManagementDefaults = { + globalIndexingRule: { + allocationAmount: parseGRT('100'), + parallelAllocations: 1, + requireSupported: true, + safety: true, + protocolNetwork: 'goerli', + }, + } + + const network = await Network.create( + logger, + networkSpecification, + queryFeeModels, + graphNode, + metrics, + ) + + const multiNetworks = new MultiNetworks( + [network], + (n: Network) => n.specification.networkIdentifier, + ) + + return await createIndexerManagementClient({ + models: managementModels, + graphNode, + indexNodeIDs, + logger, + defaults, + multiNetworks, + }) +} + +export const defaults: IndexerManagementDefaults = { + globalIndexingRule: { + allocationAmount: parseGRT('100'), + parallelAllocations: 1, + requireSupported: true, + safety: true, + }, +} + +export const subgraphDeployment1 = 'Qmew9PZUJCoDzXqqU6vGyTENTKHrrN4dy5h94kertfudqy' +export const subgraphDeployment2 = 'QmWq1pmnhEvx25qxpYYj9Yp6E1xMKMVoUjXVQBxUJmreSe' +export const subgraphDeployment3 = 'QmRhH2nhNibDVPZmYqq3TUZZARZ77vgjYCvPNiGBCogtgM' +export const notPublishedSubgraphDeployment = + 'QmeqJ6hsdyk9dVbo1tvRgAxWrVS3rkERiEMsxzPShKLco6' + +export const queuedAllocateAction = { + status: ActionStatus.QUEUED, + type: ActionType.ALLOCATE, + deploymentID: subgraphDeployment1, + amount: '10000', + force: false, + source: 'indexerAgent', + reason: 'indexingRule', + priority: 0, + protocolNetwork: 'goerli', +} as ActionInput + +export const allocateToNotPublishedDeployment = { + status: ActionStatus.QUEUED, + type: ActionType.ALLOCATE, + deploymentID: notPublishedSubgraphDeployment, + amount: '10000', + force: false, + source: 'indexerAgent', + reason: 'indexingRule', + priority: 0, + protocolNetwork: 'goerli', +} as ActionInput + +export const invalidUnallocateAction = { + status: ActionStatus.QUEUED, + type: ActionType.UNALLOCATE, + allocationID: '0x8f63930129e585c69482b56390a09b6b176f4a4c', + deploymentID: subgraphDeployment1, + amount: undefined, + poi: undefined, + force: false, + source: 'indexerAgent', + reason: 'indexingRule', + priority: 0, + protocolNetwork: 'goerli', +} as ActionInput + +export const invalidReallocateAction = { + status: ActionStatus.QUEUED, + type: ActionType.REALLOCATE, + deploymentID: subgraphDeployment1, + allocationID: '0x8f63930129e585c69482b56390a09b6b176f4a4c', + poi: undefined, + amount: undefined, + force: false, + source: 'indexerAgent', + reason: 'indexingRule', + priority: 0, + protocolNetwork: 'goerli', +} as ActionInput From a5f752212019ad1584041f8c6167b43fa1e95fbf Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:08:21 -0300 Subject: [PATCH 07/16] cli: update modules for the new protocolNetwork field --- packages/indexer-cli/src/actions.ts | 14 +++ packages/indexer-cli/src/allocations.ts | 39 ++++++-- packages/indexer-cli/src/command-helpers.ts | 45 +++++++++ .../src/commands/indexer/actions/approve.ts | 21 +++- .../src/commands/indexer/actions/cancel.ts | 11 ++- .../src/commands/indexer/actions/execute.ts | 10 +- .../src/commands/indexer/actions/get.ts | 25 ++++- .../src/commands/indexer/actions/queue.ts | 25 ++++- .../src/commands/indexer/actions/update.ts | 13 ++- .../src/commands/indexer/allocations/close.ts | 41 ++++---- .../commands/indexer/allocations/create.ts | 27 ++++-- .../src/commands/indexer/allocations/get.ts | 10 +- .../indexer/allocations/reallocate.ts | 18 +++- .../src/commands/indexer/disputes/get.ts | 14 ++- .../src/commands/indexer/rules/clear.ts | 9 +- .../src/commands/indexer/rules/delete.ts | 84 ++++++++++++---- .../src/commands/indexer/rules/get.ts | 27 ++++-- .../src/commands/indexer/rules/maybe.ts | 9 +- .../src/commands/indexer/rules/offchain.ts | 10 +- .../src/commands/indexer/rules/set.ts | 7 +- .../src/commands/indexer/rules/start.ts | 9 +- .../src/commands/indexer/rules/stop.ts | 9 +- .../src/commands/indexer/status.ts | 95 +++++++++++++------ packages/indexer-cli/src/disputes.ts | 20 +++- packages/indexer-cli/src/rules.ts | 26 +++-- 25 files changed, 490 insertions(+), 128 deletions(-) diff --git a/packages/indexer-cli/src/actions.ts b/packages/indexer-cli/src/actions.ts index c4b9a944f..1fa975b18 100644 --- a/packages/indexer-cli/src/actions.ts +++ b/packages/indexer-cli/src/actions.ts @@ -10,6 +10,7 @@ import { nullPassThrough, OrderDirection, parseBoolean, + validateNetworkIdentifier, } from '@graphprotocol/indexer-common' import { validatePOI, validateRequiredParams } from './command-helpers' import gql from 'graphql-tag' @@ -32,6 +33,7 @@ export async function buildActionInput( reason: string, status: ActionStatus, priority: number, + protocolNetwork: string, ): Promise { await validateActionInput(type, actionParams) switch (type) { @@ -44,6 +46,7 @@ export async function buildActionInput( reason, status, priority, + protocolNetwork, } case ActionType.UNALLOCATE: { let poi = actionParams.param2 @@ -60,6 +63,7 @@ export async function buildActionInput( reason, status, priority, + protocolNetwork, } } case ActionType.REALLOCATE: { @@ -78,6 +82,7 @@ export async function buildActionInput( reason, status, priority, + protocolNetwork, } } } @@ -182,6 +187,7 @@ export async function queueActions( reason priority status + protocolNetwork } } `, @@ -206,6 +212,7 @@ const ACTION_PARAMS_PARSERS: Record any> type: x => validateActionType(x), status: x => validateActionStatus(x), reason: nullPassThrough, + protocolNetwork: x => validateNetworkIdentifier(x), } /** @@ -234,6 +241,7 @@ export async function executeApprovedActions( mutation executeApprovedActions { executeApprovedActions { id + protocolNetwork status type deploymentID @@ -279,6 +287,7 @@ export async function approveActions( priority transaction status + protocolNetwork } } `, @@ -303,6 +312,7 @@ export async function cancelActions( mutation cancelActions($actionIDs: [Int!]!) { cancelActions(actionIDs: $actionIDs) { id + protocolNetwork type allocationID deploymentID @@ -338,6 +348,7 @@ export async function fetchAction( query action($actionID: Int!) { action(actionID: $actionID) { id + protocolNetwork type allocationID deploymentID @@ -386,6 +397,7 @@ export async function fetchActions( first: $first ) { id + protocolNetwork type allocationID deploymentID @@ -422,6 +434,7 @@ export async function deleteActions( mutation deleteActions($actionIDs: [Int!]!) { deleteActions(actionIDs: $actionIDs) { id + protocolNetwork type allocationID deploymentID @@ -471,6 +484,7 @@ export async function updateActions( transaction status failureReason + protocolNetwork } } `, diff --git a/packages/indexer-cli/src/allocations.ts b/packages/indexer-cli/src/allocations.ts index a0bc6660c..1133746a8 100644 --- a/packages/indexer-cli/src/allocations.ts +++ b/packages/indexer-cli/src/allocations.ts @@ -10,6 +10,7 @@ import { CloseAllocationResult, CreateAllocationResult, ReallocateAllocationResult, + resolveChainAlias, } from '@graphprotocol/indexer-common' export interface IndexerAllocation { @@ -28,6 +29,7 @@ export interface IndexerAllocation { indexingRewards: BigNumber queryFeesCollected: BigNumber status: string + protocolNetwork: string } const ALLOCATION_CONVERTERS_FROM_GRAPHQL: Record< @@ -51,6 +53,7 @@ const ALLOCATION_CONVERTERS_FROM_GRAPHQL: Record< indexingRewards: nullPassThrough((x: string) => BigNumber.from(x)), queryFeesCollected: nullPassThrough((x: string) => BigNumber.from(x)), status: x => x, + protocolNetwork: x => x, } const ALLOCATION_FORMATTERS: Record< @@ -73,6 +76,7 @@ const ALLOCATION_FORMATTERS: Record< indexingRewards: x => utils.commify(formatGRT(x)), queryFeesCollected: x => utils.commify(formatGRT(x)), status: x => x, + protocolNetwork: resolveChainAlias, } /** @@ -171,6 +175,7 @@ export const createAllocation = async ( deployment: string, amount: BigNumber, indexNode: string | undefined, + protocolNetwork: string, ): Promise => { const result = await client .mutation( @@ -178,16 +183,19 @@ export const createAllocation = async ( mutation createAllocation( $deployment: String! $amount: String! + $protocolNetwork: String! $indexNode: String ) { createAllocation( deployment: $deployment amount: $amount + protocolNetwork: $protocolNetwork indexNode: $indexNode ) { allocation deployment allocatedTokens + protocolNetwork } } `, @@ -195,6 +203,7 @@ export const createAllocation = async ( deployment, amount: amount.toString(), indexNode, + protocolNetwork, }, ) .toPromise() @@ -211,23 +220,36 @@ export const closeAllocation = async ( allocationID: string, poi: string | undefined, force: boolean, + protocolNetwork: string, ): Promise => { const result = await client .mutation( gql` - mutation closeAllocation($allocation: String!, $poi: String, $force: Boolean) { - closeAllocation(allocation: $allocation, poi: $poi, force: $force) { + mutation closeAllocation( + $allocation: String! + $poi: String + $force: Boolean + $protocolNetwork: String! + ) { + closeAllocation( + allocation: $allocation + poi: $poi + force: $force + protocolNetwork: $protocolNetwork + ) { allocation allocatedTokens indexingRewards receiptsWorthCollecting + protocolNetwork } } `, { allocation: allocationID, - poi: poi, - force: force, + poi, + force, + protocolNetwork, }, ) .toPromise() @@ -245,6 +267,7 @@ export const reallocateAllocation = async ( poi: string | undefined, amount: BigNumber, force: boolean, + protocolNetwork: string, ): Promise => { const result = await client .mutation( @@ -254,26 +277,30 @@ export const reallocateAllocation = async ( $poi: String $amount: String! $force: Boolean + $protocolNetwork: String! ) { reallocateAllocation( allocation: $allocation poi: $poi amount: $amount force: $force + protocolNetwork: $protocolNetwork ) { closedAllocation indexingRewardsCollected receiptsWorthCollecting createdAllocation createdAllocationStake + protocolNetwork } } `, { allocation: allocationID, - poi: poi, + poi, amount: amount.toString(), - force: force, + force, + protocolNetwork, }, ) .toPromise() diff --git a/packages/indexer-cli/src/command-helpers.ts b/packages/indexer-cli/src/command-helpers.ts index 65995927d..2d8b1aef8 100644 --- a/packages/indexer-cli/src/command-helpers.ts +++ b/packages/indexer-cli/src/command-helpers.ts @@ -36,6 +36,7 @@ export enum OutputFormat { import yaml from 'yaml' import { GluegunParameters, GluegunPrint } from 'gluegun' import { utils } from 'ethers' +import { validateNetworkIdentifier } from '@graphprotocol/indexer-common' export const fixParameters = ( parameters: GluegunParameters, @@ -206,3 +207,47 @@ export function suggestCommands( ) return suggestions.length > 0 ? suggestions : supported_commands } + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function extractProtocolNetworkOption( + options: { + [key: string]: any + }, + required = false, +): string | undefined { + const { n, network } = options + + // Tries to extract the --network option from Gluegun options. + // Throws if required is set to true and the option is not found. + if (!n && !network) { + if (required) { + throw new Error("The option '--network' is required") + } else { + return undefined + } + } + + // Check for invalid usage + const allowedUsages = + (n === undefined && typeof network === 'string') || + (network === undefined && typeof n === 'string') + if (!allowedUsages) { + throw new Error("Invalid usage of the option '--network'") + } + const input = (network ?? n) as string + + try { + return validateNetworkIdentifier(input) + } catch (parseError) { + throw new Error(`Invalid value for the option '--network'. ${parseError}`) + } +} + +// Same as `extractProtocolNetworkOption`, but always require the --network option to be set +export function requireProtocolNetworkOption(options: { [key: string]: any }): string { + const protocolNetwork = extractProtocolNetworkOption(options, true) + if (!protocolNetwork) { + throw new Error("The option '--network' is required") + } + return protocolNetwork +} diff --git a/packages/indexer-cli/src/commands/indexer/actions/approve.ts b/packages/indexer-cli/src/commands/indexer/actions/approve.ts index 315a9e5c4..00e71e197 100644 --- a/packages/indexer-cli/src/commands/indexer/actions/approve.ts +++ b/packages/indexer-cli/src/commands/indexer/actions/approve.ts @@ -9,7 +9,7 @@ import { parseOutputFormat, } from '../../../command-helpers' import { approveActions, fetchActions } from '../../../actions' -import { ActionStatus } from '@graphprotocol/indexer-common' +import { ActionStatus, resolveChainAlias } from '@graphprotocol/indexer-common' const HELP = ` ${chalk.bold('graph indexer actions approve')} [options] [ ...] @@ -18,7 +18,7 @@ ${chalk.bold('graph indexer actions approve')} [options] queued ${chalk.dim('Options:')} -h, --help Show usage information - -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML + -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` module.exports = { @@ -67,6 +67,17 @@ module.exports = { numericActionIDs = actionIDs.map(action => +action) } + // Ensure all provided actionIDs are positive numbers + const invalidActionIDs: string[] = [] + numericActionIDs.forEach((id, index) => { + if (isNaN(id) || id < 1) { + invalidActionIDs.push(actionIDs[index]) + } + }) + if (invalidActionIDs.length > 0) { + throw Error(`Invaild action IDs: ${invalidActionIDs}`) + } + inputSpinner.succeed('Processed input parameters') } catch (error) { inputSpinner.fail(error.toString()) @@ -79,10 +90,16 @@ module.exports = { try { const queuedAction = await approveActions(client, numericActionIDs) + // Format Actions 'protocolNetwork' field to display human-friendly chain aliases instead of CAIP2-IDs + queuedAction.forEach( + action => (action.protocolNetwork = resolveChainAlias(action.protocolNetwork)), + ) + actionSpinner.succeed(`Actions approved`) printObjectOrArray(print, outputFormat, queuedAction, [ 'id', 'type', + 'protocolNetwork', 'deploymentID', 'allocationID', 'amount', diff --git a/packages/indexer-cli/src/commands/indexer/actions/cancel.ts b/packages/indexer-cli/src/commands/indexer/actions/cancel.ts index ea75b22a7..d5b74701c 100644 --- a/packages/indexer-cli/src/commands/indexer/actions/cancel.ts +++ b/packages/indexer-cli/src/commands/indexer/actions/cancel.ts @@ -1,6 +1,6 @@ import { GluegunToolbox } from 'gluegun' import chalk from 'chalk' - +import { resolveChainAlias } from '@graphprotocol/indexer-common' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' import { fixParameters, printObjectOrArray } from '../../../command-helpers' @@ -12,7 +12,7 @@ ${chalk.bold('graph indexer actions cancel')} [options] [ ...] ${chalk.dim('Options:')} -h, --help Show usage information - -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML + -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` module.exports = { @@ -66,8 +66,15 @@ module.exports = { const queuedAction = await cancelActions(client, numericActionIDs) actionSpinner.succeed(`Actions canceled`) + + // Format Actions 'protocolNetwork' field to display human-friendly chain aliases instead of CAIP2-IDs + queuedAction.forEach( + action => (action.protocolNetwork = resolveChainAlias(action.protocolNetwork)), + ) + printObjectOrArray(print, outputFormat, queuedAction, [ 'id', + 'protocolNetwork', 'type', 'deploymentID', 'allocationID', diff --git a/packages/indexer-cli/src/commands/indexer/actions/execute.ts b/packages/indexer-cli/src/commands/indexer/actions/execute.ts index d8e27c85b..a742f21cd 100644 --- a/packages/indexer-cli/src/commands/indexer/actions/execute.ts +++ b/packages/indexer-cli/src/commands/indexer/actions/execute.ts @@ -1,6 +1,6 @@ import { GluegunToolbox } from 'gluegun' import chalk from 'chalk' - +import { resolveChainAlias } from '@graphprotocol/indexer-common' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' import { fixParameters, printObjectOrArray } from '../../../command-helpers' @@ -12,7 +12,7 @@ ${chalk.bold('graph indexer actions execute approved')} [options] ${chalk.dim('Options:')} -h, --help Show usage information - -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML + -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` module.exports = { @@ -60,8 +60,14 @@ module.exports = { spinner.succeed(`Executed approved actions`) + // Format Actions 'protocolNetwork' field to display human-friendly chain aliases instead of CAIP2-IDs + executedActions.forEach( + action => (action.protocolNetwork = resolveChainAlias(action.protocolNetwork)), + ) + printObjectOrArray(print, outputFormat, executedActions, [ 'id', + 'protocolNetwork', 'status', 'type', 'deploymentID', diff --git a/packages/indexer-cli/src/commands/indexer/actions/get.ts b/packages/indexer-cli/src/commands/indexer/actions/get.ts index dc805ca34..553e0566b 100644 --- a/packages/indexer-cli/src/commands/indexer/actions/get.ts +++ b/packages/indexer-cli/src/commands/indexer/actions/get.ts @@ -6,20 +6,23 @@ import { ActionParams, ActionResult, OrderDirection, + resolveChainAlias, } from '@graphprotocol/indexer-common' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters, printObjectOrArray } from '../../../command-helpers' +import { + fixParameters, + printObjectOrArray, + extractProtocolNetworkOption, +} from '../../../command-helpers' import { fetchAction, fetchActions } from '../../../actions' const HELP = ` ${chalk.bold('graph indexer actions get')} [options] -${chalk.bold('graph indexer allocations get')} [options] -${chalk.bold('graph indexer allocations get')} [options] all - ${chalk.dim('Options:')} -h, --help Show usage information + -n, --network Filter by protocol network --type allocate|unallocate|reallocate|collect Filter by type --status queued|approved|pending|success|failed|canceled Filter by status --source Fetch only actions queued by a specific source @@ -33,6 +36,7 @@ ${chalk.dim('Options:')} const actionFields: (keyof Action)[] = [ 'id', + 'protocolNetwork', 'type', 'deploymentID', 'allocationID', @@ -92,6 +96,10 @@ module.exports = { let orderDirectionValue = OrderDirection.DESC const outputFormat = o || output || 'table' + const protocolNetwork: string | undefined = extractProtocolNetworkOption( + parameters.options, + ) + if (help || h) { inputSpinner.stopAndPersist({ symbol: '💁', text: HELP }) return @@ -125,7 +133,7 @@ module.exports = { if (action == 'all') { if (type || status || source || reason) { throw Error( - `Invalid query, cannot specify '--type', '--status', '--source' or '--reason' filters in addition to 'action = all'`, + `Invalid query, cannot specify '--type', '--status', '--source', or '--reason' filters in addition to 'action = all'`, ) } } @@ -186,6 +194,7 @@ module.exports = { status, source, reason, + protocolNetwork, }, first, orderByParam, @@ -202,6 +211,12 @@ module.exports = { const displayProperties = actionFields.filter(field => selectedFields.includes(field), ) + + // Format Actions 'protocolNetwork' field to display human-friendly chain aliases instead of CAIP2-IDs + actions.forEach( + action => (action.protocolNetwork = resolveChainAlias(action.protocolNetwork)), + ) + printObjectOrArray(print, outputFormat, actions, displayProperties) } catch (error) { actionSpinner.fail(error.toString()) diff --git a/packages/indexer-cli/src/commands/indexer/actions/queue.ts b/packages/indexer-cli/src/commands/indexer/actions/queue.ts index 76ff9f07d..76393eddb 100644 --- a/packages/indexer-cli/src/commands/indexer/actions/queue.ts +++ b/packages/indexer-cli/src/commands/indexer/actions/queue.ts @@ -3,9 +3,16 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { printObjectOrArray } from '../../../command-helpers' +import { + requireProtocolNetworkOption, + printObjectOrArray, +} from '../../../command-helpers' import { buildActionInput, queueActions, validateActionType } from '../../../actions' -import { ActionInput, ActionStatus } from '@graphprotocol/indexer-common' +import { + ActionInput, + ActionStatus, + resolveChainAlias, +} from '@graphprotocol/indexer-common' const HELP = ` ${chalk.bold( @@ -21,8 +28,9 @@ ${chalk.bold( ${chalk.dim('Options:')} - -h, --help Show usage information - -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML + -h, --help Show usage informatio + -n, --network [REQUIRED] The protocol network for this action + -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML -s, --source Specify the source of the action decision -r, --reason Specify the reason for the action to be taken -p, --priority Define a priority order for the action @@ -65,6 +73,8 @@ module.exports = { ) } + const networkIdentifier = requireProtocolNetworkOption(parameters.options) + actionInputParams = await buildActionInput( validateActionType(type), { targetDeployment, param1, param2, param3, param4 }, @@ -72,6 +82,7 @@ module.exports = { decisionReason, ActionStatus.QUEUED, executionPriority, + networkIdentifier, ) inputSpinner.succeed(`Processed input parameters`) @@ -93,8 +104,14 @@ module.exports = { actionSpinner.succeed(`${type} action added to queue`) + // Format Actions 'protocolNetwork' field to display human-friendly chain aliases instead of CAIP2-IDs + queuedAction.forEach( + action => (action.protocolNetwork = resolveChainAlias(action.protocolNetwork)), + ) + printObjectOrArray(print, outputFormat, queuedAction, [ 'id', + 'protocolNetwork', 'type', 'deploymentID', 'allocationID', diff --git a/packages/indexer-cli/src/commands/indexer/actions/update.ts b/packages/indexer-cli/src/commands/indexer/actions/update.ts index 4beaaaaf2..6ea470cac 100644 --- a/packages/indexer-cli/src/commands/indexer/actions/update.ts +++ b/packages/indexer-cli/src/commands/indexer/actions/update.ts @@ -1,7 +1,12 @@ import { GluegunToolbox } from 'gluegun' import chalk from 'chalk' -import { Action, ActionFilter, ActionUpdateInput } from '@graphprotocol/indexer-common' +import { + Action, + ActionFilter, + ActionUpdateInput, + resolveChainAlias, +} from '@graphprotocol/indexer-common' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' import { fixParameters, printObjectOrArray } from '../../../command-helpers' @@ -98,6 +103,7 @@ module.exports = { const displayProperties: (keyof Action)[] = [ 'id', 'type', + 'protocolNetwork', 'deploymentID', 'allocationID', 'amount', @@ -111,6 +117,11 @@ module.exports = { 'reason', ] + // Format Actions 'protocolNetwork' field to display human-friendly chain aliases instead of CAIP2-IDs + actionsUpdated.forEach( + action => (action.protocolNetwork = resolveChainAlias(action.protocolNetwork)), + ) + printObjectOrArray(print, outputFormat, actionsUpdated, displayProperties) } catch (error) { actionSpinner.fail(error.toString()) diff --git a/packages/indexer-cli/src/commands/indexer/allocations/close.ts b/packages/indexer-cli/src/commands/indexer/allocations/close.ts index 652082b5c..7bf5d9117 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/close.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/close.ts @@ -3,18 +3,18 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { utils } from 'ethers' import { closeAllocation } from '../../../allocations' -import { printObjectOrArray } from '../../../command-helpers' +import { validatePOI, printObjectOrArray } from '../../../command-helpers' +import { validateNetworkIdentifier } from '@graphprotocol/indexer-common' const HELP = ` -${chalk.bold('graph indexer allocations close')} [options] +${chalk.bold('graph indexer allocations close')} [options] ${chalk.dim('Options:')} -h, --help Show usage information -f, --force Bypass POIaccuracy checks and submit transaction with provided data - -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML + -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` module.exports = { @@ -43,8 +43,7 @@ module.exports = { return } - const [id] = parameters.array || [] - let [, poi] = parameters.array || [] + const [network, id, unformattedPoi] = parameters.array || [] if (id === undefined) { spinner.fail(`Missing required argument: 'id'`) @@ -53,28 +52,36 @@ module.exports = { return } - if (poi !== undefined) { - if (typeof poi == 'number' && poi == 0) { - poi = utils.hexlify(Array(32).fill(0)) - } + let protocolNetwork: string + if (!network) { + spinner.fail(`Missing required argument: 'network'`) + print.info(HELP) + process.exitCode = 1 + return + } else { try { - // Ensure user provided POI is formatted properly - '0x...' (32 bytes) - const isHex = utils.isHexString(poi, 32) - if (!isHex) { - throw new Error('Must be a 32 byte length hex string') - } + protocolNetwork = validateNetworkIdentifier(network) } catch (error) { - spinner.fail(`Invalid POI provided, '${poi}'. ` + error.toString()) + spinner.fail(`Invalid value for argument 'network': '${network}' `) process.exitCode = 1 return } } + let poi: string | undefined + try { + poi = validatePOI(unformattedPoi) + } catch (error) { + spinner.fail(`Invalid POI provided, '${unformattedPoi}'. ` + error.message()) + process.exitCode = 1 + return + } + spinner.text = `Closing allocation '${id}` try { const config = loadValidatedConfig() const client = await createIndexerManagementClient({ url: config.api }) - const closeResult = await closeAllocation(client, id, poi, toForce) + const closeResult = await closeAllocation(client, id, poi, toForce, protocolNetwork) spinner.succeed('Allocation closed') printObjectOrArray(print, outputFormat, closeResult, [ diff --git a/packages/indexer-cli/src/commands/indexer/allocations/create.ts b/packages/indexer-cli/src/commands/indexer/allocations/create.ts index 64c49a450..84f15f8b2 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/create.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/create.ts @@ -5,19 +5,23 @@ import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' import { BigNumber } from 'ethers' import { createAllocation } from '../../../allocations' -import { processIdentifier, SubgraphIdentifierType } from '@graphprotocol/indexer-common' +import { + processIdentifier, + SubgraphIdentifierType, + validateNetworkIdentifier, +} from '@graphprotocol/indexer-common' import { printObjectOrArray } from '../../../command-helpers' const HELP = ` ${chalk.bold( 'graph indexer allocations create', -)} [options] +)} [options] ${chalk.dim('Options:')} -h, --help Show usage information -f, --force Bypass POI accuracy checks and submit transaction with provided data - -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML + -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` module.exports = { @@ -45,14 +49,23 @@ module.exports = { return } - const [deploymentID, amount, indexNode] = parameters.array || [] + const [deploymentID, protocolNetwork, amount, indexNode] = parameters.array || [] try { - if (!deploymentID || !amount) { + if (!deploymentID || !amount || !protocolNetwork) { throw new Error( - `Must provide a deployment ID and allocation amount (deploymentID: '${deploymentID}', allocationAmount: '${amount}'`, + 'Must provide a deployment ID, a network identifier and allocation amount' + + `(deploymentID: '${deploymentID}', network: '${protocolNetwork}' allocationAmount: '${amount}')`, ) } + + // This nested try block is necessary to complement the parsing error with the 'network' field. + try { + validateNetworkIdentifier(protocolNetwork) + } catch (parsingError) { + throw new Error(`Invalid 'network' provided. ${parsingError}`) + } + const [deploymentString, type] = await processIdentifier(deploymentID, { all: false, global: false, @@ -73,6 +86,7 @@ module.exports = { deploymentString, allocationAmount, indexNode, + protocolNetwork, ) spinner.succeed('Allocation created') @@ -80,6 +94,7 @@ module.exports = { 'allocation', 'deployment', 'allocatedTokens', + 'protocolNetwork', ]) } catch (error) { spinner.fail(error.toString()) diff --git a/packages/indexer-cli/src/commands/indexer/allocations/get.ts b/packages/indexer-cli/src/commands/indexer/allocations/get.ts index 39b924c47..d4fa0c7a0 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/get.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/get.ts @@ -3,7 +3,7 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters } from '../../../command-helpers' +import { extractProtocolNetworkOption, fixParameters } from '../../../command-helpers' import gql from 'graphql-tag' import { SubgraphDeploymentID } from '@graphprotocol/common-ts' import { processIdentifier, SubgraphIdentifierType } from '@graphprotocol/indexer-common' @@ -18,7 +18,8 @@ ${chalk.bold('graph indexer allocations get')} [options] all ${chalk.dim('Options:')} -h, --help Show usage information - --status active|closed|claimable Filter by status + -n, --network Filter allocations by their protocol network + --status active|closed|claimable Filter by status --deployment Fetch only allocations for a specific subgraph deployment -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -43,6 +44,8 @@ module.exports = { } try { + const protocolNetwork = extractProtocolNetworkOption(parameters.options) + if (!['json', 'yaml', 'table'].includes(outputFormat)) { throw Error( `Invalid output format "${outputFormat}" must be one of 'json', 'yaml' or 'table'`, @@ -96,6 +99,7 @@ module.exports = { query allocations($filter: AllocationFilter!) { allocations(filter: $filter) { id + protocolNetwork indexer subgraphDeployment allocatedTokens @@ -117,6 +121,7 @@ module.exports = { filter: { status: status ? status : null, allocation: allocation ? allocation : null, + protocolNetwork, }, }, ) @@ -140,6 +145,7 @@ module.exports = { let displayProperties: (keyof IndexerAllocation)[] = [ 'id', + 'protocolNetwork', 'indexer', 'subgraphDeployment', 'allocatedTokens', diff --git a/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts b/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts index 3f1c5c579..ba40f0022 100644 --- a/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts +++ b/packages/indexer-cli/src/commands/indexer/allocations/reallocate.ts @@ -8,12 +8,14 @@ import { reallocateAllocation } from '../../../allocations' import { printObjectOrArray, validatePOI } from '../../../command-helpers' const HELP = ` -${chalk.bold('graph indexer allocations reallocate')} [options] +${chalk.bold( + 'graph indexer allocations reallocate', +)} [options] ${chalk.dim('Options:')} -h, --help Show usage information - -f, --force Bypass POI accuracy checks and submit transaction with provided data + -f, --force Bypass POI accuracy checks and submit transaction with provided data ` module.exports = { @@ -43,7 +45,14 @@ module.exports = { } // eslint-disable-next-line prefer-const - let [id, amount, poi] = parameters.array || [] + let [network, id, amount, poi] = parameters.array || [] + + if (network === undefined) { + spinner.fail(`Missing required argument: 'network'`) + print.info(HELP) + process.exitCode = 1 + return + } if (id === undefined) { spinner.fail(`Missing required argument: 'id'`) @@ -60,7 +69,7 @@ module.exports = { } try { - await validatePOI(poi) + validatePOI(poi) const allocationAmount = BigNumber.from(amount) const config = loadValidatedConfig() const client = await createIndexerManagementClient({ url: config.api }) @@ -72,6 +81,7 @@ module.exports = { poi, allocationAmount, toForce, + network, ) spinner.succeed('Reallocated') diff --git a/packages/indexer-cli/src/commands/indexer/disputes/get.ts b/packages/indexer-cli/src/commands/indexer/disputes/get.ts index 4dd46cd17..c945d3bb7 100644 --- a/packages/indexer-cli/src/commands/indexer/disputes/get.ts +++ b/packages/indexer-cli/src/commands/indexer/disputes/get.ts @@ -4,15 +4,16 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' import { disputes, printDisputes } from '../../../disputes' -import { parseOutputFormat } from '../../../command-helpers' +import { parseOutputFormat, extractProtocolNetworkOption } from '../../../command-helpers' const HELP = ` ${chalk.bold( 'graph indexer disputes get', )} [options] potential - + ${chalk.dim('Options:')} + -n, --network Filter by protocol network -h, --help Show usage information -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -54,8 +55,13 @@ module.exports = { // Create indexer API client const client = await createIndexerManagementClient({ url: config.api }) try { - const storedDisputes = await disputes(client, status, +minAllocationClosedEpoch) - + const protocolNetwork = extractProtocolNetworkOption(parameters.options) + const storedDisputes = await disputes( + client, + status, + +minAllocationClosedEpoch, + protocolNetwork, + ) printDisputes(print, outputFormat, storedDisputes) } catch (error) { print.error(error.toString()) diff --git a/packages/indexer-cli/src/commands/indexer/rules/clear.ts b/packages/indexer-cli/src/commands/indexer/rules/clear.ts index 44a010748..11116bb29 100644 --- a/packages/indexer-cli/src/commands/indexer/rules/clear.ts +++ b/packages/indexer-cli/src/commands/indexer/rules/clear.ts @@ -4,7 +4,11 @@ import { partition } from '@thi.ng/iterators' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters, parseOutputFormat } from '../../../command-helpers' +import { + requireProtocolNetworkOption, + fixParameters, + parseOutputFormat, +} from '../../../command-helpers' import { setIndexingRule, displayRules, parseIndexingRule } from '../../../rules' import { processIdentifier } from '@graphprotocol/indexer-common' @@ -17,6 +21,7 @@ ${chalk.bold('graph indexer rules reset')} [options] [ ${chalk.dim('Options:')} -h, --help Show usage information + -n, --network [Required] the rule's protocol network -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -23,7 +28,6 @@ module.exports = { description: 'Remove one or many indexing rules', run: async (toolbox: GluegunToolbox) => { const { print, parameters } = toolbox - const { h, help, o, output } = parameters.options const [id] = fixParameters(parameters, { h, help }) || [] const outputFormat = parseOutputFormat(print, o || output || 'table') @@ -40,6 +44,7 @@ module.exports = { const config = loadValidatedConfig() try { + const protocolNetwork = requireProtocolNetworkOption(parameters.options) const [identifier, identifierType] = await processIdentifier(id, { all: true, global: true, @@ -48,28 +53,65 @@ module.exports = { const client = await createIndexerManagementClient({ url: config.api }) if (identifier === 'all') { - const rules = await indexingRules(client, false) + const rules = await indexingRules(client, false, protocolNetwork) - /* eslint-disable @typescript-eslint/no-non-null-assertion */ - await deleteIndexingRules( - client, - await Promise.all( - rules.map( - async rule => - ( - await processIdentifier(rule.identifier!, { all: true, global: true }) - )[0], - ), - ), + const rulesIdentifiers = await Promise.all( + rules.map(async function (rule) { + const identifier = ( + await processIdentifier( + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + rule.identifier!, + { + all: true, + global: true, + }, + ) + )[0] + + // All rules returned from `indexingRules` are have a `protocolNetwork` field, so we + // don't expect to see this error. + if (!rule.protocolNetwork) { + throw Error( + `Indexing Rule is missing a 'protocolNetwork' attribute: ${JSON.stringify( + rule, + )}`, + ) + } + + return { + identifier, + protocolNetwork: rule.protocolNetwork, + } + }), ) + + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + await deleteIndexingRules(client, rulesIdentifiers) /* eslint-enable @typescript-eslint/no-non-null-assertion */ - print.success(`Deleted all indexing rules`) - } else if (identifier === 'global') { - await deleteIndexingRules(client, ['global']) - print.warning(`Reset global indexing rules (the global rules cannot be deleted)`) + print.success('Deleted all indexing rules') + return + } + + // Since we are not deleting all rules, we must require a protocol network from the user + if (!protocolNetwork) { + throw Error( + 'The --netowrk option must be used when deleting a glboal Indexing Rule', + ) + } + const chainAlias = resolveChainAlias(protocolNetwork) + + if (identifier === 'global') { + const globalIdentifier = { identifier, protocolNetwork } + await deleteIndexingRules(client, [globalIdentifier]) + print.warning( + `Reset global indexing rules for network '${chainAlias}' (global rules cannot be deleted)`, + ) } else { - await deleteIndexingRules(client, [identifier]) - print.success(`Deleted indexing rules for "${identifier}" (${identifierType})`) + const ruleIdentifier = { identifier, protocolNetwork } + await deleteIndexingRules(client, [ruleIdentifier]) + print.success( + `Deleted indexing rules for "${identifier}" (${identifierType}) on network: '${chainAlias}'`, + ) } } catch (error) { print.error(error.toString()) diff --git a/packages/indexer-cli/src/commands/indexer/rules/get.ts b/packages/indexer-cli/src/commands/indexer/rules/get.ts index eb99ba80a..b39dcc9cd 100644 --- a/packages/indexer-cli/src/commands/indexer/rules/get.ts +++ b/packages/indexer-cli/src/commands/indexer/rules/get.ts @@ -3,7 +3,11 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters, parseOutputFormat } from '../../../command-helpers' +import { + extractProtocolNetworkOption, + fixParameters, + parseOutputFormat, +} from '../../../command-helpers' import { indexingRule, indexingRules, displayRules } from '../../../rules' import { IndexingRuleAttributes, processIdentifier } from '@graphprotocol/indexer-common' @@ -15,6 +19,7 @@ ${chalk.bold('graph indexer rules get')} [options] [ ...] ${chalk.dim('Options:')} -h, --help Show usage information + -n, --network Filter by the rule's protocol network --merged Shows the deployment rules and global rules merged -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -40,8 +45,8 @@ module.exports = { } try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [identifier, identifierType] = await processIdentifier(id, { + const protocolNetwork = extractProtocolNetworkOption(parameters.options) + const [identifier] = await processIdentifier(id ?? 'all', { all: true, global: true, }) @@ -51,10 +56,18 @@ module.exports = { // Create indexer API client const client = await createIndexerManagementClient({ url: config.api }) - const ruleOrRules = - identifier === 'all' - ? await indexingRules(client, !!merged) - : await indexingRule(client, identifier, !!merged) + let ruleOrRules + if (identifier === 'all') { + ruleOrRules = await indexingRules(client, !!merged, protocolNetwork) + } else { + if (!protocolNetwork) { + throw Error( + 'The --netowrk option must be used when quering for a single Indexing Rule', + ) + } + const ruleIdentifier = { identifier, protocolNetwork } + ruleOrRules = await indexingRule(client, ruleIdentifier, !!merged) + } print.info( displayRules( diff --git a/packages/indexer-cli/src/commands/indexer/rules/maybe.ts b/packages/indexer-cli/src/commands/indexer/rules/maybe.ts index 95cf3566a..dbb806842 100644 --- a/packages/indexer-cli/src/commands/indexer/rules/maybe.ts +++ b/packages/indexer-cli/src/commands/indexer/rules/maybe.ts @@ -3,7 +3,11 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters, parseOutputFormat } from '../../../command-helpers' +import { + requireProtocolNetworkOption, + fixParameters, + parseOutputFormat, +} from '../../../command-helpers' import { IndexingDecisionBasis, processIdentifier } from '@graphprotocol/indexer-common' import { setIndexingRule, displayRules, parseIndexingRule } from '../../../rules' @@ -14,6 +18,7 @@ ${chalk.bold('graph indexer rules maybe')} [options] ${chalk.dim('Options:')} -h, --help Show usage information + -n, --network [Required] the rule's protocol network -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -40,6 +45,7 @@ module.exports = { const config = loadValidatedConfig() try { + const protocolNetwork = requireProtocolNetworkOption(parameters.options) const [identifier, identifierType] = await processIdentifier(id, { all: false, global: true, @@ -49,6 +55,7 @@ module.exports = { identifier, identifierType, decisionBasis: IndexingDecisionBasis.RULES, + protocolNetwork, }) const client = await createIndexerManagementClient({ url: config.api }) diff --git a/packages/indexer-cli/src/commands/indexer/rules/offchain.ts b/packages/indexer-cli/src/commands/indexer/rules/offchain.ts index 7109c3306..0c43b3166 100644 --- a/packages/indexer-cli/src/commands/indexer/rules/offchain.ts +++ b/packages/indexer-cli/src/commands/indexer/rules/offchain.ts @@ -3,7 +3,11 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters, parseOutputFormat } from '../../../command-helpers' +import { + requireProtocolNetworkOption, + fixParameters, + parseOutputFormat, +} from '../../../command-helpers' import { IndexingDecisionBasis, processIdentifier } from '@graphprotocol/indexer-common' import { setIndexingRule, displayRules, parseIndexingRule } from '../../../rules' @@ -15,7 +19,7 @@ ${chalk.bold('graph indexer rules prepare')} [options] ${chalk.dim('Options:')} - -h, --help Show usage information +, -h, --help Show usage information -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -42,6 +46,7 @@ module.exports = { const config = loadValidatedConfig() try { + const protocolNetwork = requireProtocolNetworkOption(parameters.options) const [identifier, identifierType] = await processIdentifier(id, { all: false, global: true, @@ -50,6 +55,7 @@ module.exports = { const inputRule = parseIndexingRule({ identifier, identifierType, + protocolNetwork, decisionBasis: IndexingDecisionBasis.OFFCHAIN, }) diff --git a/packages/indexer-cli/src/commands/indexer/rules/set.ts b/packages/indexer-cli/src/commands/indexer/rules/set.ts index 165212427..ce0776010 100644 --- a/packages/indexer-cli/src/commands/indexer/rules/set.ts +++ b/packages/indexer-cli/src/commands/indexer/rules/set.ts @@ -5,6 +5,7 @@ import { partition } from '@thi.ng/iterators' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' import { + requireProtocolNetworkOption, fixParameters, parseOutputFormat, suggestCommands, @@ -25,6 +26,7 @@ ${chalk.bold('graph indexer rules set')} [options] = 2) { print.error( @@ -88,7 +92,8 @@ module.exports = { } catch (error) { // Failed to parse input, make suggestions // Generate a instance of indexing rules for valid attributes - const tmpRules = await indexingRule(client, 'global', false) + const globalRule = { identifier: 'global', protocolNetwork } + const tmpRules = await indexingRule(client, globalRule, false) if (!tmpRules) { throw new Error( `Global indexing rules missing, try again after the agent ensures global rule`, diff --git a/packages/indexer-cli/src/commands/indexer/rules/start.ts b/packages/indexer-cli/src/commands/indexer/rules/start.ts index 044eb693e..c95f19a52 100644 --- a/packages/indexer-cli/src/commands/indexer/rules/start.ts +++ b/packages/indexer-cli/src/commands/indexer/rules/start.ts @@ -3,7 +3,11 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters, parseOutputFormat } from '../../../command-helpers' +import { + requireProtocolNetworkOption, + fixParameters, + parseOutputFormat, +} from '../../../command-helpers' import { IndexingDecisionBasis, processIdentifier } from '@graphprotocol/indexer-common' import { setIndexingRule, displayRules, parseIndexingRule } from '../../../rules' @@ -16,6 +20,7 @@ ${chalk.bold('graph indexer rules always')} [options] ${chalk.dim('Options:')} -h, --help Show usage information + -n, --network [Required] the rule's protocol network -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -42,6 +47,7 @@ module.exports = { const config = loadValidatedConfig() try { + const protocolNetwork = requireProtocolNetworkOption(parameters.options) const [identifier, identifierType] = await processIdentifier(id, { all: false, global: true, @@ -51,6 +57,7 @@ module.exports = { identifier, identifierType, decisionBasis: IndexingDecisionBasis.ALWAYS, + protocolNetwork, }) // Update the indexing rule according to the key/value pairs diff --git a/packages/indexer-cli/src/commands/indexer/rules/stop.ts b/packages/indexer-cli/src/commands/indexer/rules/stop.ts index da7abe76a..bcd85f9d8 100644 --- a/packages/indexer-cli/src/commands/indexer/rules/stop.ts +++ b/packages/indexer-cli/src/commands/indexer/rules/stop.ts @@ -3,7 +3,11 @@ import chalk from 'chalk' import { loadValidatedConfig } from '../../../config' import { createIndexerManagementClient } from '../../../client' -import { fixParameters, parseOutputFormat } from '../../../command-helpers' +import { + requireProtocolNetworkOption, + fixParameters, + parseOutputFormat, +} from '../../../command-helpers' import { IndexingDecisionBasis, processIdentifier } from '@graphprotocol/indexer-common' import { setIndexingRule, displayRules, parseIndexingRule } from '../../../rules' @@ -16,6 +20,7 @@ ${chalk.bold('graph indexer rules never')} [options] ${chalk.dim('Options:')} -h, --help Show usage information + -n, --network [Required] the rule's protocol network -o, --output table|json|yaml Choose the output format: table (default), JSON, or YAML ` @@ -42,6 +47,7 @@ module.exports = { const config = loadValidatedConfig() try { + const protocolNetwork = requireProtocolNetworkOption(parameters.options) const [identifier, identifierType] = await processIdentifier(id, { all: false, global: true, @@ -51,6 +57,7 @@ module.exports = { identifier, identifierType, decisionBasis: IndexingDecisionBasis.NEVER, + protocolNetwork, }) const client = await createIndexerManagementClient({ url: config.api }) diff --git a/packages/indexer-cli/src/commands/indexer/status.ts b/packages/indexer-cli/src/commands/indexer/status.ts index d06cafbf4..6e6a0ee38 100644 --- a/packages/indexer-cli/src/commands/indexer/status.ts +++ b/packages/indexer-cli/src/commands/indexer/status.ts @@ -7,7 +7,13 @@ import { createIndexerManagementClient } from '../../client' import gql from 'graphql-tag' import { displayRules, indexingRuleFromGraphQL } from '../../rules' import { printIndexerAllocations, indexerAllocationFromGraphQL } from '../../allocations' -import { formatData, parseOutputFormat, pickFields } from '../../command-helpers' +import { + requireProtocolNetworkOption, + formatData, + parseOutputFormat, + pickFields, +} from '../../command-helpers' +import { resolveChainAlias } from '@graphprotocol/indexer-common' const HELP = ` ${chalk.bold('graph indexer status')} @@ -15,8 +21,21 @@ ${chalk.bold('graph indexer status')} ${chalk.dim('Options:')} -h, --help Show usage information + -n, --network [Required] the rule's protocol network ` +interface Endpoint { + url: string | null + healthy: boolean + protocolNetwork: string + tests: any[] +} + +interface Endpoints { + service: Endpoint + status: Endpoint +} + module.exports = { name: 'status', alias: [], @@ -44,13 +63,18 @@ module.exports = { // Query status information let result: any | undefined try { + // TODO:L2: Consider making Protocol Network optional, showing status for all + // networks, combined. + const protocolNetwork = requireProtocolNetworkOption(toolbox.parameters.options) + result = await client .query( gql` - query { - indexerRegistration { + query ($protocolNetwork: String!) { + indexerRegistration(protocolNetwork: $protocolNetwork) { url address + protocolNetwork registered location { latitude @@ -81,8 +105,9 @@ module.exports = { } } - indexerAllocations { + indexerAllocations(protocolNetwork: $protocolNetwork) { id + protocolNetwork allocatedTokens createdAtEpoch closedAtEpoch @@ -91,10 +116,11 @@ module.exports = { stakedTokens } - indexerEndpoints { + indexerEndpoints(protocolNetwork: $protocolNetwork) { service { url healthy + protocolNetwork tests { test error @@ -104,6 +130,7 @@ module.exports = { status { url healthy + protocolNetwork tests { test error @@ -112,8 +139,9 @@ module.exports = { } } - indexingRules(merged: true) { + indexingRules(merged: true, protocolNetwork: $protocolNetwork) { identifier + protocolNetwork identifierType allocationAmount allocationLifetime @@ -131,7 +159,7 @@ module.exports = { } } `, - {}, + { protocolNetwork }, ) .toPromise() } catch (error) { @@ -167,24 +195,25 @@ module.exports = { } if (result.data.indexerEndpoints) { - const keys = Object.keys(pickFields(result.data.indexerEndpoints, [])) - keys.sort() - - const statusUp = outputFormat == 'table' ? chalk.green('up') : 'up' - const statusDown = outputFormat == 'table' ? chalk.red('down') : 'down' - - data.endpoints = keys.reduce( - (out, key) => [ - ...out, + data.endpoints = result.data.indexerEndpoints.flatMap((endpoints: Endpoints) => { + const { service, status } = endpoints + return [ { - name: key, - url: result.data.indexerEndpoints[key].url, - status: result.data.indexerEndpoints[key].healthy ? statusUp : statusDown, - tests: result.data.indexerEndpoints[key].tests, + name: 'service', + url: service.url, + tests: service.tests, + protocolNetwork: resolveChainAlias(service.protocolNetwork), + status: formatStatus(outputFormat, service.healthy), }, - ], - [] as any, - ) + { + name: 'status', + url: status.url, + tests: status.tests, + protocolNetwork: resolveChainAlias(status.protocolNetwork), + status: formatStatus(outputFormat, status.healthy), + }, + ] + }) } else { data.endpoints = { error: @@ -228,15 +257,17 @@ module.exports = { print.info( formatData( data.endpoints.map((endpoint: any) => - pickFields(endpoint, ['name', 'url', 'status']), + pickFields(endpoint, ['name', 'protocolNetwork', 'url', 'status']), ), outputFormat, ), ) if ( - data.endpoints.find((endpoint: any) => - endpoint.tests.find((test: any) => test.error !== null), - ) + data.endpoints.find((endpoint: any) => { + if (endpoint.tests) { + return endpoint.tests.find((test: any) => test.error !== null) + } + }) ) { print.error('The following endpoint tests failed:\n') for (const endpoint of data.endpoints) { @@ -300,3 +331,13 @@ module.exports = { } }, } + +function formatStatus(outputFormat: string, status: boolean) { + return outputFormat === 'table' + ? status + ? chalk.green('up') + : chalk.red('down') + : status + ? 'up' + : 'down' +} diff --git a/packages/indexer-cli/src/disputes.ts b/packages/indexer-cli/src/disputes.ts index 9eab92fbd..e3fa12bb4 100644 --- a/packages/indexer-cli/src/disputes.ts +++ b/packages/indexer-cli/src/disputes.ts @@ -3,12 +3,13 @@ import { POIDisputeAttributes, indexerError, IndexerErrorCode, + resolveChainAlias, } from '@graphprotocol/indexer-common' import gql from 'graphql-tag' import yaml from 'yaml' import { GluegunPrint } from 'gluegun' import { table, getBorderCharacters } from 'table' -import { parseOutputFormat, OutputFormat } from './command-helpers' +import { OutputFormat } from './command-helpers' const DISPUTE_FORMATTERS: Record string> = { allocationID: x => x, @@ -24,6 +25,7 @@ const DISPUTE_FORMATTERS: Record strin previousEpochStartBlockHash: x => x, previousEpochStartBlockNumber: x => x, status: x => x, + protocolNetwork: resolveChainAlias, } const DISPUTES_CONVERTERS_FROM_GRAPHQL: Record< @@ -43,6 +45,7 @@ const DISPUTES_CONVERTERS_FROM_GRAPHQL: Record< previousEpochStartBlockHash: x => x, previousEpochStartBlockNumber: x => +x, status: x => x, + protocolNetwork: x => x, } /** @@ -129,13 +132,22 @@ export const disputes = async ( client: IndexerManagementClient, status: string, minClosedEpoch: number, + protocolNetwork: string | undefined, ): Promise[]> => { try { const result = await client .query( gql` - query disputes($status: String!, $minClosedEpoch: Int!) { - disputes(status: $status, minClosedEpoch: $minClosedEpoch) { + query disputes( + $status: String! + $minClosedEpoch: Int! + $protocolNetwork: String + ) { + disputes( + status: $status + minClosedEpoch: $minClosedEpoch + protocolNetwork: $protocolNetwork + ) { allocationID allocationIndexer allocationAmount @@ -148,12 +160,14 @@ export const disputes = async ( previousEpochStartBlockNumber previousEpochReferenceProof status + protocolNetwork } } `, { status, minClosedEpoch, + protocolNetwork, }, ) .toPromise() diff --git a/packages/indexer-cli/src/rules.ts b/packages/indexer-cli/src/rules.ts index f37cd74c1..e50027849 100644 --- a/packages/indexer-cli/src/rules.ts +++ b/packages/indexer-cli/src/rules.ts @@ -6,6 +6,8 @@ import { IndexerManagementClient, IndexingRuleAttributes, IndexingDecisionBasis, + IndexingRuleIdentifier, + resolveChainAlias, } from '@graphprotocol/indexer-common' import gql from 'graphql-tag' import yaml from 'yaml' @@ -34,6 +36,7 @@ const INDEXING_RULE_PARSERS: Record custom: nullPassThrough(JSON.parse), requireSupported: x => parseBoolean(x), safety: x => parseBoolean(x), + protocolNetwork: x => x, } const INDEXING_RULE_FORMATTERS: Record< @@ -57,6 +60,7 @@ const INDEXING_RULE_FORMATTERS: Record< custom: nullPassThrough(JSON.stringify), requireSupported: x => x, safety: x => x, + protocolNetwork: resolveChainAlias, } const INDEXING_RULE_CONVERTERS_FROM_GRAPHQL: Record< @@ -80,6 +84,7 @@ const INDEXING_RULE_CONVERTERS_FROM_GRAPHQL: Record< custom: nullPassThrough(JSON.stringify), requireSupported: x => x, safety: x => x, + protocolNetwork: x => x, } const INDEXING_RULE_CONVERTERS_TO_GRAPHQL: Record< @@ -103,6 +108,7 @@ const INDEXING_RULE_CONVERTERS_TO_GRAPHQL: Record< custom: nullPassThrough(JSON.stringify), requireSupported: x => x, safety: x => x, + protocolNetwork: x => x, } /** @@ -238,13 +244,15 @@ export const displayRules = ( export const indexingRules = async ( client: IndexerManagementClient, merged: boolean, + protocolNetwork?: string, ): Promise[]> => { const result = await client .query( gql` - query indexingRules($merged: Boolean!) { - indexingRules(merged: $merged) { + query indexingRules($merged: Boolean!, $protocolNetwork: String) { + indexingRules(merged: $merged, protocolNetwork: $protocolNetwork) { identifier + protocolNetwork identifierType allocationAmount allocationLifetime @@ -262,7 +270,7 @@ export const indexingRules = async ( } } `, - { merged: !!merged }, + { merged: !!merged, protocolNetwork }, ) .toPromise() @@ -275,13 +283,13 @@ export const indexingRules = async ( export const indexingRule = async ( client: IndexerManagementClient, - identifier: string, + identifier: IndexingRuleIdentifier, merged: boolean, ): Promise | null> => { const result = await client .query( gql` - query indexingRule($identifier: String!, $merged: Boolean!) { + query indexingRule($identifier: IndexingRuleIdentifier!, $merged: Boolean!) { indexingRule(identifier: $identifier, merged: $merged) { identifier identifierType @@ -298,6 +306,7 @@ export const indexingRule = async ( decisionBasis requireSupported safety + protocolNetwork } } `, @@ -340,6 +349,7 @@ export const setIndexingRule = async ( decisionBasis requireSupported safety + protocolNetwork } } `, @@ -356,16 +366,16 @@ export const setIndexingRule = async ( export const deleteIndexingRules = async ( client: IndexerManagementClient, - identifiers: string[], + deployments: IndexingRuleIdentifier[], ): Promise => { const result = await client .mutation( gql` - mutation deleteIndexingRules($deployments: [String!]!) { + mutation deleteIndexingRules($deployments: [IndexingRuleIdentifier!]!) { deleteIndexingRules(identifiers: $deployments) } `, - { deployments: identifiers.map(identifier => identifier.toString()) }, + { deployments }, ) .toPromise() From 23e95545114dd040ec4a07dd95040dd6f80193ba Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:08:46 -0300 Subject: [PATCH 08/16] cil: update tests --- .../src/__tests__/indexer/actions.test.ts | 2 + .../src/__tests__/indexer/cost.test.ts | 7 +- .../src/__tests__/indexer/rules.test.ts | 321 ++++++++++++++---- .../indexer-actions-get-first.stdout | 10 +- .../references/indexer-actions-get.stdout | 14 +- .../indexer-rule-deployment-always.stdout | 10 +- ...deployment-deleted-offchain-success.stdout | 2 +- ...xer-rule-deployment-deleted-success.stdout | 2 +- .../indexer-rule-deployment-lifetime.stdout | 10 +- .../indexer-rule-deployment-never.stdout | 10 +- .../indexer-rule-deployment-offchain.stdout | 10 +- .../indexer-rule-deployment-rules.stdout | 10 +- .../indexer-rule-deployment-safety.stdout | 10 +- .../indexer-rule-deployment-supported.stdout | 10 +- .../indexer-rule-deployment-yaml.stdout | 1 + .../indexer-rule-global-rules.stdout | 10 +- .../indexer-rule-subgraph-offchain.stdout | 10 +- .../indexer-rule-subgraph-options.stdout | 10 +- .../indexer-rule-subgraph-rules.stdout | 10 +- .../indexer-rules-command-no-args.stdout | 12 +- .../indexer-rules-no-identifier.stderr | 1 + .../indexer-rules-no-network.stderr | 1 + packages/indexer-cli/src/__tests__/util.ts | 207 ++++++++--- 23 files changed, 515 insertions(+), 175 deletions(-) create mode 100644 packages/indexer-cli/src/__tests__/references/indexer-rules-no-identifier.stderr create mode 100644 packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr diff --git a/packages/indexer-cli/src/__tests__/indexer/actions.test.ts b/packages/indexer-cli/src/__tests__/indexer/actions.test.ts index 516b0da1c..e18627697 100644 --- a/packages/indexer-cli/src/__tests__/indexer/actions.test.ts +++ b/packages/indexer-cli/src/__tests__/indexer/actions.test.ts @@ -86,6 +86,7 @@ async function createTestAction() { deploymentID: 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', source: 'test', reason: 'test', + protocolNetwork: 'eip155:5', }) await Action.create({ type: ActionType.UNALLOCATE, @@ -93,6 +94,7 @@ async function createTestAction() { deploymentID: 'QmfWRZCjT8pri4Amey3e3mb2Bga75Vuh2fPYyNVnmPYL66', source: 'test', reason: 'test', + protocolNetwork: 'eip155:5', }) } diff --git a/packages/indexer-cli/src/__tests__/indexer/cost.test.ts b/packages/indexer-cli/src/__tests__/indexer/cost.test.ts index 215a1fffb..08fdbee5f 100644 --- a/packages/indexer-cli/src/__tests__/indexer/cost.test.ts +++ b/packages/indexer-cli/src/__tests__/indexer/cost.test.ts @@ -1,11 +1,14 @@ -import { cliTest, setup, teardown } from '../util' +import { cliTest, setup, seed, teardown } from '../util' import path from 'path' const baseDir = path.join(__dirname, '..') describe('Indexer cost tests', () => { describe('With indexer management server', () => { - beforeEach(setup) + beforeEach(async () => { + await setup() + await seed() + }) afterEach(teardown) describe('Cost help', () => { cliTest('Indexer cost', ['indexer', 'cost'], 'references/indexer-cost', { diff --git a/packages/indexer-cli/src/__tests__/indexer/rules.test.ts b/packages/indexer-cli/src/__tests__/indexer/rules.test.ts index 139ef14a9..94989dcf5 100644 --- a/packages/indexer-cli/src/__tests__/indexer/rules.test.ts +++ b/packages/indexer-cli/src/__tests__/indexer/rules.test.ts @@ -1,18 +1,27 @@ -import { cliTest, setup, teardown } from '../util' +import { cliTest, setup, seed, teardown, deleteFromAllTables } from '../util' import path from 'path' const baseDir = path.join(__dirname, '..') describe('Indexer rules tests', () => { describe('With indexer management server', () => { - beforeEach(setup) - afterEach(teardown) + beforeAll(setup) + beforeEach(async () => { + await deleteFromAllTables() + await seed() + }) + afterAll(teardown) describe('Rules help', () => { - cliTest('Indexer rules', ['indexer', 'rules'], 'references/indexer-rules', { - expectedExitCode: 255, - cwd: baseDir, - timeout: 10000, - }) + cliTest( + 'Indexer rules', + ['indexer', 'rules', '--network', 'goerli'], + 'references/indexer-rules', + { + expectedExitCode: 255, + cwd: baseDir, + timeout: 10000, + }, + ) cliTest( 'Indexer rules help', ['indexer', 'rules', '--help'], @@ -28,7 +37,14 @@ describe('Indexer rules tests', () => { describe('Rules start...', () => { cliTest( 'Indexer rules start - success', - ['indexer', 'rules', 'start', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'start', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-rule-deployment-always', { expectedExitCode: 0, @@ -37,9 +53,19 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules start - no args', + 'Indexer rules start - no network', ['indexer', 'rules', 'start'], - 'references/indexer-rules-command-no-args', + 'references/indexer-rules-no-network', + { + expectedExitCode: 1, + cwd: baseDir, + timeout: 10000, + }, + ) + cliTest( + 'Indexer rules start - no identifier', + ['indexer', 'rules', 'start', '--network', 'goerli'], + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -48,7 +74,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules start - invalid deployment ID ', - ['indexer', 'rules', 'start', 'Qmemememememe'], + ['indexer', 'rules', 'start', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -61,7 +87,14 @@ describe('Indexer rules tests', () => { describe('Rules prepare...', () => { cliTest( 'Indexer rules prepare - success', - ['indexer', 'rules', 'prepare', 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK'], + [ + 'indexer', + 'rules', + 'prepare', + '--network', + 'goerli', + 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK', + ], 'references/indexer-rule-deployment-offchain', { expectedExitCode: 0, @@ -75,6 +108,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'offchain', + '--network', + 'goerli', 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK', ], 'references/indexer-rule-deployment-offchain', @@ -85,9 +120,19 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules prepare - no args', + 'Indexer rules prepare - no network', ['indexer', 'rules', 'prepare'], - 'references/indexer-rules-command-no-args', + 'references/indexer-rules-no-network', + { + expectedExitCode: 1, + cwd: baseDir, + timeout: 10000, + }, + ) + cliTest( + 'Indexer rules prepare - no identifier', + ['indexer', 'rules', 'prepare', '--network', 'goerli'], + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -96,7 +141,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules prepare - invalid deployment ID ', - ['indexer', 'rules', 'prepare', 'Qmemememememe'], + ['indexer', 'rules', 'prepare', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -109,7 +154,14 @@ describe('Indexer rules tests', () => { describe('Rules stop...', () => { cliTest( 'Indexer rules stop - success', - ['indexer', 'rules', 'stop', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'stop', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-rule-deployment-never', { expectedExitCode: 0, @@ -118,9 +170,19 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules stop - no args', + 'Indexer rules stop - no network', ['indexer', 'rules', 'stop'], - 'references/indexer-rules-command-no-args', + 'references/indexer-rules-no-network', + { + expectedExitCode: 1, + cwd: baseDir, + timeout: 10000, + }, + ) + cliTest( + 'Indexer rules stop - no identifier', + ['indexer', 'rules', 'stop', '--network', 'goerli'], + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -129,7 +191,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules stop - invalid deployment ID', - ['indexer', 'rules', 'stop', 'Qmemememememe'], + ['indexer', 'rules', 'stop', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -142,7 +204,14 @@ describe('Indexer rules tests', () => { describe('Rules maybe...', () => { cliTest( 'Indexer rules maybe - success', - ['indexer', 'rules', 'maybe', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'maybe', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-rule-deployment-rules', { expectedExitCode: 0, @@ -151,9 +220,19 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules maybe - no args', + 'Indexer rules maybe - no network', ['indexer', 'rules', 'maybe'], - 'references/indexer-rules-command-no-args', + 'references/indexer-rules-no-network', + { + expectedExitCode: 1, + cwd: baseDir, + timeout: 10000, + }, + ) + cliTest( + 'Indexer rules maybe - no identifier', + ['indexer', 'rules', 'maybe', '--network', 'goerli'], + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -162,7 +241,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules maybe - invalid deployment ID ', - ['indexer', 'rules', 'maybe', 'Qmemememememe'], + ['indexer', 'rules', 'maybe', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -175,7 +254,14 @@ describe('Indexer rules tests', () => { describe('Rules clear...', () => { cliTest( 'Indexer rules clear - success', - ['indexer', 'rules', 'clear', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'clear', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-rule-deployment-rules', { expectedExitCode: 0, @@ -184,9 +270,19 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules clear - no args', + 'Indexer rules clear - no network', ['indexer', 'rules', 'clear'], - 'references/indexer-rules-command-no-args', + 'references/indexer-rules-no-network', + { + expectedExitCode: 1, + cwd: baseDir, + timeout: 10000, + }, + ) + cliTest( + 'Indexer rules clear - no identifier', + ['indexer', 'rules', 'clear', '--network', 'goerli'], + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -195,7 +291,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules clear - invalid deployment ID ', - ['indexer', 'rules', 'clear', 'Qmemememememe'], + ['indexer', 'rules', 'clear', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -208,7 +304,14 @@ describe('Indexer rules tests', () => { describe('Rules delete...', () => { cliTest( 'Indexer rules delete - success', - ['indexer', 'rules', 'delete', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'delete', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-rule-deployment-deleted-success', { expectedExitCode: 0, @@ -218,7 +321,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules delete - success', - ['indexer', 'rules', 'delete', 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK'], + [ + 'indexer', + 'rules', + 'delete', + '--network', + 'goerli', + 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK', + ], 'references/indexer-rule-deployment-deleted-offchain-success', { expectedExitCode: 0, @@ -227,9 +337,19 @@ describe('Indexer rules tests', () => { }, ) cliTest( - 'Indexer rules delete - no args', + 'Indexer rules delete - no network', ['indexer', 'rules', 'delete'], - 'references/indexer-rules-command-no-args', + 'references/indexer-rules-no-network', + { + expectedExitCode: 1, + cwd: baseDir, + timeout: 10000, + }, + ) + cliTest( + 'Indexer rules delete - no identifier', + ['indexer', 'rules', 'delete', '--network', 'goerli'], + 'references/indexer-rules-no-identifier', { expectedExitCode: 1, cwd: baseDir, @@ -238,7 +358,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules delete - invalid deployment ID ', - ['indexer', 'rules', 'delete', 'Qmemememememe'], + ['indexer', 'rules', 'delete', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -255,6 +375,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'set', + '--network', + 'goerli', '0x0000000000000000000000000000000000000000-0', 'allocationAmount', '1000', @@ -272,6 +394,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'set', + '--network', + 'goerli', '0x0000000000000000000000000000000000000000-1', 'allocationAmount', '1000', @@ -289,7 +413,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules set deployment id - success', - ['indexer', 'rules', 'set', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'set', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-rule-deployment-rules', { expectedExitCode: 0, @@ -303,6 +434,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'set', + '--network', + 'goerli', 'QmVEV7RA2U6BJT9Ssjxcfyrk4YQUnVqSRNX4TvYagjzh9h', 'requireSupported', 'false', @@ -320,6 +453,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'set', + '--network', + 'goerli', 'QmVEV7RA2U6BJT9Ssjxcfyrk4YQUnVqSRNX4TvYagjzh9h', 'safety', 'false', @@ -337,6 +472,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'set', + '--network', + 'goerli', 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK', 'decisionBasis', 'offchain', @@ -358,6 +495,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'set', + '--network', + 'goerli', 'global', 'minSignal', '500', @@ -374,7 +513,7 @@ describe('Indexer rules tests', () => { cliTest( 'Indexer rules set - no args', ['indexer', 'rules', 'set'], - 'references/indexer-rules-command-no-args', + 'references/indexer-rules-no-network', { expectedExitCode: 1, cwd: baseDir, @@ -383,7 +522,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules set - invalid deployment ID ', - ['indexer', 'rules', 'set', 'Qmemememememe'], + ['indexer', 'rules', 'set', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -397,6 +536,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'set', + '--network', + 'goerli', '0x0000000000000000000000000000000000000000-0', 'allocationAmoewt', '1000', @@ -423,7 +564,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules get deployment - success', - ['indexer', 'rules', 'get', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'get', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-rule-deployment-rules', { expectedExitCode: 0, @@ -433,7 +581,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules get deployment - success - offchain', - ['indexer', 'rules', 'get', 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK'], + [ + 'indexer', + 'rules', + 'get', + '--network', + 'goerli', + 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK', + ], 'references/indexer-rule-deployment-offchain', { expectedExitCode: 0, @@ -443,7 +598,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules get subgraph - success', - ['indexer', 'rules', 'get', '0x0000000000000000000000000000000000000000-0'], + [ + 'indexer', + 'rules', + 'get', + '--network', + 'goerli', + '0x0000000000000000000000000000000000000000-0', + ], 'references/indexer-rule-subgraph-rules', { expectedExitCode: 0, @@ -453,7 +615,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules get subgraph - success - options', - ['indexer', 'rules', 'get', '0x0000000000000000000000000000000000000000-2'], + [ + 'indexer', + 'rules', + 'get', + '--network', + 'goerli', + '0x0000000000000000000000000000000000000000-2', + ], 'references/indexer-rule-subgraph-options', { expectedExitCode: 0, @@ -467,6 +636,8 @@ describe('Indexer rules tests', () => { 'indexer', 'rules', 'get', + '--network', + 'goerli', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', '--output', 'yaml', @@ -480,7 +651,7 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules get global - success', - ['indexer', 'rules', 'get', 'global'], + ['indexer', 'rules', 'get', '--network', 'goerli', 'global'], 'references/indexer-rule-global-rules', { expectedExitCode: 0, @@ -493,14 +664,14 @@ describe('Indexer rules tests', () => { ['indexer', 'rules', 'get'], 'references/indexer-rules-command-no-args', { - expectedExitCode: 1, + expectedExitCode: 0, cwd: baseDir, timeout: 10000, }, ) cliTest( 'Indexer rules get - invalid deployment ID ', - ['indexer', 'rules', 'get', 'Qmemememememe'], + ['indexer', 'rules', 'get', '--network', 'goerli', 'Qmemememememe'], 'references/indexer-rules-invalid-identifier-arg', { expectedExitCode: 1, @@ -514,7 +685,14 @@ describe('Indexer rules tests', () => { describe('Without indexer management server', () => { cliTest( 'Indexer rules start - not connected', - ['indexer', 'rules', 'start', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'start', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-not-connected', { expectedExitCode: 1, @@ -524,7 +702,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules stop - not connected', - ['indexer', 'rules', 'stop', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'stop', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-not-connected', { expectedExitCode: 1, @@ -534,7 +719,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules maybe - not connected', - ['indexer', 'rules', 'maybe', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'maybe', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-not-connected', { expectedExitCode: 1, @@ -542,19 +734,16 @@ describe('Indexer rules tests', () => { timeout: 10000, }, ) - cliTest( - 'Indexer rules get - no args', - ['indexer', 'rules', 'get'], - 'references/indexer-rules-command-no-args', - { - expectedExitCode: 1, - cwd: baseDir, - timeout: 10000, - }, - ) cliTest( 'Indexer rules get - not connected', - ['indexer', 'rules', 'get', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'get', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-not-connected', { expectedExitCode: 1, @@ -564,7 +753,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules delete - not connected', - ['indexer', 'rules', 'delete', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'delete', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-not-connected', { expectedExitCode: 1, @@ -574,7 +770,14 @@ describe('Indexer rules tests', () => { ) cliTest( 'Indexer rules clear - not connected', - ['indexer', 'rules', 'clear', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], + [ + 'indexer', + 'rules', + 'clear', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], 'references/indexer-not-connected', { expectedExitCode: 1, diff --git a/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout b/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout index 773bca940..508c1a86b 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-actions-get-first.stdout @@ -1,5 +1,5 @@ -┌────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────┬──────────┬────────┬────────┬───────────────┬─────────────┬────────┐ -│ id │ type │ deploymentID │ allocationID │ amount │ poi │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ -├────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼────────┼────────┼───────────────┼─────────────┼────────┤ -│ 2 │ unallocate │ QmfWRZCjT8pri4Amey3e3mb2Bga75Vuh2fPYyNVnmPYL66 │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ -└────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────┴──────────┴────────┴────────┴───────────────┴─────────────┴────────┘ +┌────┬─────────────────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────┬──────────┬────────┬────────┬───────────────┬─────────────┬────────┐ +│ id │ protocolNetwork │ type │ deploymentID │ allocationID │ amount │ poi │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ +├────┼─────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼────────┼────────┼───────────────┼─────────────┼────────┤ +│ 2 │ goerli │ unallocate │ QmfWRZCjT8pri4Amey3e3mb2Bga75Vuh2fPYyNVnmPYL66 │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ +└────┴─────────────────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────┴──────────┴────────┴────────┴───────────────┴─────────────┴────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout b/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout index 145603b61..6b00ddbbc 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-actions-get.stdout @@ -1,7 +1,7 @@ -┌────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────┬──────────┬─────────┬────────┬───────────────┬─────────────┬────────┐ -│ id │ type │ deploymentID │ allocationID │ amount │ poi │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ -├────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┤ -│ 2 │ unallocate │ QmfWRZCjT8pri4Amey3e3mb2Bga75Vuh2fPYyNVnmPYL66 │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ -├────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┤ -│ 1 │ allocate │ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ null │ null │ null │ null │ 0 │ success │ test │ null │ null │ test │ -└────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────┴──────────┴─────────┴────────┴───────────────┴─────────────┴────────┘ +┌────┬─────────────────┬────────────┬────────────────────────────────────────────────┬──────────────┬────────┬──────┬───────┬──────────┬─────────┬────────┬───────────────┬─────────────┬────────┐ +│ id │ protocolNetwork │ type │ deploymentID │ allocationID │ amount │ poi │ force │ priority │ status │ source │ failureReason │ transaction │ reason │ +├────┼─────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┤ +│ 2 │ goerli │ unallocate │ QmfWRZCjT8pri4Amey3e3mb2Bga75Vuh2fPYyNVnmPYL66 │ null │ null │ null │ null │ 0 │ failed │ test │ null │ null │ test │ +├────┼─────────────────┼────────────┼────────────────────────────────────────────────┼──────────────┼────────┼──────┼───────┼──────────┼─────────┼────────┼───────────────┼─────────────┼────────┤ +│ 1 │ goerli │ allocate │ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ null │ null │ null │ null │ 0 │ success │ test │ null │ null │ test │ +└────┴─────────────────┴────────────┴────────────────────────────────────────────────┴──────────────┴────────┴──────┴───────┴──────────┴─────────┴────────┴───────────────┴─────────────┴────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-always.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-always.stdout index 37e299f4e..f1adb22f6 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-always.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-always.stdout @@ -1,5 +1,5 @@ -┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ always │ true │ true │ -└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ always │ true │ true │ goerli │ +└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-offchain-success.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-offchain-success.stdout index be659136c..01b15a29d 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-offchain-success.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-offchain-success.stdout @@ -1 +1 @@ -Deleted indexing rules for "QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK" (deployment) +Deleted indexing rules for "QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK" (deployment) on network: 'goerli' diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-success.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-success.stdout index 546cb83c6..062a6ba5c 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-success.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-deleted-success.stdout @@ -1 +1 @@ -Deleted indexing rules for "QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr" (deployment) +Deleted indexing rules for "QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr" (deployment) on network: 'goerli' diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-lifetime.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-lifetime.stdout index 5f5115f1c..0eb184ed9 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-lifetime.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-lifetime.stdout @@ -1,5 +1,5 @@ -┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK │ deployment │ null │ 21 │ false │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ -└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK │ deployment │ null │ 21 │ false │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ goerli │ +└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-never.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-never.stdout index 4fdd160b5..813c5736a 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-never.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-never.stdout @@ -1,5 +1,5 @@ -┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ never │ true │ true │ -└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ never │ true │ true │ goerli │ +└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-offchain.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-offchain.stdout index 5806df5c7..d44a372dc 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-offchain.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-offchain.stdout @@ -1,5 +1,5 @@ -┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ -└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ goerli │ +└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-rules.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-rules.stdout index 96f4174a9..3ea675fbb 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-rules.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-rules.stdout @@ -1,5 +1,5 @@ -┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ true │ -└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ true │ goerli │ +└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-safety.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-safety.stdout index d502b5c44..390ad6605 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-safety.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-safety.stdout @@ -1,5 +1,5 @@ -┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ QmVEV7RA2U6BJT9Ssjxcfyrk4YQUnVqSRNX4TvYagjzh9h │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ false │ -└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ QmVEV7RA2U6BJT9Ssjxcfyrk4YQUnVqSRNX4TvYagjzh9h │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ false │ goerli │ +└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-supported.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-supported.stdout index cd5f1a9a3..8f8dc475a 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-supported.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-supported.stdout @@ -1,5 +1,5 @@ -┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ QmVEV7RA2U6BJT9Ssjxcfyrk4YQUnVqSRNX4TvYagjzh9h │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ false │ true │ -└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ QmVEV7RA2U6BJT9Ssjxcfyrk4YQUnVqSRNX4TvYagjzh9h │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ false │ true │ goerli │ +└────────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-yaml.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-yaml.stdout index 994546f99..bb4efe2aa 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-yaml.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-deployment-yaml.stdout @@ -13,3 +13,4 @@ custom: null decisionBasis: rules requireSupported: true safety: true +protocolNetwork: goerli diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-global-rules.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-global-rules.stdout index 3a1021afb..bfc937631 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-global-rules.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-global-rules.stdout @@ -1,5 +1,5 @@ -┌────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ global │ group │ 0.01 │ null │ true │ null │ null │ 500.0 │ null │ null │ null │ null │ rules │ true │ true │ -└────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ global │ group │ 0.01 │ null │ true │ null │ null │ 500.0 │ null │ null │ null │ null │ rules │ true │ true │ goerli │ +└────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-offchain.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-offchain.stdout index c948ce8fe..fff19583a 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-offchain.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-offchain.stdout @@ -1,5 +1,5 @@ -┌──────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├──────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ 0x0000000000000000000000000000000000000000-1 │ subgraph │ 1,000.0 │ 12 │ true │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ -└──────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌──────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├──────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ 0x0000000000000000000000000000000000000000-1 │ subgraph │ 1,000.0 │ 12 │ true │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ goerli │ +└──────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-options.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-options.stdout index c21331585..0defef731 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-options.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-options.stdout @@ -1,5 +1,5 @@ -┌──────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├──────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ 0x0000000000000000000000000000000000000000-2 │ subgraph │ 1,000.0 │ 12 │ true │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ -└──────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌──────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├──────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ 0x0000000000000000000000000000000000000000-2 │ subgraph │ 1,000.0 │ 12 │ true │ null │ null │ null │ null │ null │ null │ null │ offchain │ true │ true │ goerli │ +└──────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-rules.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-rules.stdout index 795f870dd..e5339693d 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-rules.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rule-subgraph-rules.stdout @@ -1,5 +1,5 @@ -┌──────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ -│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ -├──────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ -│ 0x0000000000000000000000000000000000000000-0 │ subgraph │ 1,000.0 │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ true │ -└──────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +┌──────────────────────────────────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┬─────────────────┐ +│ identifier │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ protocolNetwork │ +├──────────────────────────────────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┼─────────────────┤ +│ 0x0000000000000000000000000000000000000000-0 │ subgraph │ 1,000.0 │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ true │ goerli │ +└──────────────────────────────────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┴─────────────────┘ diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rules-command-no-args.stdout b/packages/indexer-cli/src/__tests__/references/indexer-rules-command-no-args.stdout index b2b9731e4..75048306b 100644 --- a/packages/indexer-cli/src/__tests__/references/indexer-rules-command-no-args.stdout +++ b/packages/indexer-cli/src/__tests__/references/indexer-rules-command-no-args.stdout @@ -1 +1,11 @@ -Error: Invalid subgraph identifier "undefined". Subgraph identifier should match 1 type of [deployment ID, subgraph ID, group identifier]. +┌────────────────────────────────────────────────┬─────────────────┬────────────────┬──────────────────┬────────────────────┬─────────────┬─────────────────────┬─────────────────────────┬───────────┬───────────┬──────────┬─────────────────────┬────────┬───────────────┬──────────────────┬────────┐ +│ identifier │ protocolNetwork │ identifierType │ allocationAmount │ allocationLifetime │ autoRenewal │ parallelAllocations │ maxAllocationPercentage │ minSignal │ maxSignal │ minStake │ minAverageQueryFees │ custom │ decisionBasis │ requireSupported │ safety │ +├────────────────────────────────────────────────┼─────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ +│ global │ goerli │ group │ 0.01 │ null │ true │ null │ null │ 500.0 │ null │ null │ null │ null │ rules │ true │ true │ +├────────────────────────────────────────────────┼─────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ +│ 0x0000000000000000000000000000000000000000-0 │ goerli │ subgraph │ 1,000.0 │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ true │ +├────────────────────────────────────────────────┼─────────────────┼────────────────┼──────────────────┼────────────────────┼─────────────┼─────────────────────┼─────────────────────────┼───────────┼───────────┼──────────┼─────────────────────┼────────┼───────────────┼──────────────────┼────────┤ +│ QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr │ goerli │ deployment │ null │ null │ true │ null │ null │ null │ null │ null │ null │ null │ rules │ true │ true │ +└────────────────────────────────────────────────┴─────────────────┴────────────────┴──────────────────┴────────────────────┴─────────────┴─────────────────────┴─────────────────────────┴───────────┴───────────┴──────────┴─────────────────────┴────────┴───────────────┴──────────────────┴────────┘ +Offchain sync list +0x0000000000000000000000000000000000000000-1,0x0000000000000000000000000000000000000000-2,QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rules-no-identifier.stderr b/packages/indexer-cli/src/__tests__/references/indexer-rules-no-identifier.stderr new file mode 100644 index 000000000..b2b9731e4 --- /dev/null +++ b/packages/indexer-cli/src/__tests__/references/indexer-rules-no-identifier.stderr @@ -0,0 +1 @@ +Error: Invalid subgraph identifier "undefined". Subgraph identifier should match 1 type of [deployment ID, subgraph ID, group identifier]. diff --git a/packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr b/packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr new file mode 100644 index 000000000..0c57fdb91 --- /dev/null +++ b/packages/indexer-cli/src/__tests__/references/indexer-rules-no-network.stderr @@ -0,0 +1 @@ +Error: The option '--network' is required diff --git a/packages/indexer-cli/src/__tests__/util.ts b/packages/indexer-cli/src/__tests__/util.ts index 535d3c5d6..09233423f 100644 --- a/packages/indexer-cli/src/__tests__/util.ts +++ b/packages/indexer-cli/src/__tests__/util.ts @@ -1,5 +1,4 @@ import { exec, ExecOptions } from 'child_process' -import { Wallet } from 'ethers' import fs from 'fs' import http from 'http' import { Socket } from 'net' @@ -13,32 +12,78 @@ import { defineIndexerManagementModels, IndexerManagementClient, IndexerManagementModels, - IndexingStatusResolver, - NetworkSubgraph, + GraphNode, + specification, + IndexerManagementDefaults, + Network, + MultiNetworks, + QueryFeeModels, + defineQueryFeeModels, } from '@graphprotocol/indexer-common' import { - connectContracts, + createMetrics, + Metrics, connectDatabase, createLogger, Logger, - NetworkContracts, parseGRT, - toAddress, } from '@graphprotocol/common-ts' +const INDEXER_SAVE_CLI_TEST_OUTPUT: boolean = + !!process.env.INDEXER_SAVE_CLI_TEST_OUTPUT && + process.env.INDEXER_SAVE_CLI_TEST_OUTPUT.toLowerCase() !== 'false' + declare const __DATABASE__: never declare const __LOG_LEVEL__: never let defaultMaxEventListeners: number let sequelize: Sequelize let models: IndexerManagementModels -let wallet: Wallet -let address: string -let contracts: NetworkContracts +let queryFeeModels: QueryFeeModels let logger: Logger let indexerManagementClient: IndexerManagementClient let server: http.Server let sockets: Socket[] = [] +let metrics: Metrics + +const PUBLIC_JSON_RPC_ENDPOINT = 'https://ethereum-goerli.publicnode.com' + +const testProviderUrl = + process.env.INDEXER_TEST_JRPC_PROVIDER_URL ?? PUBLIC_JSON_RPC_ENDPOINT + +export const testNetworkSpecification = specification.NetworkSpecification.parse({ + networkIdentifier: 'goerli', + gateway: { + url: 'http://localhost:8030/', + }, + networkProvider: { + url: testProviderUrl, + }, + indexerOptions: { + address: '0xf56b5d582920E4527A818FBDd801C0D80A394CB8', + mnemonic: + 'famous aspect index polar tornado zero wedding electric floor chalk tenant junk', + url: 'http://test-indexer.xyz', + }, + subgraphs: { + networkSubgraph: { + url: 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-goerli', + }, + epochSubgraph: { + url: 'http://test-url.xyz', + }, + }, + transactionMonitoring: { + gasIncreaseTimeout: 240000, + gasIncreaseFactor: 1.2, + baseFeePerGasMax: 100 * 10 ** 9, + maxTransactionAttempts: 0, + }, + dai: { + contractAddress: '0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448', + inject: false, + }, +}) export const setup = async () => { logger = createLogger({ @@ -49,43 +94,52 @@ export const setup = async () => { sequelize = await connectDatabase(__DATABASE__) models = defineIndexerManagementModels(sequelize) - address = '0x3C17A4c7cD8929B83e4705e04020fA2B1bca2E55' - contracts = await connectContracts(wallet, 5) - await sequelize.sync({ force: true }) + queryFeeModels = defineQueryFeeModels(sequelize) + metrics = createMetrics() + // Clearing the registry prevents duplicate metric registration in the default registry. + metrics.registry.clear() - wallet = Wallet.createRandom() + sequelize = await sequelize.sync({ force: true }) const statusEndpoint = 'http://localhost:8030/graphql' - const indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, + const indexNodeIDs = ['node_1'] + const graphNode = new GraphNode( + logger, + 'http://test-admin-endpoint.xyz', + 'https://test-query-endpoint.xyz', statusEndpoint, - }) + indexNodeIDs, + ) - const networkSubgraph = await NetworkSubgraph.create({ + const network = await Network.create( logger, - endpoint: - 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-testnet', - deployment: undefined, - }) - const indexNodeIDs = ['node_1'] + testNetworkSpecification, + queryFeeModels, + graphNode, + metrics, + ) + + const multiNetworks = new MultiNetworks( + [network], + (n: Network) => n.specification.networkIdentifier, + ) + + const defaults: IndexerManagementDefaults = { + globalIndexingRule: { + allocationAmount: parseGRT('100'), + parallelAllocations: 1, + requireSupported: true, + safety: true, + }, + } + indexerManagementClient = await createIndexerManagementClient({ models, - address: toAddress(address), - contracts: contracts, - indexingStatusResolver, + graphNode, indexNodeIDs, - deploymentManagementEndpoint: statusEndpoint, - networkSubgraph, logger, - defaults: { - globalIndexingRule: { - allocationAmount: parseGRT('1000'), - parallelAllocations: 1, - }, - }, - features: { - injectDai: false, - }, + defaults, + multiNetworks, }) server = await createIndexerManagementServer({ @@ -101,28 +155,66 @@ export const setup = async () => { defaultMaxEventListeners = process.getMaxListeners() process.setMaxListeners(100) - process.on('SIGTERM', await shutdownIndexerManagementServer) - process.on('SIGINT', await shutdownIndexerManagementServer) + process.on('SIGTERM', shutdownIndexerManagementServer) + process.on('SIGINT', shutdownIndexerManagementServer) +} - // Set global, deployment, and subgraph based test rules and cost model +// Set global, deployment, and subgraph based test rules and cost model +export const seed = async () => { const commands: string[][] = [ ['indexer', 'connect', 'http://localhost:18000'], - ['indexer', 'rules', 'set', 'global', 'minSignal', '500', 'allocationAmount', '.01'], - ['indexer', 'rules', 'set', 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr'], - ['indexer', 'rules', 'prepare', 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK'], [ 'indexer', 'rules', 'set', + '--network', + 'goerli', + 'global', + 'minSignal', + '500', + 'allocationAmount', + '.01', + ], + [ + 'indexer', + 'rules', + 'set', + '--network', + 'goerli', + 'QmZZtzZkfzCWMNrajxBf22q7BC9HzoT5iJUK3S8qA6zNZr', + ], + [ + 'indexer', + 'rules', + 'prepare', + '--network', + 'goerli', + 'QmZfeJYR86UARzp9HiXbURWunYgC9ywvPvoePNbuaATrEK', + ], + [ + 'indexer', + 'rules', + 'set', + '--network', + 'goerli', '0x0000000000000000000000000000000000000000-0', 'allocationAmount', '1000', ], - ['indexer', 'rules', 'offchain', '0x0000000000000000000000000000000000000000-1'], + [ + 'indexer', + 'rules', + 'offchain', + '--network', + 'goerli', + '0x0000000000000000000000000000000000000000-1', + ], [ 'indexer', 'rules', 'set', + '--network', + 'goerli', '0x0000000000000000000000000000000000000000-2', 'allocationAmount', '1000', @@ -150,8 +242,10 @@ export const setup = async () => { ], ] for (const command of commands) { - const { exitCode } = await runIndexerCli(command, process.cwd()) + const { exitCode, stderr, stdout } = await runIndexerCli(command, process.cwd()) if (exitCode == 1) { + console.error(stderr) + console.log(stdout) throw Error(`Setup failed: indexer rules or cost set command failed: ${command}`) } } @@ -175,6 +269,12 @@ export const teardown = async () => { await dropSequelizeModels() } +export const deleteFromAllTables = async () => { + const queryInterface = sequelize.getQueryInterface() + const allTables = await queryInterface.showAllTables() + await Promise.all(allTables.map(tableName => queryInterface.bulkDelete(tableName, {}))) +} + export interface CommandResult { exitCode: number | null stdout: string | undefined @@ -224,12 +324,31 @@ export const cliTest = ( `Make sure there is least one expected output located at the defined 'outputReferencePath', '${outputReferencePath}'`, ) } + + if (INDEXER_SAVE_CLI_TEST_OUTPUT) { + // To aid debugging, persist the output of CLI test commands for reviewing potential issues when tests fail. + // Requires setting the environment variable INDEXER_SAVE_CLI_TEST_OUTPUT. + const outfile = outputReferencePath.replace('references/', '') + const prefix = `/tmp/indexer-cli-test` + if (stdout) { + fs.writeFile(`${prefix}-${outfile}.stdout`, stripAnsi(stdout), 'utf8', err => { + err ? console.error('test: %s, error: %s', outfile, err) : null + }) + } + if (stderr) { + fs.writeFile(`${prefix}-${outfile}.stderr`, stripAnsi(stderr), 'utf8', err => { + err ? console.error('test: %s, error: %s', outfile, err) : null + }) + } + } + if (expectedExitCode !== undefined) { if (exitCode == undefined) { throw new Error('Expected exitCode (found undefined)') } expect(exitCode).toBe(expectedExitCode) } + if (expectedStderr) { if (stderr == undefined) { throw new Error('Expected stderr (found undefined)') From 5bd9ee560dd94e1698e787e625dd336dd05bde39 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:23:14 -0300 Subject: [PATCH 09/16] agent: new commands --- .../src/commands/common-options.ts | 155 +++ .../src/commands/error-handling.ts | 53 + .../src/commands/start-multi-network.ts | 130 +++ packages/indexer-agent/src/commands/start.ts | 991 ++++++++---------- 4 files changed, 772 insertions(+), 557 deletions(-) create mode 100644 packages/indexer-agent/src/commands/common-options.ts create mode 100644 packages/indexer-agent/src/commands/error-handling.ts create mode 100644 packages/indexer-agent/src/commands/start-multi-network.ts diff --git a/packages/indexer-agent/src/commands/common-options.ts b/packages/indexer-agent/src/commands/common-options.ts new file mode 100644 index 000000000..be8c571ac --- /dev/null +++ b/packages/indexer-agent/src/commands/common-options.ts @@ -0,0 +1,155 @@ +import fs from 'fs' + +import { Argv } from 'yargs' +import { parse as yaml_parse } from 'yaml' + +// Injects all CLI options shared between this module's commands into a `yargs.Argv` object. +export function injectCommonStartupOptions(argv: Argv): Argv { + argv + .option('index-node-ids', { + description: + 'Node IDs of Graph nodes to use for indexing (separated by commas)', + type: 'string', + array: true, + required: true, + coerce: ( + arg, // TODO: we shouldn't need to coerce because yargs already separates values by space + ) => + arg.reduce( + (acc: string[], value: string) => [...acc, ...value.split(',')], + [], + ), + group: 'Indexer Infrastructure', + }) + .option('indexer-management-port', { + description: 'Port to serve the indexer management API at', + type: 'number', + default: 8000, + required: false, + group: 'Indexer Infrastructure', + }) + .option('metrics-port', { + description: 'Port to serve Prometheus metrics at', + type: 'number', + default: 7300, + required: false, + group: 'Indexer Infrastructure', + }) + .option('syncing-port', { + description: + 'Port to serve the network subgraph and other syncing data for indexer service at', + type: 'number', + default: 8002, + required: false, + group: 'Indexer Infrastructure', + }) + .option('log-level', { + description: 'Log level', + type: 'string', + default: 'debug', + group: 'Indexer Infrastructure', + }) + .option('offchain-subgraphs', { + description: 'Subgraphs to index that are not on chain (comma-separated)', + type: 'string', + array: true, + default: [], + coerce: arg => + arg + .reduce( + (acc: string[], value: string) => [...acc, ...value.split(',')], + [], + ) + .map((id: string) => id.trim()) + .filter((id: string) => id.length > 0), + }) + .option('postgres-host', { + description: 'Postgres host', + type: 'string', + required: true, + group: 'Postgres', + }) + .option('postgres-port', { + description: 'Postgres port', + type: 'number', + default: 5432, + group: 'Postgres', + }) + .option('postgres-username', { + description: 'Postgres username', + type: 'string', + required: false, + default: 'postgres', + group: 'Postgres', + }) + .option('postgres-password', { + description: 'Postgres password', + type: 'string', + default: '', + required: false, + group: 'Postgres', + }) + .option('postgres-database', { + description: 'Postgres database name', + type: 'string', + required: true, + group: 'Postgres', + }) + .option('graph-node-query-endpoint', { + description: 'Graph Node endpoint for querying subgraphs', + type: 'string', + required: true, + group: 'Indexer Infrastructure', + }) + .option('graph-node-status-endpoint', { + description: 'Graph Node endpoint for indexing statuses etc.', + type: 'string', + required: true, + group: 'Indexer Infrastructure', + }) + .option('graph-node-admin-endpoint', { + description: + 'Graph Node endpoint for applying and updating subgraph deployments', + type: 'string', + required: true, + group: 'Indexer Infrastructure', + }) + .option('enable-auto-migration-support', { + description: + 'Auto migrate allocations from L1 to L2 (multi-network mode must be enabled)', + type: 'boolean', + required: false, + default: false, + group: 'Indexer Infrastructure', + }) + .config({ + key: 'config-file', + description: 'Indexer agent configuration file (YAML format)', + parseFn: function (cfgFilePath: string) { + return yaml_parse(fs.readFileSync(cfgFilePath, 'utf-8')) + }, + }) + .check(argv => { + // Unset arguments set to empty strings. + // This can happen when users set their options as enviroment variables and don't + // assign any vaule to them. + // + // For example: + // ```sh + // export INDEXER_AGENT_OFFCHAIN_SUBGRAPHS= + // export INDEXER_AGENT_OFFCHAIN_SUBGRAPHS= + // ``` + // + // In NodeJs, those enviroment variables will be set as an empty string instead of + // being undefined, which can cause parse errors when the Agent is building a + // NetworkSpecification. + for (const [key, value] of Object.entries(argv)) { + if (value === '') { + delete argv[key] + } + } + return true + }) + + return argv +} diff --git a/packages/indexer-agent/src/commands/error-handling.ts b/packages/indexer-agent/src/commands/error-handling.ts new file mode 100644 index 000000000..8d4cd8714 --- /dev/null +++ b/packages/indexer-agent/src/commands/error-handling.ts @@ -0,0 +1,53 @@ +import { ZodError } from 'zod' +import { + fromZodError, + FromZodErrorOptions, + ValidationError, +} from 'zod-validation-error' + +type ErrorFormatOptions = Pick< + Required, + 'issueSeparator' | 'prefix' | 'prefixSeparator' +> + +const errorFormatOptions: ErrorFormatOptions = { + // Arbitrary character sequence that is unlikely to appear as part of a validation + // message. It is used for splitting the concateneted error message into individual + // issues. + issueSeparator: '@#validation-error#@', + prefixSeparator: ':', + prefix: 'Indexer Agent Configuration Error(s)', +} + +// Converts a ValidationError into human-friendly error messages. It utilizes +// 'zod-validation-error' to produce these messages from Zod errors, then re-formats the +// concatenated messages into a list with one issue per line, optionally including the +// original file path in the error message. +function formatError( + error: ValidationError, + errorFormatOptions: ErrorFormatOptions, + filePath?: string, +) { + const prefix = errorFormatOptions.prefix + errorFormatOptions.prefixSeparator + const issues = error + .toString() + .substring(prefix.length) + .split(errorFormatOptions.issueSeparator) + .map(issue => issue.trim()) + .map(issue => `- ${issue}`) + .join('\n') + const file = filePath ? ` [ file: ${filePath} ]` : '' + return `${prefix}${file}\n${issues}` +} + +// Helper funciton that processeses a ZodError and displays validation issues in the +// terminal using a human-friendly format +export function displayZodParsingError(error: ZodError, filePath?: string) { + const validationError = fromZodError(error, errorFormatOptions) + const formattedError = formatError( + validationError, + errorFormatOptions, + filePath, + ) + console.error(formattedError) +} diff --git a/packages/indexer-agent/src/commands/start-multi-network.ts b/packages/indexer-agent/src/commands/start-multi-network.ts new file mode 100644 index 000000000..8f7d8214b --- /dev/null +++ b/packages/indexer-agent/src/commands/start-multi-network.ts @@ -0,0 +1,130 @@ +import * as fs from 'fs' +import * as path from 'path' +import { specification as spec } from '@graphprotocol/indexer-common' +import * as YAML from 'yaml' +import { Argv } from 'yargs' +import { injectCommonStartupOptions } from './common-options' +import { displayZodParsingError } from './error-handling' +import { Logger } from '@graphprotocol/common-ts' + +export const startMultiNetwork = { + command: 'start', + describe: 'Start the Agent in multiple Protocol Networks', + builder: (args: Argv): Argv => { + const updatedArgs = injectCommonStartupOptions(args) + return updatedArgs.option('network-specifications-directory', { + alias: 'dir', + description: 'Path to a directory containing network specification files', + type: 'string', + required: true, + }) + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + handler: (_argv: any) => {}, +} + +export function parseNetworkSpecifications( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + argv: any, + logger: Logger, +): spec.NetworkSpecification[] { + const dir: string = argv.dir + const yamlFiles = scanDirectoryForYamlFiles(dir, logger) + return parseYamlFiles(yamlFiles) +} + +function scanDirectoryForYamlFiles( + directoryPath: string, + logger: Logger, +): string[] { + const yamlFiles: string[] = [] + + // Check if the directory exists + if (!fs.existsSync(directoryPath)) { + throw new Error(`Directory does not exist: ${directoryPath}`) + } + + // Check if the provided path is a directory + const isDirectory = fs.lstatSync(directoryPath).isDirectory() + if (!isDirectory) { + throw new Error(`Provided path is not a directory: ${directoryPath}`) + } + + // Read the directory + const files = fs.readdirSync(directoryPath) + logger.trace( + `Network configuration directory contains ${files.length} file(s)`, + { directoryPath, files }, + ) + + // Iterate over each file in the directory + for (const file of files) { + const filePath = path.join(directoryPath, file) + + // Check if the file is a regular file and has a YAML extension + const isFile = fs.lstatSync(filePath).isFile() + const isYaml = /\.ya?ml$/i.test(file) + logger.trace(`Network specification candidate file found: '${file}'`, { + isFile, + isYaml, + }) + if (isFile && isYaml) { + try { + // Check if the file can be read + fs.accessSync(filePath, fs.constants.R_OK) + yamlFiles.push(filePath) + } catch (error) { + throw new Error(`Cannot read file: ${filePath}`) + } + } + } + + // Check if at least one YAMl file was found + if (yamlFiles.length === 0) { + throw new Error( + `No YAML file was found in '${directoryPath}'. At least one file is required.`, + ) + } + + return yamlFiles +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function readYamlFile(filePath: string): any { + const text = fs.readFileSync(filePath, 'utf8') + let content + try { + content = YAML.parse(text) + } catch (yamlParseError) { + throw new Error( + `Failed to parse network specification YAML file at ${filePath}.\n${yamlParseError}`, + ) + } + if (!content) { + throw new Error( + `Failed to parse network specification YAML file: ${filePath}.\nFile is empty.`, + ) + } + return content +} + +function parseYamlFile(filePath: string): spec.NetworkSpecification { + let yamlContent + try { + yamlContent = readYamlFile(filePath) + } catch (error) { + console.log(error.message) + process.exit(1) + } + + try { + return spec.NetworkSpecification.parse(yamlContent) + } catch (error) { + displayZodParsingError(error, filePath) + process.exit(1) + } +} + +function parseYamlFiles(filePaths: string[]): spec.NetworkSpecification[] { + return filePaths.map(parseYamlFile) +} diff --git a/packages/indexer-agent/src/commands/start.ts b/packages/indexer-agent/src/commands/start.ts index 8a50e71f4..7327faf8c 100644 --- a/packages/indexer-agent/src/commands/start.ts +++ b/packages/indexer-agent/src/commands/start.ts @@ -1,53 +1,52 @@ -import fs from 'fs' import path from 'path' +import axios from 'axios' import { Argv } from 'yargs' -import { parse as yaml_parse } from 'yaml' import { SequelizeStorage, Umzug } from 'umzug' - import { - connectContracts, connectDatabase, - createLogger, createMetrics, createMetricsServer, formatGRT, - parseGRT, - SubgraphDeploymentID, - toAddress, Logger, + SubgraphDeploymentID, } from '@graphprotocol/common-ts' import { - AllocationReceiptCollector, createIndexerManagementClient, createIndexerManagementServer, defineIndexerManagementModels, defineQueryFeeModels, + GraphNode, indexerError, IndexerErrorCode, - IndexingStatusResolver, + MultiNetworks, Network, - NetworkSubgraph, + Operator, registerIndexerErrorMetrics, - AllocationManagementMode, - NetworkMonitor, - EpochSubgraph, resolveChainId, + specification as spec, } from '@graphprotocol/indexer-common' -import { startAgent } from '../agent' -import { Indexer } from '../indexer' -import { Wallet } from 'ethers' -import { Network as NetworkMetadata } from '@ethersproject/networks' +import { Agent } from '../agent' import { startCostModelAutomation } from '../cost' import { createSyncingServer } from '../syncing-server' -import { monitorEthBalance } from '../utils' +import { injectCommonStartupOptions } from './common-options' +import pMap from 'p-map' +import { NetworkSpecification } from '@graphprotocol/indexer-common/dist/network-specification' +import { BigNumber } from 'ethers' +import { displayZodParsingError } from './error-handling' -export default { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AgentOptions = { [key: string]: any } & Argv['argv'] + +export const start = { command: 'start', describe: 'Start the agent', - builder: (yargs: Argv): Argv => { - return yargs - .option('ethereum', { + builder: (args: Argv): Argv => { + const updatedArgs = injectCommonStartupOptions(args) + return updatedArgs + .option('network-provider', { + alias: 'ethereum', description: 'Ethereum node or provider URL', + array: false, type: 'string', required: true, group: 'Ethereum', @@ -64,7 +63,6 @@ export default { type: 'number', default: 240, group: 'Ethereum', - coerce: arg => arg * 1000, }) .option('gas-increase-factor', { description: @@ -79,7 +77,6 @@ export default { default: 100, deprecated: true, group: 'Ethereum', - coerce: arg => arg * 10 ** 9, }) .option('base-fee-per-gas-max', { description: @@ -87,7 +84,6 @@ export default { type: 'number', required: false, group: 'Ethereum', - coerce: arg => arg * 10 ** 9, }) .option('transaction-attempts', { description: @@ -108,25 +104,6 @@ export default { required: true, group: 'Ethereum', }) - .option('graph-node-query-endpoint', { - description: 'Graph Node endpoint for querying subgraphs', - type: 'string', - required: true, - group: 'Indexer Infrastructure', - }) - .option('graph-node-status-endpoint', { - description: 'Graph Node endpoint for indexing statuses etc.', - type: 'string', - required: true, - group: 'Indexer Infrastructure', - }) - .option('graph-node-admin-endpoint', { - description: - 'Graph Node endpoint for applying and updating subgraph deployments', - type: 'string', - required: true, - group: 'Indexer Infrastructure', - }) .option('public-indexer-url', { description: 'Indexer endpoint for receiving requests from the network', type: 'string', @@ -136,22 +113,34 @@ export default { .options('indexer-geo-coordinates', { description: `Coordinates describing the Indexer's location using latitude and longitude`, type: 'string', - array: true, + nargs: 2, default: ['31.780715', '-41.179504'], group: 'Indexer Infrastructure', - coerce: arg => - arg.reduce( - (acc: string[], value: string) => [...acc, ...value.split(' ')], - [], - ), + coerce: function ( + coordinates: string | [string, string], + ): [number, number] { + if (typeof coordinates === 'string') { + // When this value is set in an enviromnent variable, yarns passes + // it as a single string. + + // Yargs should have passed 2 arguments to this functions, so we + // expect this array has two elements + return coordinates.split(' ').map(parseFloat) as [number, number] + } + // When this value is set in the command line, yargs passes it as an + // array of two strings. + return coordinates.map(parseFloat) as [number, number] + }, }) .option('network-subgraph-deployment', { description: 'Network subgraph deployment', + array: false, type: 'string', group: 'Network Subgraph', }) .option('network-subgraph-endpoint', { description: 'Endpoint to query the network subgraph from', + array: false, type: 'string', group: 'Network Subgraph', }) @@ -163,53 +152,18 @@ export default { }) .option('epoch-subgraph-endpoint', { description: 'Endpoint to query the epoch block oracle subgraph from', + array: false, type: 'string', required: true, group: 'Protocol', }) - .option('index-node-ids', { - description: - 'Node IDs of Graph nodes to use for indexing (separated by commas)', - type: 'string', - array: true, - required: true, - coerce: arg => - arg.reduce( - (acc: string[], value: string) => [...acc, ...value.split(',')], - [], - ), - group: 'Indexer Infrastructure', - }) .option('default-allocation-amount', { description: 'Default amount of GRT to allocate to a subgraph deployment', - type: 'string', - default: '0.01', - required: false, - group: 'Protocol', - coerce: arg => parseGRT(arg), - }) - .option('indexer-management-port', { - description: 'Port to serve the indexer management API at', - type: 'number', - default: 8000, - required: false, - group: 'Indexer Infrastructure', - }) - .option('metrics-port', { - description: 'Port to serve Prometheus metrics at', - type: 'number', - defaut: 7300, - required: false, - group: 'Indexer Infrastructure', - }) - .option('syncing-port', { - description: - 'Port to serve the network subgraph and other syncing data for indexer service at', type: 'number', - default: 8002, + default: 0.01, required: false, - group: 'Indexer Infrastructure', + group: 'Protocol', }) .option('restake-rewards', { description: `Restake claimed indexer rewards, if set to 'false' rewards will be returned to the wallet`, @@ -219,17 +173,15 @@ export default { }) .option('rebate-claim-threshold', { description: `Minimum value of rebate for a single allocation (in GRT) in order for it to be included in a batch rebate claim on-chain`, - type: 'string', - default: '200', // This value (the marginal gain of a claim in GRT), should always exceed the marginal cost of a claim (in ETH gas) + type: 'number', + default: 200, // This value (the marginal gain of a claim in GRT), should always exceed the marginal cost of a claim (in ETH gas) group: 'Query Fees', - coerce: arg => parseGRT(arg), }) .option('rebate-claim-batch-threshold', { description: `Minimum total value of all rebates in an batch (in GRT) before the batch is claimed on-chain`, - type: 'string', - default: '2000', + type: 'number', + default: 2000, group: 'Query Fees', - coerce: arg => parseGRT(arg), }) .option('rebate-claim-max-batch-size', { description: `Maximum number of rebates inside a batch. Upper bound is constrained by available system memory, and by the block gas limit`, @@ -239,17 +191,15 @@ export default { }) .option('voucher-redemption-threshold', { description: `Minimum value of rebate for a single allocation (in GRT) in order for it to be included in a batch rebate claim on-chain`, - type: 'string', - default: '200', // This value (the marginal gain of a claim in GRT), should always exceed the marginal cost of a claim (in ETH gas) + type: 'number', + default: 200, // This value (the marginal gain of a claim in GRT), should always exceed the marginal cost of a claim (in ETH gas) group: 'Query Fees', - coerce: arg => parseGRT(arg), }) .option('voucher-redemption-batch-threshold', { description: `Minimum total value of all rebates in an batch (in GRT) before the batch is claimed on-chain`, - type: 'string', - default: '2000', + type: 'number', + default: 2000, group: 'Query Fees', - coerce: arg => parseGRT(arg), }) .option('voucher-redemption-max-batch-size', { description: `Maximum number of rebates inside a batch. Upper bound is constrained by available system memory, and by the block gas limit`, @@ -271,65 +221,12 @@ export default { // Default to USDC default: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', }) - .option('postgres-host', { - description: 'Postgres host', - type: 'string', - required: true, - group: 'Postgres', - }) - .option('postgres-port', { - description: 'Postgres port', - type: 'number', - default: 5432, - group: 'Postgres', - }) - .option('postgres-username', { - description: 'Postgres username', - type: 'string', - required: false, - default: 'postgres', - group: 'Postgres', - }) - .option('postgres-password', { - description: 'Postgres password', - type: 'string', - default: '', - required: false, - group: 'Postgres', - }) - .option('postgres-database', { - description: 'Postgres database name', - type: 'string', - required: true, - group: 'Postgres', - }) - .option('log-level', { - description: 'Log level', - type: 'string', - default: 'debug', - group: 'Indexer Infrastructure', - }) .option('register', { description: 'Whether to register the indexer on chain', type: 'boolean', default: true, group: 'Protocol', }) - .option('offchain-subgraphs', { - description: - 'Subgraphs to index that are not on chain (comma-separated)', - type: 'string', - array: true, - default: [], - coerce: arg => - arg - .reduce( - (acc: string[], value: string) => [...acc, ...value.split(',')], - [], - ) - .map((id: string) => id.trim()) - .filter((id: string) => id.length > 0), - }) .option('poi-disputable-epochs', { description: 'The number of epochs in the past to look for potential POI disputes', @@ -343,12 +240,34 @@ export default { default: false, group: 'Disputes', }) + .option('gateway-endpoint', { + description: 'Gateway endpoint base URL', + alias: 'collect-receipts-endpoint', + type: 'string', + array: false, + required: true, + group: 'Query Fees', + }) + .option('allocation-management', { + description: + 'Indexer agent allocation management automation mode (auto|manual) ', + type: 'string', + required: false, + default: 'auto', + group: 'Indexer Infrastructure', + }) + .option('auto-allocation-min-batch-size', { + description: `Minimum number of allocation transactions inside a batch for auto allocation management. No obvious upperbound, with default of 1`, + type: 'number', + default: 1, + group: 'Indexer Infrastructure', + }) .check(argv => { if ( !argv['network-subgraph-endpoint'] && !argv['network-subgraph-deployment'] ) { - return `At least one of --network-subgraph-endpoint and --network-subgraph-deployment must be provided` + return 'At least one of --network-subgraph-endpoint and --network-subgraph-deployment must be provided' } if (argv['indexer-geo-coordinates']) { const [geo1, geo2] = argv['indexer-geo-coordinates'] @@ -357,7 +276,7 @@ export default { } } if (argv['gas-increase-timeout']) { - if (argv['gas-increase-timeout'] < 30000) { + if (argv['gas-increase-timeout'] < 30) { return 'Invalid --gas-increase-timeout provided. Must be at least 30 seconds' } } @@ -372,439 +291,397 @@ export default { } return true }) - .option('collect-receipts-endpoint', { - description: 'Client endpoint for collecting receipts', - type: 'string', - required: false, - group: 'Query Fees', - }) - .option('allocation-management', { - description: - 'Indexer agent allocation management automation mode (auto|manual) ', - type: 'string', - required: false, - default: 'auto', - group: 'Indexer Infrastructure', - }) - .option('auto-allocation-min-batch-size', { - description: `Minimum number of allocation transactions inside a batch for auto allocation management. No obvious upperbound, with default of 1`, - type: 'number', - default: 1, - group: 'Indexer Infrastructure', - }) - .config({ - key: 'config-file', - description: 'Indexer agent configuration file (YAML format)', - parseFn: function (cfgFilePath: string) { - return yaml_parse(fs.readFileSync(cfgFilePath, 'utf-8')) - }, - }) }, - handler: async ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - argv: { [key: string]: any } & Argv['argv'], - ): Promise => { - const logger = createLogger({ - name: 'IndexerAgent', - async: false, - level: argv.logLevel, - }) - - if (argv.gasIncreaseTimeout < 90000) { - logger.warn( - 'Gas increase timeout is set to less than 90 seconds (~ 6 blocks). This may lead to high gas usage', - { gasIncreaseTimeout: argv.gasIncreaseTimeout / 1000.0 }, - ) - } - - if (argv.gasIncreaseFactor > 1.5) { - logger.warn( - `Gas increase factor is set to > 1.5. This may lead to high gas usage`, - { gasIncreaseFactor: argv.gasIncreaseFactor }, - ) - } + // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any + handler: (_argv: any) => {}, +} - if (argv.rebateClaimThreshold.lt(argv.voucherRedemptionThreshold)) { - logger.warn( - `Rebate single minimum claim value is less than voucher minimum redemption value, but claims depend on redemptions`, - { - voucherRedemptionThreshold: formatGRT( - argv.voucherRedemptionThreshold, - ), - rebateClaimThreshold: formatGRT(argv.rebateClaimThreshold), - }, - ) - } +export async function createNetworkSpecification( + argv: AgentOptions, +): Promise { + const gateway = { + url: argv.gatewayEndpoint, + } - if (argv.rebateClaimThreshold.eq(0)) { - logger.warn( - `Minimum query fee rebate value is 0 GRT, which may lead to claiming unprofitable rebates`, - ) - } + const indexerOptions = { + address: argv.indexerAddress, + mnemonic: argv.mnemonic, + url: argv.publicIndexerUrl, + geoCoordinates: argv.indexerGeoCoordinates, + restakeRewards: argv.restakeRewards, + rebateClaimThreshold: argv.rebateClaimThreshold, + rebateClaimBatchThreshold: argv.rebateClaimBatchThreshold, + rebateClaimMaxBatchSize: argv.rebateClaimMaxBatchSize, + poiDisputeMonitoring: argv.poiDisputeMonitoring, + poiDisputableEpochs: argv.poiDisputableEpochs, + defaultAllocationAmount: argv.defaultAllocationAmount, + voucherRedemptionThreshold: argv.voucherRedemptionThreshold, + voucherRedemptionBatchThreshold: argv.voucherRedemptionBatchThreshold, + voucherRedemptionMaxBatchSize: argv.voucherRedemptionMaxBatchSize, + allocationManagementMode: argv.allocationManagement, + autoAllocationMinBatchSize: argv.autoAllocationMinBatchSize, + } - if (argv.rebateClaimMaxBatchSize > 200) { - logger.warn( - `Setting the max batch size for rebate claims to more than 200 may result in batches that are too large to fit into a block`, - { rebateClaimMaxBatchSize: argv.rebateClaimMaxBatchSize }, - ) - } + const transactionMonitoring = { + gasIncreaseTimeout: argv.gasIncreaseTimeout, + gasIncreaseFactor: argv.gasIncreaseFactor, + baseFeePerGasMax: argv.baseFeeGasMax, + maxTransactionAttempts: argv.maxTransactionAttempts, + } - if (argv.voucherRedemptionThreshold.eq(0)) { - logger.warn( - `Minimum voucher redemption value is 0 GRT, which may lead to redeeming unprofitable vouchers`, - ) - } + const subgraphs = { + networkSubgraph: { + deployment: argv.networkSubgraphDeployment, + url: argv.networkSubgraphEndpoint, + }, + epochSubgraph: { + // TODO: We should consider indexing the Epoch Subgraph, similar + // to how we currently do it for the Network Subgraph. + url: argv.epochSubgraphEndpoint, + }, + } - if (argv.voucherRedemptionMaxBatchSize > 200) { - logger.warn( - `Setting the max batch size for voucher redemptions to more than 200 may result in batches that are too large to fit into a block`, - { voucherRedemptionMaxBatchSize: argv.voucherRedemptionMaxBatchSize }, - ) - } + const dai = { + contractAddress: argv.daiContractAddress, + inject: argv.injectDai, + } - process.on('unhandledRejection', err => { - logger.warn(`Unhandled promise rejection`, { - err: indexerError(IndexerErrorCode.IE035, err), - }) - }) + const networkProvider = { + url: argv.networkProvider, + } - process.on('uncaughtException', err => { - logger.warn(`Uncaught exception`, { - err: indexerError(IndexerErrorCode.IE036, err), - }) + // Since we can't infer the network identifier, we must ask the configured + // JSON RPC provider for its `chainID`. + const chainId = await fetchChainId(networkProvider.url) + const networkIdentifier = resolveChainId(chainId) + + try { + return spec.NetworkSpecification.parse({ + networkIdentifier, + gateway, + indexerOptions, + transactionMonitoring, + subgraphs, + networkProvider, + dai, }) + } catch (parsingError) { + displayZodParsingError(parsingError) + process.exit(1) + } +} - // Spin up a metrics server - const metrics = createMetrics() - createMetricsServer({ - logger: logger.child({ component: 'MetricsServer' }), - registry: metrics.registry, - port: argv.metricsPort, +// TODO: Split this code into two functions: +// 1. [X] Create NetworkSpecification +// 2. [ ] Start Agent with NetworkSpecification as input. +export async function run( + argv: AgentOptions, + networkSpecifications: spec.NetworkSpecification[], + logger: Logger, +): Promise { + // -------------------------------------------------------------------------------- + // * Configure event listeners for unhandled promise rejections and uncaught + // exceptions. + // -------------------------------------------------------------------------------- + process.on('unhandledRejection', err => { + logger.warn(`Unhandled promise rejection`, { + err: indexerError(IndexerErrorCode.IE035, err), }) + }) - // Register indexer error metrics so we can track any errors that happen - // inside the agent - registerIndexerErrorMetrics(metrics) - - const indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, - statusEndpoint: argv.graphNodeStatusEndpoint, + process.on('uncaughtException', err => { + logger.warn(`Uncaught exception`, { + err: indexerError(IndexerErrorCode.IE036, err), }) + }) + + // -------------------------------------------------------------------------------- + // * Metrics Server + // -------------------------------------------------------------------------------- + const metrics = createMetrics() + createMetricsServer({ + logger: logger.child({ component: 'MetricsServer' }), + registry: metrics.registry, + port: argv.metricsPort, + }) + + // Register indexer error metrics so we can track any errors that happen + // inside the agent + registerIndexerErrorMetrics(metrics) + + // -------------------------------------------------------------------------------- + // * Graph Node + // ---------------------------------------------------------------- ---------------- + const graphNode = new GraphNode( + logger, + argv.graphNodeAdminEndpoint, + argv.graphNodeQueryEndpoint, + argv.graphNodeStatusEndpoint, + argv.indexNodeIds, + ) - // Parse the Network Subgraph optional argument - const networkSubgraphDeploymentId = argv.networkSubgraphDeployment - ? new SubgraphDeploymentID(argv.networkSubgraphDeployment) - : undefined - - const networkSubgraph = await NetworkSubgraph.create({ - logger, - endpoint: argv.networkSubgraphEndpoint, - deployment: - networkSubgraphDeploymentId !== undefined - ? { - indexingStatusResolver: indexingStatusResolver, - deployment: networkSubgraphDeploymentId, - graphNodeQueryEndpoint: argv.graphNodeQueryEndpoint, - } - : undefined, + // -------------------------------------------------------------------------------- + // * Database - Connection + // -------------------------------------------------------------------------------- + logger.info('Connect to database', { + host: argv.postgresHost, + port: argv.postgresPort, + database: argv.postgresDatabase, + }) + const sequelize = await connectDatabase({ + logging: undefined, + host: argv.postgresHost, + port: argv.postgresPort, + username: argv.postgresUsername, + password: argv.postgresPassword, + database: argv.postgresDatabase, + }) + logger.info('Successfully connected to database') + + // -------------------------------------------------------------------------------- + // * Database - Migrations + // -------------------------------------------------------------------------------- + logger.info(`Run database migrations`) + + // If the application is being executed using ts-node __dirname may be in /src rather than /dist + const migrations_path = __dirname.includes('dist') + ? path.join(__dirname, '..', 'db', 'migrations', '*.js') + : path.join(__dirname, '..', '..', 'dist', 'db', 'migrations', '*.js') + + try { + const umzug = new Umzug({ + migrations: { + glob: migrations_path, + }, + context: { + queryInterface: sequelize.getQueryInterface(), + logger, + graphNodeAdminEndpoint: argv.graphNodeAdminEndpoint, + networkSpecifications, + }, + storage: new SequelizeStorage({ sequelize }), + logger: console, }) - - const networkProvider = await Network.provider( - logger, - metrics, - argv.ethereum, - argv.ethereumPollingInterval, - ) - - const networkMeta = await networkProvider.getNetwork() - - logger.info(`Connect to contracts`, { - network: networkMeta.name, - chainId: networkMeta.chainId, - providerNetworkChainID: networkProvider.network.chainId, + const pending = await umzug.pending() + const executed = await umzug.executed() + logger.debug(`Migrations status`, { pending, executed }) + await umzug.up() + } catch (err) { + logger.fatal(`Failed to run database migrations`, { + err: indexerError(IndexerErrorCode.IE001, err), }) + process.exit(1) + } + logger.info(`Successfully ran database migrations`) + + // -------------------------------------------------------------------------------- + // * Database - Sync Models + // -------------------------------------------------------------------------------- + logger.info(`Sync database models`) + const managementModels = defineIndexerManagementModels(sequelize) + const queryFeeModels = defineQueryFeeModels(sequelize) + await sequelize.sync() + logger.info(`Successfully synced database models`) + + // -------------------------------------------------------------------------------- + // * Networks + // -------------------------------------------------------------------------------- + logger.info('Connect to network/s', { + networks: networkSpecifications.map(spec => spec.networkIdentifier), + }) + + const networks: Network[] = await pMap( + networkSpecifications, + async (spec: NetworkSpecification) => + Network.create(logger, spec, queryFeeModels, graphNode, metrics), + ) - logger.info(`Connect wallet`, { - network: networkMeta.name, - chainId: networkMeta.chainId, - }) - let wallet = Wallet.fromMnemonic(argv.mnemonic) - wallet = wallet.connect(networkProvider) - logger.info(`Connected wallet`) - - let contracts = undefined - try { - contracts = await connectContracts(wallet, networkMeta.chainId) - } catch (err) { - logger.error( - `Failed to connect to contracts, please ensure you are using the intended Ethereum network`, - { - err, - }, - ) - process.exit(1) - } - logger.info(`Successfully connected to contracts`, { - curation: contracts.curation.address, - disputeManager: contracts.disputeManager.address, - epochManager: contracts.epochManager.address, - gns: contracts.gns.address, - rewardsManager: contracts.rewardsManager.address, - serviceRegistry: contracts.serviceRegistry.address, - staking: contracts.staking.address, - token: contracts.token.address, - }) + // -------------------------------------------------------------------------------- + // * Indexer Management (GraphQL) Server + // -------------------------------------------------------------------------------- + const multiNetworks = new MultiNetworks( + networks, + (n: Network) => n.specification.networkIdentifier, + ) - const indexerAddress = toAddress(argv.indexerAddress) + const indexerManagementClient = await createIndexerManagementClient({ + models: managementModels, + graphNode, + indexNodeIDs: argv.indexNodeIds, + logger, + defaults: { + globalIndexingRule: { + // TODO: Update this, there will be defaults per network + allocationAmount: BigNumber.from(100), + parallelAllocations: 1, + }, + }, + multiNetworks, + }) + + // * Indexer Management Server + logger.info('Launch indexer management API server', { + port: argv.indexerManagementPort, + }) + await createIndexerManagementServer({ + logger, + client: indexerManagementClient, + port: argv.indexerManagementPort, + }) + logger.info(`Successfully launched indexer management API server`) + + // -------------------------------------------------------------------------------- + // * Cost Model Automation + // -------------------------------------------------------------------------------- + startCostModelAutomation({ + logger, + networks, + indexerManagement: indexerManagementClient, + metrics, + }) + + // -------------------------------------------------------------------------------- + // * Syncing Server + // -------------------------------------------------------------------------------- + logger.info(`Launch syncing server`) + + await createSyncingServer({ + logger, + networkSubgraphs: await multiNetworks.map( + async network => network.networkSubgraph, + ), + port: argv.syncingPort, + }) + logger.info(`Successfully launched syncing server`) + + // -------------------------------------------------------------------------------- + // * Operator + // -------------------------------------------------------------------------------- + const operators: Operator[] = await pMap( + networkSpecifications, + async (spec: NetworkSpecification) => + new Operator(logger, indexerManagementClient, spec), + ) - const epochSubgraph = await EpochSubgraph.create(argv.epochSubgraphEndpoint) + // -------------------------------------------------------------------------------- + // * The Agent itself + // -------------------------------------------------------------------------------- + const agent = new Agent( + logger, + metrics, + graphNode, + operators, + indexerManagementClient, + networks, + argv.offchainSubgraphs.map((s: string) => new SubgraphDeploymentID(s)), + argv.enableAutoMigrationSupport, + ) + await agent.start() +} - const networkMonitor = new NetworkMonitor( - resolveChainId(networkMeta.chainId), - contracts, - toAddress(indexerAddress), - logger.child({ component: 'NetworkMonitor' }), - indexingStatusResolver, - networkSubgraph, - networkProvider, - epochSubgraph, +// Review CLI arguments, emit non-interrupting warnings about expected behavior. +// Perform this check immediately after parsing the command line arguments. +// Ideally, this check could be made inside yargs.check, but we can't access a Logger +// instance in that context. +export function reviewArgumentsForWarnings(argv: AgentOptions, logger: Logger) { + const { + gasIncreaseTimeout, + gasIncreaseFactor, + rebateClaimThreshold, + voucherRedemptionThreshold, + rebateClaimMaxBatchSize, + voucherRedemptionMaxBatchSize, + collectReceiptsEndpoint, + } = argv + + logger.debug('Reviewing Indexer Agent configuration') + + const advisedGasIncreaseTimeout = 90000 + const advisedGasIncreaseFactor = 1.5 + const advisedRebateClaimMaxBatchSize = 200 + const advisedVoucherRedemptionMaxBatchSize = 200 + + if (collectReceiptsEndpoint) { + logger.warn( + "The option '--collect-receipts-endpoint' is depracated. " + + "Please use the option '--gateway-endpoint' to inform the Gatway base URL.", ) + } - logger.info('Connect to database', { - host: argv.postgresHost, - port: argv.postgresPort, - database: argv.postgresDatabase, - }) - const sequelize = await connectDatabase({ - logging: undefined, - host: argv.postgresHost, - port: argv.postgresPort, - username: argv.postgresUsername, - password: argv.postgresPassword, - database: argv.postgresDatabase, - }) - logger.info('Successfully connected to database') - - // Automatic database migrations - logger.info(`Run database migrations`) - - // If the application is being executed using ts-node __dirname may be in /src rather than /dist - const migrations_path = __dirname.includes('dist') - ? path.join(__dirname, '..', 'db', 'migrations', '*.js') - : path.join(__dirname, '..', '..', 'dist', 'db', 'migrations', '*.js') - - try { - const umzug = new Umzug({ - migrations: { - glob: migrations_path, - }, - context: { - queryInterface: sequelize.getQueryInterface(), - logger, - indexingStatusResolver, - graphNodeAdminEndpoint: argv.graphNodeAdminEndpoint, - networkMonitor, - }, - storage: new SequelizeStorage({ sequelize }), - logger: console, - }) - const pending = await umzug.pending() - const executed = await umzug.executed() - logger.debug(`Migrations status`, { pending, executed }) - await umzug.up() - } catch (err) { - logger.fatal(`Failed to run database migrations`, { - err: indexerError(IndexerErrorCode.IE001, err), - }) - process.exit(1) - } - logger.info(`Successfully ran database migrations`) - - logger.info(`Sync database models`) - const managementModels = defineIndexerManagementModels(sequelize) - const queryFeeModels = defineQueryFeeModels(sequelize) - await sequelize.sync() - logger.info(`Successfully synced database models`) - - logger.info('Connect to network') - const maxGasFee = argv.baseFeeGasMax || argv.gasPriceMax - const network = await Network.create( - logger, - networkProvider, - contracts, - wallet, - indexerAddress, - argv.publicIndexerUrl, - argv.indexerGeoCoordinates, - networkSubgraph, - argv.restakeRewards, - argv.rebateClaimThreshold, - argv.rebateClaimBatchThreshold, - argv.rebateClaimMaxBatchSize, - argv.poiDisputeMonitoring, - argv.poiDisputableEpochs, - argv.gasIncreaseTimeout, - argv.gasIncreaseFactor, - maxGasFee, - argv.transactionAttempts, + if (gasIncreaseTimeout < advisedGasIncreaseTimeout) { + logger.warn( + `Gas increase timeout is set to less than ${ + gasIncreaseTimeout / 1000 + } seconds. This may lead to high gas usage`, + { gasIncreaseTimeout: gasIncreaseTimeout / 1000.0 }, ) - logger.info('Successfully connected to network', { - restakeRewards: argv.restakeRewards, - }) + } - const receiptCollector = new AllocationReceiptCollector({ - logger, - metrics, - transactionManager: network.transactionManager, - models: queryFeeModels, - allocationExchange: network.contracts.allocationExchange, - collectEndpoint: argv.collectReceiptsEndpoint, - voucherRedemptionThreshold: argv.voucherRedemptionThreshold, - voucherRedemptionBatchThreshold: argv.voucherRedemptionBatchThreshold, - voucherRedemptionMaxBatchSize: argv.voucherRedemptionMaxBatchSize, - }) - await receiptCollector.queuePendingReceiptsFromDatabase() - - logger.info('Launch indexer management API server') - const allocationManagementMode = - AllocationManagementMode[ - argv.allocationManagement.toUpperCase() as keyof typeof AllocationManagementMode - ] - const indexerManagementClient = await createIndexerManagementClient({ - models: managementModels, - address: indexerAddress, - contracts, - indexingStatusResolver, - indexNodeIDs: argv.indexNodeIds, - deploymentManagementEndpoint: argv.graphNodeAdminEndpoint, - networkSubgraph, - logger, - defaults: { - globalIndexingRule: { - allocationAmount: argv.defaultAllocationAmount, - parallelAllocations: 1, - }, - }, - features: { - injectDai: argv.injectDai, + if (gasIncreaseFactor > advisedGasIncreaseTimeout) { + logger.warn( + `Gas increase factor is set to > ${advisedGasIncreaseFactor}. ` + + 'This may lead to high gas usage', + { gasIncreaseFactor: gasIncreaseFactor }, + ) + } + if (rebateClaimThreshold < voucherRedemptionThreshold) { + logger.warn( + 'Rebate single minimum claim value is less than voucher minimum redemption value, ' + + 'but claims depend on redemptions', + { + voucherRedemptionThreshold: formatGRT(voucherRedemptionThreshold), + rebateClaimThreshold: formatGRT(rebateClaimThreshold), }, - transactionManager: network.transactionManager, - receiptCollector, - networkMonitor, - allocationManagementMode, - autoAllocationMinBatchSize: argv.autoAllocationMinBatchSize, - }) - - await createIndexerManagementServer({ - logger, - client: indexerManagementClient, - port: argv.indexerManagementPort, - }) - logger.info(`Successfully launched indexer management API server`) - - const indexer = new Indexer( - logger, - argv.graphNodeAdminEndpoint, - indexingStatusResolver, - indexerManagementClient, - argv.indexNodeIds, - argv.defaultAllocationAmount, - indexerAddress, - allocationManagementMode, ) + } - if (networkSubgraphDeploymentId !== undefined) { - // Make sure the network subgraph is being indexed - await indexer.ensure( - `indexer-agent/${networkSubgraphDeploymentId.ipfsHash.slice(-10)}`, - networkSubgraphDeploymentId, - ) - - // Validate if the Network Subgraph belongs to the current provider's network. - // This check must be performed after we ensure the Network Subgraph is being indexed. - try { - await validateNetworkId( - networkMeta, - argv.networkSubgraphDeployment, - indexingStatusResolver, - logger, - ) - } catch (e) { - logger.critical('Failed to validate Network Subgraph. Exiting.', e) - process.exit(1) - } - } + if (rebateClaimThreshold === 0) { + logger.warn( + `Minimum query fee rebate value is 0 GRT, which may lead to claiming unprofitable rebates`, + ) + } - // Monitor ETH balance of the operator and write the latest value to a metric - await monitorEthBalance(logger, wallet, metrics) + if (rebateClaimMaxBatchSize > advisedRebateClaimMaxBatchSize) { + logger.warn( + `Setting the max batch size for rebate claims to more than ${advisedRebateClaimMaxBatchSize}` + + 'may result in batches that are too large to fit into a block', + { rebateClaimMaxBatchSize: rebateClaimMaxBatchSize }, + ) + } - logger.info(`Launch syncing server`) - await createSyncingServer({ - logger, - networkSubgraph, - port: argv.syncingPort, - }) - logger.info(`Successfully launched syncing server`) - - startCostModelAutomation({ - logger, - ethereum: networkProvider, - contracts: network.contracts, - indexerManagement: indexerManagementClient, - injectDai: argv.injectDai, - daiContractAddress: toAddress(argv.daiContract), - metrics, - }) + if (voucherRedemptionThreshold == 0) { + logger.warn( + `Minimum voucher redemption value is 0 GRT, which may lead to redeeming unprofitable vouchers`, + ) + } - await startAgent({ - logger, - metrics, - indexer, - network, - networkMonitor, - networkSubgraph, - allocateOnNetworkSubgraph: argv.allocateOnNetworkSubgraph, - registerIndexer: argv.register, - offchainSubgraphs: argv.offchainSubgraphs.map( - (s: string) => new SubgraphDeploymentID(s), - ), - receiptCollector, - }) - }, + if (voucherRedemptionMaxBatchSize > advisedVoucherRedemptionMaxBatchSize) { + logger.warn( + `Setting the max batch size for voucher redemptions to more than ${advisedVoucherRedemptionMaxBatchSize} ` + + 'may result in batches that are too large to fit into a block', + { voucherRedemptionMaxBatchSize: voucherRedemptionMaxBatchSize }, + ) + } } -// Compares the CAIP-2 chain ID between the Ethereum provider and the Network Subgraph and requires -// they are equal. -async function validateNetworkId( - providerNetwork: NetworkMetadata, - networkSubgraphDeploymentIpfsHash: string, - indexingStatusResolver: IndexingStatusResolver, - logger: Logger, -) { - const subgraphNetworkId = new SubgraphDeploymentID( - networkSubgraphDeploymentIpfsHash, - ) - const { network: subgraphNetworkChainName } = - await indexingStatusResolver.subgraphFeatures(subgraphNetworkId) - - if (!subgraphNetworkChainName) { - // This is unlikely to happen because we expect that the Network Subgraph manifest is valid. - const errorMsg = 'Failed to fetch the networkId for the Network Subgraph' - logger.error(errorMsg, { networkSubgraphDeploymentIpfsHash }) - throw new Error(errorMsg) +// Retrieves the network identifier in contexts where we haven't yet instantiated the JSON +// RPC Provider, which has additional and more complex dependencies. +async function fetchChainId(url: string): Promise { + const payload = { + jsonrpc: '2.0', + id: 0, + method: 'eth_chainId', } - - const providerChainId = resolveChainId(providerNetwork.chainId) - const networkSubgraphChainId = resolveChainId(subgraphNetworkChainName) - if (providerChainId !== networkSubgraphChainId) { - const errorMsg = - 'The configured provider and the Network Subgraph have different CAIP-2 chain IDs. ' + - 'Please ensure that both Network Subgraph and the Ethereum provider are correctly configured.' - logger.error(errorMsg, { - networkSubgraphDeploymentIpfsHash, - networkSubgraphChainId, - providerChainId, - }) - throw new Error(errorMsg) + try { + const response = await axios.post(url, payload) + if (response.status !== 200) { + throw `HTTP ${response.status}` + } + if (!response.data || !response.data.result) { + throw `Received invalid response body from provider: ${response.data}` + } + return parseInt(response.data.result, 16) + } catch (error) { + throw new Error(`Failed to fetch chainID from provider: ${error}`) } } From edae8fcd77eea813a68dbbf109a80743b638a4c6 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:24:00 -0300 Subject: [PATCH 10/16] agent: update modules for the new protocolNetwork field --- packages/indexer-agent/src/agent.ts | 1088 ++++++++++++----- packages/indexer-agent/src/cost.ts | 39 +- packages/indexer-agent/src/index.ts | 87 +- packages/indexer-agent/src/indexer.ts | 888 -------------- packages/indexer-agent/src/syncing-server.ts | 71 +- packages/indexer-agent/src/types.ts | 4 +- .../src/utils/balance-monitor.ts | 39 - packages/indexer-agent/src/utils/index.ts | 1 - 8 files changed, 913 insertions(+), 1304 deletions(-) delete mode 100644 packages/indexer-agent/src/indexer.ts delete mode 100644 packages/indexer-agent/src/utils/balance-monitor.ts delete mode 100644 packages/indexer-agent/src/utils/index.ts diff --git a/packages/indexer-agent/src/agent.ts b/packages/indexer-agent/src/agent.ts index 0b7cf0cb6..67865c3e3 100644 --- a/packages/indexer-agent/src/agent.ts +++ b/packages/indexer-agent/src/agent.ts @@ -1,13 +1,14 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import { + Eventual, join, Logger, Metrics, SubgraphDeploymentID, timer, - toAddress, } from '@graphprotocol/common-ts' import { + ActivationCriteria, ActionStatus, Allocation, AllocationManagementMode, @@ -16,23 +17,42 @@ import { indexerError, IndexerErrorCode, IndexingDecisionBasis, + IndexerManagementClient, IndexingRuleAttributes, Network, - NetworkMonitor, - NetworkSubgraph, POIDisputeAttributes, - ReceiptCollector, RewardsPool, Subgraph, + SubgraphDeployment, SubgraphIdentifierType, evaluateDeployments, AllocationDecision, + GraphNode, + Operator, + validateProviderNetworkIdentifier, + MultiNetworks, + NetworkMapped, + TransferredSubgraphDeployment, + networkIsL2, + networkIsL1, } from '@graphprotocol/indexer-common' -import { Indexer } from './indexer' -import { AgentConfig } from './types' + import PQueue from 'p-queue' import pMap from 'p-map' import pFilter from 'p-filter' +import isEqual from 'lodash.isequal' +import mapValues from 'lodash.mapvalues' +import zip from 'lodash.zip' + +// Temporary marker used to signal that the following contract calls have been deprecated in the +// network it appears: +// - channelDisputeEpochs +// - claimRebateRewards +// Once Exponential Rebates have been deployed to all networks, these calls should be removed from +// the code. +const EXPONENTIAL_REBATES_MARKER = -1 + +type ActionReconciliationContext = [AllocationDecision[], number, number] const deploymentInList = ( list: SubgraphDeploymentID[], @@ -116,152 +136,411 @@ export const convertSubgraphBasedRulesToDeploymentBased = ( const deploymentIDSet = (deployments: SubgraphDeploymentID[]): Set => new Set(deployments.map(id => id.bytes32)) -class Agent { +// Represents a pair of Network and Operator instances belonging to the same protocol +// network. Used when mapping over multiple protocol networks. +type NetworkAndOperator = { + network: Network + operator: Operator +} + +// Extracts the network identifier from a pair of matching Network and Operator objects. +function networkAndOperatorIdentity({ + network, + operator, +}: NetworkAndOperator): string { + const networkId = network.specification.networkIdentifier + const operatorId = operator.specification.networkIdentifier + if (networkId !== operatorId) { + throw new Error( + `Network and Operator pairs have different network identifiers: ${networkId} != ${operatorId}`, + ) + } + return networkId +} + +// Helper function to produce a `MultiNetworks` while validating its +// inputs. +function createMultiNetworks( + networks: Network[], + operators: Operator[], +): MultiNetworks { + // Validate that Networks and Operator arrays have even lengths and + // contain unique, matching network identifiers. + const visited = new Set() + const validInputs = + networks.length === operators.length && + networks.every((network, index) => { + const sameIdentifier = + network.specification.networkIdentifier === + operators[index].specification.networkIdentifier + if (!sameIdentifier) { + return false + } + if (visited.has(network.specification.networkIdentifier)) { + return false + } + visited.add(network.specification.networkIdentifier) + return true + }) + + if (!validInputs) { + throw new Error( + 'Invalid Networks and Operator pairs used in Agent initialization', + ) + } + // Note on undefineds: `lodash.zip` can return `undefined` if array lengths are + // uneven, but we have just checked that. + const networksAndOperators = zip(networks, operators).map(pair => { + const [network, operator] = pair + return { network: network!, operator: operator! } + }) + return new MultiNetworks(networksAndOperators, networkAndOperatorIdentity) +} + +export class Agent { logger: Logger metrics: Metrics - indexer: Indexer - network: Network - networkMonitor: NetworkMonitor - networkSubgraph: NetworkSubgraph - allocateOnNetworkSubgraph: boolean - registerIndexer: boolean + graphNode: GraphNode + multiNetworks: MultiNetworks + indexerManagement: IndexerManagementClient offchainSubgraphs: SubgraphDeploymentID[] - receiptCollector: ReceiptCollector + autoMigrationSupport: boolean constructor( logger: Logger, metrics: Metrics, - indexer: Indexer, - network: Network, - networkMonitor: NetworkMonitor, - networkSubgraph: NetworkSubgraph, - allocateOnNetworkSubgraph: boolean, - registerIndexer: boolean, + graphNode: GraphNode, + operators: Operator[], + indexerManagement: IndexerManagementClient, + networks: Network[], offchainSubgraphs: SubgraphDeploymentID[], - receiptCollector: ReceiptCollector, + autoMigrationSupport: boolean, ) { this.logger = logger.child({ component: 'Agent' }) this.metrics = metrics - this.indexer = indexer - this.network = network - this.networkMonitor = networkMonitor - this.networkSubgraph = networkSubgraph - this.allocateOnNetworkSubgraph = allocateOnNetworkSubgraph - this.registerIndexer = registerIndexer + this.graphNode = graphNode + this.indexerManagement = indexerManagement + this.multiNetworks = createMultiNetworks(networks, operators) this.offchainSubgraphs = offchainSubgraphs - this.receiptCollector = receiptCollector + this.autoMigrationSupport = !!autoMigrationSupport } async start(): Promise { + // -------------------------------------------------------------------------------- + // * Connect to Graph Node + // -------------------------------------------------------------------------------- this.logger.info(`Connect to Graph node(s)`) - await this.indexer.connect() + await this.graphNode.connect() this.logger.info(`Connected to Graph node(s)`) - // Ensure there is a 'global' indexing rule - await this.indexer.ensureGlobalIndexingRule() + // -------------------------------------------------------------------------------- + // * Ensure there is a 'global' indexing rule + // * Ensure NetworkSubgraph is indexing + // * Register the Indexer in the Network + // -------------------------------------------------------------------------------- + await this.multiNetworks.map( + async ({ network, operator }: NetworkAndOperator) => { + operator.ensureGlobalIndexingRule() + this.ensureNetworkSubgraphIsIndexing(network) + network.register() + }, + ) - if (this.registerIndexer) { - await this.network.register() - } + this.buildEventualTree() + return this + } - const currentEpochNumber = timer(600_000).tryMap( - async () => this.networkMonitor.currentEpochNumber(), + buildEventualTree() { + const logger = this.logger.child({ context: 'reconciliation-loop' }) + const currentEpochNumber: Eventual> = timer( + 600_000, + ).tryMap( + async () => + await this.multiNetworks.map(({ network }) => { + logger.trace('Fetching current epoch number', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.networkMonitor.currentEpochNumber() + }), { onError: error => - this.logger.warn(`Failed to fetch current epoch`, { error }), + logger.warn(`Failed to fetch current epoch`, { error }), }, ) - const channelDisputeEpochs = timer(600_000).tryMap( - () => this.network.contracts.staking.channelDisputeEpochs(), + const channelDisputeEpochs: Eventual> = timer( + 600_000, + ).map(() => + this.multiNetworks.map(async ({ network }) => { + logger.trace('Fetching channel dispute epochs', { + protocolNetwork: network.specification.networkIdentifier, + }) + try { + return network.contracts.staking.channelDisputeEpochs() + } catch (error) { + // Disregards `channelDisputeEpochs` value from this point forward. + // TODO: Investigate error to confirm it comes from a reverted call. + logger.warn( + 'Failed to fetch channel dispute epochs. ' + + 'Ignoring claimable allocations for this reconciliation cycle.', + { + error, + protocolNetwork: network.specification.networkIdentifier, + }, + ) + return EXPONENTIAL_REBATES_MARKER + } + }), + ) + + const maxAllocationEpochs: Eventual> = timer( + 600_000, + ).tryMap( + () => + this.multiNetworks.map(({ network }) => { + logger.trace('Fetching max allocation epochs', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.contracts.staking.maxAllocationEpochs() + }), { onError: error => - this.logger.warn(`Failed to fetch channel dispute epochs`, { error }), + logger.warn(`Failed to fetch max allocation epochs`, { error }), }, ) - const maxAllocationEpochs = timer(600_000).tryMap( - () => this.network.contracts.staking.maxAllocationEpochs(), + const indexingRules: Eventual> = + timer(20_000).tryMap( + async () => { + return this.multiNetworks.map(async ({ network, operator }) => { + logger.trace('Fetching indexing rules', { + protocolNetwork: network.specification.networkIdentifier, + context: 'reconciliation-loop', + }) + let rules = await operator.indexingRules(true) + const subgraphRuleIds = rules + .filter( + rule => rule.identifierType == SubgraphIdentifierType.SUBGRAPH, + ) + .map(rule => rule.identifier!) + const subgraphsMatchingRules = + await network.networkMonitor.subgraphs(subgraphRuleIds) + if (subgraphsMatchingRules.length >= 1) { + const epochLength = + await network.contracts.epochManager.epochLength() + const blockPeriod = 15 + const bufferPeriod = epochLength.toNumber() * blockPeriod * 100 // 100 epochs + rules = convertSubgraphBasedRulesToDeploymentBased( + rules, + subgraphsMatchingRules, + bufferPeriod, + ) + } + return rules + }) + }, + { + onError: error => + logger.warn(`Failed to obtain indexing rules, trying again later`, { + error, + }), + }, + ) + + const activeDeployments: Eventual = timer( + 60_000, + ).tryMap( + () => { + logger.trace('Fetching active deployments') + return this.graphNode.subgraphDeployments() + }, { onError: error => - this.logger.warn(`Failed to fetch max allocation epochs`, { error }), + logger.warn( + `Failed to obtain active deployments, trying again later`, + { error }, + ), }, ) - const indexingRules = timer(20_000).tryMap( + const networkDeployments: Eventual> = + timer(240_000).tryMap( + async () => + await this.multiNetworks.map(({ network }) => { + logger.trace('Fetching network deployments', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.networkMonitor.subgraphDeployments() + }), + { + onError: error => + logger.warn( + `Failed to obtain network deployments, trying again later`, + { error }, + ), + }, + ) + + const eligibleTransferDeployments: Eventual< + NetworkMapped + > = timer(300_000).tryMap( async () => { - let rules = await this.indexer.indexingRules(true) - const subgraphRuleIds = rules - .filter( - rule => rule.identifierType == SubgraphIdentifierType.SUBGRAPH, - ) - .map(rule => rule.identifier!) - const subgraphsMatchingRules = await this.networkMonitor.subgraphs( - subgraphRuleIds, - ) - if (subgraphsMatchingRules.length >= 1) { - const epochLength = - await this.network.contracts.epochManager.epochLength() - const blockPeriod = 15 - const bufferPeriod = epochLength.toNumber() * blockPeriod * 100 // 100 epochs - rules = convertSubgraphBasedRulesToDeploymentBased( - rules, - subgraphsMatchingRules, - bufferPeriod, + // Return early if the auto migration feature is disabled. + if (!this.autoMigrationSupport) { + logger.trace( + 'Auto Migration feature is disabled, skipping querying transferred subgraphs', ) + return this.multiNetworks.map(async () => []) } - return rules + + const statuses = await this.graphNode.indexingStatus([]) + return this.multiNetworks.map(async ({ network }) => { + const protocolNetwork = network.specification.networkIdentifier + logger.trace('Fetching deployments eligible for L2 transfer', { + protocolNetwork, + }) + const transfers = + await network.networkMonitor.transferredDeployments() + logger.trace( + `Found ${transfers.length} transferred subgraphs in the network`, + { protocolNetwork }, + ) + return transfers + .map(transfer => { + const status = statuses.find( + status => + status.subgraphDeployment.ipfsHash == transfer.ipfsHash, + ) + if (status) { + transfer.ready = status.synced && status.health == 'healthy' + } + return transfer + }) + .filter(transfer => transfer.ready == true) + }) }, { onError: error => - this.logger.warn( - `Failed to obtain indexing rules, trying again later`, + logger.warn( + `Failed to obtain transferred deployments, trying again later`, { error }, ), }, ) - const activeDeployments = timer(60_000).tryMap( - () => this.indexer.subgraphDeployments(), - { - onError: error => - this.logger.warn( - `Failed to obtain active deployments, trying again later`, - { - error, - }, - ), + // While in the L1 -> L2 transfer period this will be an intermediate value + // with the final value including transfer considerations + const intermediateNetworkDeploymentAllocationDecisions: Eventual< + NetworkMapped + > = join({ + networkDeployments, + indexingRules, + }).tryMap( + ({ indexingRules, networkDeployments }) => { + return mapValues( + this.multiNetworks.zip(indexingRules, networkDeployments), + ([indexingRules, networkDeployments]: [ + IndexingRuleAttributes[], + SubgraphDeployment[], + ]) => { + // Identify subgraph deployments on the network that are worth picking up; + // these may overlap with the ones we're already indexing + logger.trace('Evaluating which deployments are worth allocating to') + return indexingRules.length === 0 + ? [] + : evaluateDeployments(logger, networkDeployments, indexingRules) + }, + ) }, - ) - - const networkDeployments = timer(240_000).tryMap( - async () => await this.networkMonitor.subgraphDeployments(), { onError: error => - this.logger.warn( - `Failed to obtain network deployments, trying again later`, - { - error, - }, - ), + logger.warn(`Failed to evaluate deployments, trying again later`, { + error, + }), }, ) - const networkDeploymentAllocationDecisions = join({ - networkDeployments, - indexingRules, + // Update targetDeployments and networkDeplomentAllocationDecisions using transferredSubgraphDeployments data + // This will be somewhat custom and will likely be yanked out later after the transfer stage is complete + // Cases: + // - L1 subgraph that had the transfer started: keep synced and allocated to for at least one week + // post transfer. + // - L2 subgraph that has been transferred: + // - if already synced, allocate to it immediately using default allocation amount + // - if not synced, no changes + const networkDeploymentAllocationDecisions: Eventual< + NetworkMapped + > = join({ + intermediateNetworkDeploymentAllocationDecisions, + eligibleTransferDeployments, }).tryMap( - ({ indexingRules, networkDeployments }) => { - // Identify subgraph deployments on the network that are worth picking up; - // these may overlap with the ones we're already indexing - return indexingRules.length === 0 - ? [] - : evaluateDeployments(this.logger, networkDeployments, indexingRules) - }, + ({ + intermediateNetworkDeploymentAllocationDecisions, + eligibleTransferDeployments, + }) => + mapValues( + this.multiNetworks.zip( + intermediateNetworkDeploymentAllocationDecisions, + eligibleTransferDeployments, + ), + ([allocationDecisions, eligibleTransferDeployments]: [ + AllocationDecision[], + TransferredSubgraphDeployment[], + ]) => { + logger.debug( + `Found ${eligibleTransferDeployments.length} deployments eligible for transfer`, + { eligibleTransferDeployments }, + ) + const oneWeekAgo = Math.floor(Date.now() / 1_000) - 86_400 * 7 + return allocationDecisions.map(decision => { + const matchingTransfer = eligibleTransferDeployments.find( + deployment => + deployment.ipfsHash == decision.deployment.ipfsHash && + deployment.startedTransferToL2At.toNumber() > oneWeekAgo, + ) + if (matchingTransfer) { + logger.debug('Found a matching subgraph transfer', { + matchingTransfer, + }) + // L1 deployments being transferred need to be supported for one week post transfer + // to ensure continued support. + if (networkIsL1(matchingTransfer.protocolNetwork)) { + decision.toAllocate = true + decision.ruleMatch.activationCriteria = + ActivationCriteria.L2_TRANSFER_SUPPORT + logger.debug( + `Allocating towards L1 subgraph deployment to support its transfer`, + { + subgraphDeployment: matchingTransfer, + allocationDecision: decision, + }, + ) + } + // L2 Deployments + if ( + networkIsL2(matchingTransfer.protocolNetwork) && + !!matchingTransfer.transferredToL2 + ) { + decision.toAllocate = true + decision.ruleMatch.activationCriteria = + ActivationCriteria.L2_TRANSFER_SUPPORT + logger.debug( + `Allocating towards L2 subgraph deployment after transfer finished`, + { + subgraphDeployment: matchingTransfer, + allocationDecision: decision, + }, + ) + } + } + return decision + }) + }, + ), { onError: error => - this.logger.warn( - `Failed to obtain target allocations, trying again later`, + logger.warn( + `Failed to merge L2 transfer decisions, trying again later`, { error, }, @@ -271,103 +550,151 @@ class Agent { // let targetDeployments be an union of targetAllocations // and offchain subgraphs. - const targetDeployments = join({ + const targetDeployments: Eventual = join({ ticker: timer(120_000), indexingRules, networkDeploymentAllocationDecisions, }).tryMap( async ({ indexingRules, networkDeploymentAllocationDecisions }) => { - const rules = indexingRules - const targetDeploymentIDs = new Set( - networkDeploymentAllocationDecisions + logger.trace('Resolving target deployments') + const targetDeploymentIDs: Set = new Set( + // Concatenate all AllocationDecisions from all protocol networks + Object.values(networkDeploymentAllocationDecisions) + .flat() .filter(decision => decision.toAllocate === true) .map(decision => decision.deployment), ) - // add offchain subgraphs to the deployment list - // from rules - rules + // Add offchain subgraphs to the deployment list from rules + Object.values(indexingRules) + .flat() .filter( rule => rule?.decisionBasis === IndexingDecisionBasis.OFFCHAIN, ) - .map(rule => { + .forEach(rule => { targetDeploymentIDs.add(new SubgraphDeploymentID(rule.identifier)) }) - // from startup args - this.offchainSubgraphs.map(deployment => { + // From startup args + this.offchainSubgraphs.forEach(deployment => { targetDeploymentIDs.add(deployment) }) return [...targetDeploymentIDs] }, { onError: error => - this.logger.warn( + logger.warn( `Failed to obtain target deployments, trying again later`, - { - error, - }, + { error }, ), }, ) - const activeAllocations = timer(120_000).tryMap( - () => this.networkMonitor.allocations(AllocationStatus.ACTIVE), + const activeAllocations: Eventual> = timer( + 120_000, + ).tryMap( + () => + this.multiNetworks.map(({ network }) => { + logger.trace('Fetching active allocations', { + protocolNetwork: network.specification.networkIdentifier, + }) + return network.networkMonitor.allocations(AllocationStatus.ACTIVE) + }), { onError: () => - this.logger.warn( + logger.warn( `Failed to obtain active allocations, trying again later`, ), }, ) - const recentlyClosedAllocations = join({ + // `activeAllocations` is used to trigger this Eventual, but not really needed + // inside. + const recentlyClosedAllocations: Eventual = join({ activeAllocations, currentEpochNumber, }).tryMap( // eslint-disable-next-line @typescript-eslint/no-unused-vars - ({ activeAllocations, currentEpochNumber }) => - this.networkMonitor.recentlyClosedAllocations( + async ({ activeAllocations: _, currentEpochNumber }) => { + const allocationsByNetwork = await this.multiNetworks.mapNetworkMapped( currentEpochNumber, - 1, //TODO: Parameterize with a user provided value - ), + async ({ network }, epochNumber): Promise => { + logger.trace('Fetching recently closed allocations', { + protocolNetwork: network.specification.networkIdentifier, + currentEpochNumber, + }) + return network.networkMonitor.recentlyClosedAllocations( + epochNumber, + 1, + ) + }, + ) + return Object.values(allocationsByNetwork).flat() + }, { onError: () => - this.logger.warn( + logger.warn( `Failed to obtain active allocations, trying again later`, ), }, ) - const claimableAllocations = join({ + const claimableAllocations: Eventual> = join({ currentEpochNumber, channelDisputeEpochs, }).tryMap( - ({ currentEpochNumber, channelDisputeEpochs }) => - this.network.claimableAllocations( - currentEpochNumber - channelDisputeEpochs, + async ({ currentEpochNumber, channelDisputeEpochs }) => + this.multiNetworks.mapNetworkMapped( + this.multiNetworks.zip(currentEpochNumber, channelDisputeEpochs), + async ( + { network }: NetworkAndOperator, + [currentEpochNumber, channelDisputeEpochs]: [number, number], + ): Promise => { + logger.trace('Fetching claimable allocations', { + protocolNetwork: network.specification.networkIdentifier, + currentEpochNumber, + channelDisputeEpochs, + }) + if (channelDisputeEpochs === EXPONENTIAL_REBATES_MARKER) { + return [] // Ignore claimable allocations in Exponential Rebates context + } else { + return network.networkMonitor.claimableAllocations( + currentEpochNumber - channelDisputeEpochs, + ) + } + }, ), + { onError: () => - this.logger.warn( + logger.warn( `Failed to obtain claimable allocations, trying again later`, ), }, ) - this.logger.info(`Waiting for network data before reconciling every 120s`) - const disputableAllocations = join({ + const disputableAllocations: Eventual> = join({ currentEpochNumber, activeDeployments, }).tryMap( - ({ currentEpochNumber, activeDeployments }) => - this.network.disputableAllocations( + async ({ currentEpochNumber, activeDeployments }) => + this.multiNetworks.mapNetworkMapped( currentEpochNumber, - activeDeployments, - 0, + ({ network }: NetworkAndOperator, currentEpochNumber: number) => { + logger.trace('Fetching disputable allocations', { + protocolNetwork: network.specification.networkIdentifier, + currentEpochNumber, + }) + return network.networkMonitor.disputableAllocations( + currentEpochNumber, + activeDeployments, + 0, + ) + }, ), + { onError: () => - this.logger.warn( + logger.warn( `Failed to fetch disputable allocations, trying again later`, ), }, @@ -375,8 +702,6 @@ class Agent { join({ ticker: timer(240_000), - paused: this.network.transactionManager.paused, - isOperator: this.network.transactionManager.isOperator, currentEpochNumber, maxAllocationEpochs, activeDeployments, @@ -388,8 +713,6 @@ class Agent { disputableAllocations, }).pipe( async ({ - paused, - isOperator, currentEpochNumber, maxAllocationEpochs, activeDeployments, @@ -400,110 +723,125 @@ class Agent { claimableAllocations, disputableAllocations, }) => { - this.logger.info(`Reconcile with the network`, { + logger.info(`Reconcile with the network`, { currentEpochNumber, }) - // Do nothing else if the network is paused - if (paused) { - return this.logger.info( - `The network is currently paused, not doing anything until it resumes`, - ) - } - - // Do nothing if we're not authorized as an operator for the indexer - if (!isOperator) { - return this.logger.error( - `Not authorized as an operator for the indexer`, - { - err: indexerError(IndexerErrorCode.IE034), - indexer: toAddress(this.network.indexerAddress), - operator: toAddress( - this.network.transactionManager.wallet.address, - ), - }, - ) - } - - // Do nothing if there are already approved actions in the queue awaiting execution - const approvedActions = await this.indexer.fetchActions({ - status: ActionStatus.APPROVED, - }) - if (approvedActions.length > 0) { - return this.logger.info( - `There are ${approvedActions.length} approved actions awaiting execution, will reconcile with the network once they are executed`, - ) - } - // Claim rebate pool rewards from finalized allocations - try { - await this.claimRebateRewards(claimableAllocations) - } catch (err) { - this.logger.warn(`Failed to claim rebate rewards`, { err }) - } + await this.multiNetworks.mapNetworkMapped( + claimableAllocations, + async ( + { network }: NetworkAndOperator, + allocations: Allocation[], + ) => { + const protocolNetwork = network.specification.networkIdentifier + if (allocations.length) { + logger.debug( + `Claiming rebate rewards for ${allocations.length} allocations`, + { allocations, protocolNetwork }, + ) + return network.claimRebateRewards(allocations) + } else { + logger.debug('Found no allocations to claim rebate rewards for', { + protocolNetwork, + }) + } + }, + ) try { - const disputableEpoch = - currentEpochNumber - this.network.indexerConfigs.poiDisputableEpochs + const disputableEpochs = await this.multiNetworks.mapNetworkMapped( + currentEpochNumber, + async ( + { network }: NetworkAndOperator, + currentEpochNumber: number, + ) => + currentEpochNumber - + network.specification.indexerOptions.poiDisputableEpochs, + ) + // Find disputable allocations - await this.identifyPotentialDisputes( - disputableAllocations, - disputableEpoch, + await this.multiNetworks.mapNetworkMapped( + this.multiNetworks.zip(disputableEpochs, disputableAllocations), + async ( + { network, operator }: NetworkAndOperator, + [disputableEpoch, disputableAllocations]: [number, Allocation[]], + ): Promise => { + await this.identifyPotentialDisputes( + disputableAllocations, + disputableEpoch, + operator, + network, + ) + }, ) } catch (err) { - this.logger.warn(`Failed POI dispute monitoring`, { err }) + logger.warn(`Failed POI dispute monitoring`, { err }) } + const eligibleAllocations: Allocation[] = [ + ...recentlyClosedAllocations, + ...Object.values(activeAllocations).flat(), + ] + try { + // Reconcile deployments await this.reconcileDeployments( activeDeployments, targetDeployments, - [...recentlyClosedAllocations, ...activeAllocations], + eligibleAllocations, ) - + } catch (err) { + logger.warn( + `Exited early while reconciling deployments. Skipped reconciling actions.`, + { + err: indexerError(IndexerErrorCode.IE005, err), + }, + ) + return + } + try { // Reconcile allocation actions await this.reconcileActions( networkDeploymentAllocationDecisions, - activeAllocations, currentEpochNumber, maxAllocationEpochs, ) } catch (err) { - this.logger.warn( - `Exited early while reconciling deployments/allocations`, - { - err: indexerError(IndexerErrorCode.IE005, err), - }, - ) + logger.warn(`Exited early while reconciling actions`, { + err: indexerError(IndexerErrorCode.IE005, err), + }) + return } }, ) - - return this - } - - async claimRebateRewards(allocations: Allocation[]): Promise { - if (allocations.length > 0) { - this.logger.info(`Claim rebate rewards`, { - claimable: allocations.map(allocation => ({ - id: allocation.id, - deployment: allocation.subgraphDeployment.id.display, - createdAtEpoch: allocation.createdAtEpoch, - amount: allocation.queryFeeRebates, - })), - }) - await this.network.claimMany(allocations) - } } async identifyPotentialDisputes( disputableAllocations: Allocation[], disputableEpoch: number, + operator: Operator, + network: Network, ): Promise { // TODO: Support supplying status = 'any' to fetchPOIDisputes() to fetch all previously processed allocations in a single query + + this.logger.trace(`Identifying potential disputes`, { + protocolNetwork: network.specification.networkIdentifier, + }) + const alreadyProcessed = ( - await this.indexer.fetchPOIDisputes('potential', disputableEpoch) - ).concat(await this.indexer.fetchPOIDisputes('valid', disputableEpoch)) + await operator.fetchPOIDisputes( + 'potential', + disputableEpoch, + operator.specification.networkIdentifier, + ) + ).concat( + await operator.fetchPOIDisputes( + 'valid', + disputableEpoch, + operator.specification.networkIdentifier, + ), + ) const newDisputableAllocations = disputableAllocations.filter( allocation => @@ -511,15 +849,17 @@ class Agent { dispute => dispute.allocationID == allocation.id, ), ) - if (newDisputableAllocations.length == 0) { + if (newDisputableAllocations.length === 0) { this.logger.trace( 'No new disputable allocations to process for potential disputes', + { protocolNetwork: network.specification.networkIdentifier }, ) return } this.logger.debug( `Found new allocations onchain for subgraphs we have indexed. Let's compare POIs to identify any potential indexing disputes`, + { protocolNetwork: network.specification.networkIdentifier }, ) const uniqueRewardsPools: RewardsPool[] = await Promise.all( @@ -532,16 +872,18 @@ class Agent { ] .filter(pool => pool.closedAtEpochStartBlockHash) .map(async pool => { - const closedAtEpochStartBlock = await this.network.ethereum.getBlock( - pool.closedAtEpochStartBlockHash!, - ) + const closedAtEpochStartBlock = + await network.networkProvider.getBlock( + pool.closedAtEpochStartBlockHash!, + ) // Todo: Lazily fetch this, only if the first reference POI doesn't match - const previousEpochStartBlock = await this.network.ethereum.getBlock( - pool.previousEpochStartBlockHash!, - ) + const previousEpochStartBlock = + await network.networkProvider.getBlock( + pool.previousEpochStartBlockHash!, + ) pool.closedAtEpochStartBlockNumber = closedAtEpochStartBlock.number - pool.referencePOI = await this.indexer.statusResolver.proofOfIndexing( + pool.referencePOI = await this.graphNode.proofOfIndexing( pool.subgraphDeployment, { number: closedAtEpochStartBlock.number, @@ -551,15 +893,14 @@ class Agent { ) pool.previousEpochStartBlockHash = previousEpochStartBlock.hash pool.previousEpochStartBlockNumber = previousEpochStartBlock.number - pool.referencePreviousPOI = - await this.indexer.statusResolver.proofOfIndexing( - pool.subgraphDeployment, - { - number: previousEpochStartBlock.number, - hash: previousEpochStartBlock.hash, - }, - pool.allocationIndexer, - ) + pool.referencePreviousPOI = await this.graphNode.proofOfIndexing( + pool.subgraphDeployment, + { + number: previousEpochStartBlock.number, + hash: previousEpochStartBlock.hash, + }, + pool.allocationIndexer, + ) return pool }), ) @@ -607,6 +948,7 @@ class Agent { previousEpochStartBlockNumber: rewardsPool!.previousEpochStartBlockNumber!, status, + protocolNetwork: network.specification.networkIdentifier, } as POIDisputeAttributes }, ) @@ -614,7 +956,7 @@ class Agent { const potentialDisputes = disputes.filter( dispute => dispute.status == 'potential', ).length - const stored = await this.indexer.storePoiDisputes(disputes) + const stored = await operator.storePoiDisputes(disputes) this.logger.info(`Disputable allocations' POIs validated`, { potentialDisputes: potentialDisputes, @@ -622,40 +964,50 @@ class Agent { }) } + // This function assumes that allocations and deployments passed to it have already + // been retrieved from multiple networks. async reconcileDeployments( activeDeployments: SubgraphDeploymentID[], targetDeployments: SubgraphDeploymentID[], eligibleAllocations: Allocation[], ): Promise { - activeDeployments = uniqueDeployments(activeDeployments) - targetDeployments = uniqueDeployments(targetDeployments) - // Note eligibleAllocations are active or recently closed allocations still eligible for queries from the gateway - const eligibleAllocationDeployments = uniqueDeployments( - eligibleAllocations.map(allocation => allocation.subgraphDeployment.id), - ) - + // ---------------------------------------------------------------------------------------- // Ensure the network subgraph deployment is _always_ indexed - if (this.networkSubgraph.deployment) { - if ( - !deploymentInList(targetDeployments, this.networkSubgraph.deployment.id) - ) { - targetDeployments.push(this.networkSubgraph.deployment.id) + // ---------------------------------------------------------------------------------------- + this.multiNetworks.map(async ({ network }) => { + if (network.networkSubgraph.deployment) { + const networkDeploymentID = network.networkSubgraph.deployment.id + if (!deploymentInList(targetDeployments, networkDeploymentID)) { + targetDeployments.push(networkDeploymentID) + } } - } + }) + // ---------------------------------------------------------------------------------------- + // Inspect Deployments and Networks + // ---------------------------------------------------------------------------------------- // Ensure all subgraphs in offchain subgraphs list are _always_ indexed for (const offchainSubgraph of this.offchainSubgraphs) { if (!deploymentInList(targetDeployments, offchainSubgraph)) { targetDeployments.push(offchainSubgraph) } } + activeDeployments = uniqueDeployments(activeDeployments) + targetDeployments = uniqueDeployments(targetDeployments) - // only show Reconcile when active ids != target ids - // TODO: Fix this check, always returning true - if ( - deploymentIDSet(activeDeployments) != deploymentIDSet(targetDeployments) - ) { - // Turning to trace until the above conditional is fixed + // Note eligibleAllocations are active or recently closed allocations still eligible + // for queries from the gateway + const eligibleAllocationDeployments = uniqueDeployments( + eligibleAllocations.map(allocation => allocation.subgraphDeployment.id), + ) + + // Log details if active deployments are different from target deployments + const isReconciliationNeeded = !isEqual( + deploymentIDSet(activeDeployments), + deploymentIDSet(targetDeployments), + ) + if (isReconciliationNeeded) { + // QUESTION: should we return early in here case reconciliation is not needed? this.logger.debug('Reconcile deployments', { syncing: activeDeployments.map(id => id.display), target: targetDeployments.map(id => id.display), @@ -675,13 +1027,21 @@ class Agent { !deploymentInList(eligibleAllocationDeployments, deployment), ) + // QUESTION: Same as before: should we return early in here case reconciliation is + // not needed? if (deploy.length + remove.length !== 0) { this.logger.info('Deployment changes', { deploy: deploy.map(id => id.display), remove: remove.map(id => id.display), }) + } else { + this.logger.debug('No deployment changes are necessary') } + // ---------------------------------------------------------------------------------------- + // Execute Deployments (Add, Remove) + // ---------------------------------------------------------------------------------------- + // Deploy/remove up to 10 subgraphs in parallel const queue = new PQueue({ concurrency: 10 }) @@ -698,24 +1058,26 @@ class Agent { // Ensure the deployment is deployed to the indexer // Note: we're not waiting here, as sometimes indexing a subgraph // will block if the IPFS files cannot be retrieved - this.indexer.ensure(name, deployment) + this.graphNode.ensure(name, deployment) }), ) // Stop indexing deployments that are no longer worth indexing await queue.addAll( - remove.map(deployment => async () => this.indexer.remove(deployment)), + remove.map(deployment => async () => this.graphNode.remove(deployment)), ) await queue.onIdle() + this.logger.debug('Finished reconciling deployments') } async identifyExpiringAllocations( - logger: Logger, + _logger: Logger, activeAllocations: Allocation[], deploymentAllocationDecision: AllocationDecision, epoch: number, maxAllocationEpochs: number, + network: Network, ): Promise { const desiredAllocationLifetime = deploymentAllocationDecision.ruleMatch .rule?.allocationLifetime @@ -736,7 +1098,7 @@ class Agent { async (allocation: Allocation) => { try { const onChainAllocation = - await this.network.contracts.staking.getAllocation(allocation.id) + await network.contracts.staking.getAllocation(allocation.id) return onChainAllocation.closedAtEpoch.eq('0') } catch (err) { this.logger.warn( @@ -753,21 +1115,23 @@ class Agent { ) return expiredAllocations } + async reconcileDeploymentAllocationAction( deploymentAllocationDecision: AllocationDecision, + activeAllocations: Allocation[], epoch: number, maxAllocationEpochs: number, + network: Network, + operator: Operator, ): Promise { const logger = this.logger.child({ deployment: deploymentAllocationDecision.deployment.ipfsHash, + protocolNetwork: network.specification.networkIdentifier, epoch, }) - // Acuracy check: re-fetch allocations to ensure that we have a fresh state since the start of the reconciliation loop - const activeAllocations = await this.networkMonitor.allocations( - AllocationStatus.ACTIVE, - ) - + // QUESTION: Can we replace `filter` for `find` here? Is there such a case when we + // would have multiple allocations for the same subgraph? const activeDeploymentAllocations = activeAllocations.filter( allocation => allocation.subgraphDeployment.id.bytes32 === @@ -776,7 +1140,7 @@ class Agent { switch (deploymentAllocationDecision.toAllocate) { case false: - return await this.indexer.closeEligibleAllocations( + return await operator.closeEligibleAllocations( logger, deploymentAllocationDecision, activeDeploymentAllocations, @@ -785,14 +1149,16 @@ class Agent { case true: { // If no active allocations, create one if (activeDeploymentAllocations.length === 0) { - return await this.indexer.createAllocation( + // Fetch the latest closed allocation, if any + const mostRecentlyClosedAllocation = ( + await network.networkMonitor.closedAllocations( + deploymentAllocationDecision.deployment, + ) + )[0] + return await operator.createAllocation( logger, deploymentAllocationDecision, - ( - await this.networkMonitor.closedAllocations( - deploymentAllocationDecision.deployment, - ) - )[0], + mostRecentlyClosedAllocation, ) } @@ -803,9 +1169,10 @@ class Agent { deploymentAllocationDecision, epoch, maxAllocationEpochs, + network, ) if (expiringAllocations.length > 0) { - await this.indexer.refreshExpiredAllocations( + await operator.refreshExpiredAllocations( logger, deploymentAllocationDecision, expiringAllocations, @@ -815,80 +1182,151 @@ class Agent { } } + // QUESTION: the `activeAllocations` parameter is used only for logging. Should we + // remove it from this function? async reconcileActions( - networkDeploymentAllocationDecisions: AllocationDecision[], - activeAllocations: Allocation[], - epoch: number, - maxAllocationEpochs: number, + networkDeploymentAllocationDecisions: NetworkMapped, + epoch: NetworkMapped, + maxAllocationEpochs: NetworkMapped, ): Promise { - if ( - this.indexer.allocationManagementMode == AllocationManagementMode.MANUAL - ) { - this.logger.trace( - `Skipping allocation reconciliation since AllocationManagementMode = 'manual'`, - { - activeAllocations, - targetDeployments: networkDeploymentAllocationDecisions - .filter(decision => decision.toAllocate) - .map(decision => decision.deployment.ipfsHash), + // -------------------------------------------------------------------------------- + // Filter out networks set to `manual` allocation management mode, and ensure the + // Network Subgraph is NEVER allocated towards + // -------------------------------------------------------------------------------- + const validatedAllocationDecisions = + await this.multiNetworks.mapNetworkMapped( + networkDeploymentAllocationDecisions, + async ( + { network }: NetworkAndOperator, + allocationDecisions: AllocationDecision[], + ) => { + if ( + network.specification.indexerOptions.allocationManagementMode === + AllocationManagementMode.MANUAL + ) { + this.logger.trace( + `Skipping allocation reconciliation since AllocationManagementMode = 'manual'`, + { + protocolNetwork: network.specification.networkIdentifier, + targetDeployments: allocationDecisions + .filter(decision => decision.toAllocate) + .map(decision => decision.deployment.ipfsHash), + }, + ) + allocationDecisions.forEach( + allocation => (allocation.toAllocate = false), + ) + return allocationDecisions + } + const networkSubgraphDeployment = network.networkSubgraph.deployment + if ( + networkSubgraphDeployment && + !network.specification.indexerOptions.allocateOnNetworkSubgraph + ) { + const networkSubgraphIndex = allocationDecisions.findIndex( + decision => + decision.deployment.bytes32 == + networkSubgraphDeployment.id.bytes32, + ) + if (networkSubgraphIndex >= 0) { + allocationDecisions[networkSubgraphIndex].toAllocate = false + } + } + return allocationDecisions }, ) - return - } - // Ensure the network subgraph is never allocated towards + //---------------------------------------------------------------------------------------- + // For every network, loop through all deployments and queue allocation actions if needed + //---------------------------------------------------------------------------------------- + await this.multiNetworks.mapNetworkMapped( + this.multiNetworks.zip3( + validatedAllocationDecisions, + epoch, + maxAllocationEpochs, + ), + async ( + { network, operator }: NetworkAndOperator, + [ + allocationDecisions, + epoch, + maxAllocationEpochs, + ]: ActionReconciliationContext, + ) => { + // Do nothing if there are already approved actions in the queue awaiting execution + const approvedActions = await operator.fetchActions({ + status: ActionStatus.APPROVED, + protocolNetwork: network.specification.networkIdentifier, + }) + if (approvedActions.length > 0) { + this.logger.info( + `There are ${approvedActions.length} approved actions awaiting execution, will reconcile with the network once they are executed`, + { protocolNetwork: network.specification.networkIdentifier }, + ) + return + } + + // Accuracy check: re-fetch allocations to ensure that we have a fresh state since the + // start of the reconciliation loop. This means we don't use the allocations coming from + // the Eventual input. + const activeAllocations: Allocation[] = + await network.networkMonitor.allocations(AllocationStatus.ACTIVE) + + this.logger.trace(`Reconcile allocation actions`, { + protocolNetwork: network.specification.networkIdentifier, + epoch, + maxAllocationEpochs, + targetDeployments: allocationDecisions + .filter(decision => decision.toAllocate) + .map(decision => decision.deployment.ipfsHash), + activeAllocations: activeAllocations.map(allocation => ({ + id: allocation.id, + deployment: allocation.subgraphDeployment.id.ipfsHash, + createdAtEpoch: allocation.createdAtEpoch, + })), + }) + + return pMap(allocationDecisions, async decision => + this.reconcileDeploymentAllocationAction( + decision, + activeAllocations, + epoch, + maxAllocationEpochs, + network, + operator, + ), + ) + }, + ) + } + + async ensureNetworkSubgraphIsIndexing(network: Network) { if ( - !this.allocateOnNetworkSubgraph && - this.networkSubgraph.deployment?.id.bytes32 + network.specification.subgraphs.networkSubgraph.deployment !== undefined ) { - const networkSubgraphDeploymentId = this.networkSubgraph.deployment.id - const networkSubgraphIndex = - networkDeploymentAllocationDecisions.findIndex( - decision => - decision.deployment.bytes32 == networkSubgraphDeploymentId.bytes32, + // Make sure the network subgraph is being indexed + await this.graphNode.ensure( + `indexer-agent/${network.specification.subgraphs.networkSubgraph.deployment.slice( + -10, + )}`, + new SubgraphDeploymentID( + network.specification.subgraphs.networkSubgraph.deployment, + ), + ) + + // Validate if the Network Subgraph belongs to the current provider's network. + // This check must be performed after we ensure the Network Subgraph is being indexed. + try { + await validateProviderNetworkIdentifier( + network.specification.networkIdentifier, + network.specification.subgraphs.networkSubgraph.deployment, + this.graphNode, + this.logger, ) - if (networkSubgraphIndex >= 0) { - networkDeploymentAllocationDecisions[networkSubgraphIndex].toAllocate = - false + } catch (e) { + this.logger.critical('Failed to validate Network Subgraph. Exiting.', e) + process.exit(1) } } - - this.logger.trace(`Reconcile allocation actions`, { - epoch, - maxAllocationEpochs, - targetDeployments: networkDeploymentAllocationDecisions - .filter(decision => decision.toAllocate) - .map(decision => decision.deployment.ipfsHash), - activeAllocations: activeAllocations.map(allocation => ({ - id: allocation.id, - deployment: allocation.subgraphDeployment.id.ipfsHash, - createdAtEpoch: allocation.createdAtEpoch, - })), - }) - - // Loop through all deployments on network and queue allocation actions if needed - await pMap(networkDeploymentAllocationDecisions, async decision => { - await this.reconcileDeploymentAllocationAction( - decision, - epoch, - maxAllocationEpochs, - ) - }) } } - -export const startAgent = async (config: AgentConfig): Promise => { - const agent = new Agent( - config.logger, - config.metrics, - config.indexer, - config.network, - config.networkMonitor, - config.networkSubgraph, - config.allocateOnNetworkSubgraph, - config.registerIndexer, - config.offchainSubgraphs, - config.receiptCollector, - ) - return await agent.start() -} diff --git a/packages/indexer-agent/src/cost.ts b/packages/indexer-agent/src/cost.ts index 6b36de232..188cd6f94 100644 --- a/packages/indexer-agent/src/cost.ts +++ b/packages/indexer-agent/src/cost.ts @@ -8,7 +8,7 @@ import { timer, Address, } from '@graphprotocol/common-ts' -import { IndexerManagementClient } from '@graphprotocol/indexer-common' +import { IndexerManagementClient, Network } from '@graphprotocol/indexer-common' import { Contract, providers } from 'ethers' interface CostModelAutomationMetrics { @@ -30,37 +30,46 @@ const registerMetrics = (metrics: Metrics): CostModelAutomationMetrics => ({ }), }) -export interface CostModelAutomationOptions { +// Public API +export interface StartCostModelAutomationOptions { + logger: Logger + networks: Network[] + indexerManagement: IndexerManagementClient + metrics: Metrics +} + +// Internal API +interface CostModelAutomationOptions { logger: Logger ethereum: providers.BaseProvider contracts: NetworkContracts indexerManagement: IndexerManagementClient - injectDai: boolean daiContractAddress: Address - metrics: Metrics + metrics: CostModelAutomationMetrics } export const startCostModelAutomation = ({ logger, - ethereum, - contracts, + networks, indexerManagement, - injectDai, - daiContractAddress, metrics, -}: CostModelAutomationOptions): void => { +}: StartCostModelAutomationOptions): void => { logger = logger.child({ component: 'CostModelAutomation' }) const automationMetrics = registerMetrics(metrics) - if (injectDai) { + // We could have this run per network but we probably only need to run it for Mainnet + const mainnet = networks.find( + n => n.specification.networkIdentifier === 'eip155:1', + ) + if (mainnet && mainnet.specification.dai.inject) { monitorAndInjectDai({ logger, - ethereum, - contracts, + ethereum: mainnet.networkProvider, + contracts: mainnet.contracts, indexerManagement, metrics: automationMetrics, - daiContractAddress, + daiContractAddress: mainnet.specification.dai.contractAddress, }) } } @@ -74,9 +83,7 @@ const monitorAndInjectDai = async ({ indexerManagement, metrics, daiContractAddress, -}: Omit & { - metrics: CostModelAutomationMetrics -}): Promise => { +}: CostModelAutomationOptions): Promise => { // Identify the decimals used by the DAI or USDC contract const chainId = ethereum.network.chainId const stableCoin = new Contract(daiContractAddress, ERC20_ABI, ethereum) diff --git a/packages/indexer-agent/src/index.ts b/packages/indexer-agent/src/index.ts index 0e1b07810..ad933c2dc 100644 --- a/packages/indexer-agent/src/index.ts +++ b/packages/indexer-agent/src/index.ts @@ -1,22 +1,73 @@ +import { createLogger } from '@graphprotocol/common-ts' import * as yargs from 'yargs' +import { + start, + createNetworkSpecification, + reviewArgumentsForWarnings, + AgentOptions, + run, +} from './commands/start' +import { + startMultiNetwork, + parseNetworkSpecifications, +} from './commands/start-multi-network' -import start from './commands/start' +const MULTINETWORK_MODE: boolean = + !!process.env.INDEXER_AGENT_MULTINETWORK_MODE && + process.env.INDEXER_AGENT_MULTINETWORK_MODE.toLowerCase() !== 'false' -yargs - .scriptName('indexer-agent') - .env('INDEXER_AGENT') - .command(start) - .fail(function (msg, err, yargs) { - if (err) { - console.error(err) - } else { - console.error(msg) - console.error(` -Usage help... -`) - console.error(yargs.help()) - } - process.exit(1) +function parseArguments(): AgentOptions { + let builder = yargs.scriptName('indexer-agent').env('INDEXER_AGENT') + + // Dynamic argument parser construction based on network mode + if (MULTINETWORK_MODE) { + console.log('Starting the Indexer Agent in multi-network mode') + builder = builder.command(startMultiNetwork) + } else { + console.log('Starting the Indexer Agent in single-network mode') + builder = builder.command(start) + } + + return ( + builder + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .fail(function (msg, err, _yargs) { + console.error('The Indexer Agent command has failed.') + if (err) { + console.error(err) + } else { + console.error(msg) + } + process.exit(1) + }) + .demandCommand( + 1, + 'You need at least one command before continuing.' + + " See 'indexer-agent --help' for usage instructions.", + ) + .help().argv + ) +} + +async function processArgumentsAndRun(args: AgentOptions): Promise { + const logger = createLogger({ + name: 'IndexerAgent', + async: false, + level: args.logLevel, }) - .demandCommand(1, 'Choose a command from the above list') - .help().argv + if (MULTINETWORK_MODE) { + const specifications = parseNetworkSpecifications(args, logger) + await run(args, specifications, logger) + } else { + reviewArgumentsForWarnings(args, logger) + const specification = await createNetworkSpecification(args) + await run(args, [specification], logger) + } +} + +async function main(): Promise { + const args = parseArguments() + await processArgumentsAndRun(args) +} + +main() diff --git a/packages/indexer-agent/src/indexer.ts b/packages/indexer-agent/src/indexer.ts deleted file mode 100644 index bfdd394ba..000000000 --- a/packages/indexer-agent/src/indexer.ts +++ /dev/null @@ -1,888 +0,0 @@ -import gql from 'graphql-tag' -import jayson, { Client as RpcClient } from 'jayson/promise' -import { BigNumber, utils } from 'ethers' -import { - formatGRT, - Logger, - SubgraphDeploymentID, -} from '@graphprotocol/common-ts' -import { - IndexingRuleAttributes, - IndexerManagementClient, - INDEXING_RULE_GLOBAL, - indexerError, - IndexerErrorCode, - POIDisputeAttributes, - IndexingStatusResolver, - IndexingStatus, - SubgraphIdentifierType, - parseGraphQLIndexingStatus, - CostModelAttributes, - ActionResult, - ActionItem, - Action, - ActionStatus, - AllocationManagementMode, - ActionInput, - ActionType, - Allocation, - AllocationDecision, - ActionFilter, -} from '@graphprotocol/indexer-common' -import { CombinedError } from '@urql/core' -import pMap from 'p-map' - -const POI_DISPUTES_CONVERTERS_FROM_GRAPHQL: Record< - keyof POIDisputeAttributes, - (x: never) => string | BigNumber | number | null -> = { - allocationID: x => x, - subgraphDeploymentID: x => x, - allocationIndexer: x => x, - allocationAmount: x => x, - allocationProof: x => x, - closedEpoch: x => +x, - closedEpochStartBlockHash: x => x, - closedEpochStartBlockNumber: x => +x, - closedEpochReferenceProof: x => x, - previousEpochStartBlockHash: x => x, - previousEpochStartBlockNumber: x => +x, - previousEpochReferenceProof: x => x, - status: x => x, -} - -/** - * Parses a POI dispute returned from the indexer management GraphQL - * API into normalized form. - */ -const disputeFromGraphQL = ( - dispute: Partial, -): POIDisputeAttributes => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const obj = {} as any - for (const [key, value] of Object.entries(dispute)) { - if (key === '__typename') { - continue - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - obj[key] = (POI_DISPUTES_CONVERTERS_FROM_GRAPHQL as any)[key](value) - } - return obj as POIDisputeAttributes -} - -interface indexNode { - id: string - deployments: string[] -} - -export class Indexer { - statusResolver: IndexingStatusResolver - rpc: RpcClient - indexerManagement: IndexerManagementClient - logger: Logger - indexNodeIDs: string[] - defaultAllocationAmount: BigNumber - indexerAddress: string - allocationManagementMode: AllocationManagementMode - - constructor( - logger: Logger, - adminEndpoint: string, - statusResolver: IndexingStatusResolver, - indexerManagement: IndexerManagementClient, - indexNodeIDs: string[], - defaultAllocationAmount: BigNumber, - indexerAddress: string, - allocationManagementMode: AllocationManagementMode, - ) { - this.indexerManagement = indexerManagement - this.statusResolver = statusResolver - this.logger = logger - this.indexerAddress = indexerAddress - this.allocationManagementMode = allocationManagementMode - - if (adminEndpoint.startsWith('https')) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.rpc = jayson.Client.https(adminEndpoint as any) - } else { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.rpc = jayson.Client.http(adminEndpoint as any) - } - this.indexNodeIDs = indexNodeIDs - this.defaultAllocationAmount = defaultAllocationAmount - } - - async connect(): Promise { - try { - this.logger.info(`Check if indexing status API is available`) - const currentDeployments = await this.subgraphDeployments() - this.logger.info(`Successfully connected to indexing status API`, { - currentDeployments: currentDeployments.map( - deployment => deployment.display, - ), - }) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE024, error) - this.logger.error(`Failed to connect to indexing status API`, { - err, - }) - throw err - } - } - - async subgraphDeployments(): Promise { - try { - const result = await this.statusResolver.statuses - .query( - gql` - { - indexingStatuses { - subgraphDeployment: subgraph - node - } - } - `, - ) - .toPromise() - - if (result.error) { - throw result.error - } - - return result.data.indexingStatuses - .filter((status: { subgraphDeployment: string; node: string }) => { - return status.node && status.node !== 'removed' - }) - .map((status: { subgraphDeployment: string; node: string }) => { - return new SubgraphDeploymentID(status.subgraphDeployment) - }) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE018, error) - this.logger.error(`Failed to query indexing status API`, { err }) - throw err - } - } - - async indexNodes(): Promise { - try { - const result = await this.statusResolver.statuses - .query( - gql` - { - indexingStatuses { - subgraphDeployment: subgraph - node - } - } - `, - ) - .toPromise() - - if (result.error) { - throw result.error - } - - const indexNodes: indexNode[] = [] - result.data.indexingStatuses.map( - (status: { subgraphDeployment: string; node: string }) => { - const node = indexNodes.find(node => node.id === status.node) - node - ? node.deployments.push(status.subgraphDeployment) - : indexNodes.push({ - id: status.node, - deployments: [status.subgraphDeployment], - }) - }, - ) - - this.logger.trace(`Queried index nodes`, { - indexNodes, - }) - return indexNodes - } catch (error) { - const err = indexerError(IndexerErrorCode.IE018, error) - this.logger.error( - `Failed to query index nodes API (Should get a different IE?)`, - { err }, - ) - throw err - } - } - - async indexingRules(merged: boolean): Promise { - try { - const result = await this.indexerManagement - .query( - gql` - query indexingRules($merged: Boolean!) { - indexingRules(merged: $merged) { - identifier - identifierType - allocationAmount - allocationLifetime - autoRenewal - parallelAllocations - maxAllocationPercentage - minSignal - maxSignal - minStake - minAverageQueryFees - custom - decisionBasis - requireSupported - } - } - `, - { merged }, - ) - .toPromise() - - if (result.error) { - throw result.error - } - this.logger.trace('Fetched indexing rules', { - count: result.data.indexingRules.length, - rules: result.data.indexingRules.map((rule: IndexingRuleAttributes) => { - return { - identifier: rule.identifier, - identifierType: rule.identifierType, - decisionBasis: rule.decisionBasis, - } - }), - }) - return result.data.indexingRules - } catch (error) { - const err = indexerError(IndexerErrorCode.IE025, error) - this.logger.error(`Failed to query indexer management API`, { err }) - throw err - } - } - - async ensureGlobalIndexingRule(): Promise { - try { - const globalRule = await this.indexerManagement - .query( - gql` - query indexingRule($identifier: String!) { - indexingRule(identifier: $identifier, merged: false) { - identifier - identifierType - allocationAmount - decisionBasis - requireSupported - } - } - `, - { identifier: INDEXING_RULE_GLOBAL }, - ) - .toPromise() - - if (!globalRule.data.indexingRule) { - this.logger.info(`Creating default "global" indexing rule`) - - const defaults = { - identifier: INDEXING_RULE_GLOBAL, - identifierType: SubgraphIdentifierType.GROUP, - allocationAmount: this.defaultAllocationAmount.toString(), - parallelAllocations: 1, - decisionBasis: 'rules', - requireSupported: true, - safety: true, - } - - const defaultGlobalRule = await this.indexerManagement - .mutation( - gql` - mutation setIndexingRule($rule: IndexingRuleInput!) { - setIndexingRule(rule: $rule) { - identifier - identifierType - allocationAmount - allocationLifetime - autoRenewal - parallelAllocations - maxAllocationPercentage - minSignal - maxSignal - minStake - minAverageQueryFees - custom - decisionBasis - requireSupported - safety - } - } - `, - { rule: defaults }, - ) - .toPromise() - - if (defaultGlobalRule.error) { - throw defaultGlobalRule.error - } - - this.logger.info(`Created default "global" indexing rule`, { - rule: defaultGlobalRule.data.setIndexingRule, - }) - } - } catch (error) { - const err = indexerError(IndexerErrorCode.IE017, error) - this.logger.warn('Failed to ensure default "global" indexing rule', { - err, - }) - throw err - } - } - - async costModels( - deployments: SubgraphDeploymentID[], - ): Promise { - try { - const result = await this.indexerManagement - .query( - gql` - query costModels($deployments: [String!]!) { - costModels(deployments: $deployments) { - deployment - model - variables - } - } - `, - { - deployments: deployments.map(deployment => deployment.bytes32), - }, - ) - .toPromise() - - if (result.error) { - throw result.error - } - return result.data.costModels - } catch (error) { - this.logger.warn(`Failed to query cost models`, { error }) - throw error - } - } - - async storePoiDisputes( - disputes: POIDisputeAttributes[], - ): Promise { - try { - const result = await this.indexerManagement - .mutation( - gql` - mutation storeDisputes($disputes: [POIDisputeInput!]!) { - storeDisputes(disputes: $disputes) { - allocationID - subgraphDeploymentID - allocationIndexer - allocationAmount - allocationProof - closedEpoch - closedEpochStartBlockHash - closedEpochStartBlockNumber - closedEpochReferenceProof - previousEpochStartBlockHash - previousEpochStartBlockNumber - previousEpochReferenceProof - status - } - } - `, - { disputes: disputes }, - ) - .toPromise() - - if (result.error) { - throw result.error - } - - return result.data.storeDisputes.map( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (dispute: Record) => { - return disputeFromGraphQL(dispute) - }, - ) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE039, error) - this.logger.error('Failed to store potential POI disputes', { - err, - }) - throw err - } - } - - async fetchPOIDisputes( - status: string, - minClosedEpoch: number, - ): Promise { - try { - const result = await this.indexerManagement - .query( - gql` - query disputes($status: String!, $minClosedEpoch: Int!) { - disputes(status: $status, minClosedEpoch: $minClosedEpoch) { - allocationID - subgraphDeploymentID - allocationIndexer - allocationAmount - allocationProof - closedEpoch - closedEpochStartBlockHash - closedEpochStartBlockNumber - closedEpochReferenceProof - previousEpochStartBlockHash - previousEpochStartBlockNumber - previousEpochReferenceProof - status - } - } - `, - { - status, - minClosedEpoch, - }, - ) - .toPromise() - - if (result.error) { - throw result.error - } - - return result.data.disputes.map( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (dispute: Record) => { - return disputeFromGraphQL(dispute) - }, - ) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE039, error) - this.logger.error('Failed to store potential POI disputes', { - err, - }) - throw err - } - } - - async indexingStatus( - deployment: SubgraphDeploymentID, - ): Promise { - try { - const result = await this.statusResolver.statuses - .query( - gql` - query indexingStatus($deployments: [String!]!) { - indexingStatuses(subgraphs: $deployments) { - subgraphDeployment: subgraph - synced - health - fatalError { - handler - message - } - chains { - network - ... on EthereumIndexingStatus { - latestBlock { - number - hash - } - chainHeadBlock { - number - hash - } - } - } - } - } - `, - { deployments: [deployment.ipfsHash] }, - ) - .toPromise() - this.logger.debug(`Query indexing status`, { - deployment, - statuses: result.data, - }) - return ( - result.data.indexingStatuses - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .map((status: any) => parseGraphQLIndexingStatus(status)) - .pop() - ) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE018, error) - this.logger.error(`Failed to query indexing status API`, { - err, - }) - throw err - } - } - - async fetchActions(actionFilter: ActionFilter): Promise { - const result = await this.indexerManagement - .query( - gql` - query actions($filter: ActionFilter!) { - actions(filter: $filter) { - id - type - allocationID - deploymentID - amount - poi - force - source - reason - priority - transaction - status - failureReason - } - } - `, - { filter: actionFilter }, - ) - .toPromise() - - if (result.error) { - throw result.error - } - - return result.data.actions - } - - async queueAction(action: ActionItem): Promise { - let status = ActionStatus.QUEUED - switch (this.allocationManagementMode) { - case AllocationManagementMode.MANUAL: - throw Error( - `Cannot queue actions when AllocationManagementMode = 'MANUAL'`, - ) - case AllocationManagementMode.AUTO: - status = ActionStatus.APPROVED - break - case AllocationManagementMode.OVERSIGHT: - status = ActionStatus.QUEUED - } - - const actionInput = { - ...action.params, - status, - type: action.type, - source: 'indexerAgent', - reason: action.reason, - priority: 0, - } as ActionInput - - const actionResult = await this.indexerManagement - .mutation( - gql` - mutation queueActions($actions: [ActionInput!]!) { - queueActions(actions: $actions) { - id - type - deploymentID - source - reason - priority - status - } - } - `, - { actions: [actionInput] }, - ) - .toPromise() - - if (actionResult.error) { - if ( - actionResult.error instanceof CombinedError && - actionResult.error.message.includes('Duplicate') - ) { - this.logger.warn( - `Action not queued: Already a queued action targeting ${actionInput.deploymentID} from another source`, - { action }, - ) - return [] - } - throw actionResult.error - } - - if (actionResult.data.queueActions.length > 0) { - this.logger.info(`Queued ${action.type} action for execution`, { - queuedAction: actionResult.data.queueActions, - }) - } - - return actionResult.data.queueActions - } - - async createAllocation( - logger: Logger, - deploymentAllocationDecision: AllocationDecision, - mostRecentlyClosedAllocation: Allocation, - ): Promise { - const desiredAllocationAmount = deploymentAllocationDecision.ruleMatch.rule - ?.allocationAmount - ? BigNumber.from( - deploymentAllocationDecision.ruleMatch.rule.allocationAmount, - ) - : this.defaultAllocationAmount - - logger.info(`No active allocation for deployment, creating one now`, { - allocationAmount: formatGRT(desiredAllocationAmount), - }) - - // Skip allocating if the previous allocation for this deployment was closed with 0x00 POI but rules set to un-safe - if ( - deploymentAllocationDecision.ruleMatch.rule?.safety && - mostRecentlyClosedAllocation && - mostRecentlyClosedAllocation.poi === utils.hexlify(Array(32).fill(0)) - ) { - logger.warn( - `Skipping allocation to this deployment as the last allocation to it was closed with a zero POI`, - { - notSafe: !deploymentAllocationDecision.ruleMatch.rule?.safety, - deployment: deploymentAllocationDecision.deployment, - closedAllocation: mostRecentlyClosedAllocation.id, - }, - ) - return - } - - // Send AllocateAction to the queue - await this.queueAction({ - params: { - deploymentID: deploymentAllocationDecision.deployment.ipfsHash, - amount: formatGRT(desiredAllocationAmount), - }, - type: ActionType.ALLOCATE, - reason: deploymentAllocationDecision.reasonString(), - }) - - return - } - - async closeEligibleAllocations( - logger: Logger, - deploymentAllocationDecision: AllocationDecision, - activeDeploymentAllocations: Allocation[], - epoch: number, - ): Promise { - const activeDeploymentAllocationsEligibleForClose = - activeDeploymentAllocations - .filter(allocation => allocation.createdAtEpoch < epoch) - .map(allocation => allocation.id) - // Make sure to close all active allocations on the way out - if (activeDeploymentAllocationsEligibleForClose.length > 0) { - logger.info( - `Deployment is not (or no longer) worth allocating towards, close allocation`, - { - eligibleForClose: activeDeploymentAllocationsEligibleForClose, - }, - ) - await pMap( - // We can only close allocations from a previous epoch; - // try the others again later - activeDeploymentAllocationsEligibleForClose, - async allocation => { - // Send unallocate action to the queue - await this.queueAction({ - params: { - allocationID: allocation, - deploymentID: deploymentAllocationDecision.deployment.ipfsHash, - poi: undefined, - force: false, - }, - type: ActionType.UNALLOCATE, - reason: deploymentAllocationDecision.reasonString(), - } as ActionItem) - }, - { concurrency: 1 }, - ) - } - } - - async refreshExpiredAllocations( - logger: Logger, - deploymentAllocationDecision: AllocationDecision, - expiredAllocations: Allocation[], - ): Promise { - if (deploymentAllocationDecision.ruleMatch.rule?.autoRenewal) { - logger.info(`Reallocating expired allocations`, { - number: expiredAllocations.length, - expiredAllocations: expiredAllocations.map(allocation => allocation.id), - }) - - const desiredAllocationAmount = deploymentAllocationDecision.ruleMatch - .rule?.allocationAmount - ? BigNumber.from( - deploymentAllocationDecision.ruleMatch.rule.allocationAmount, - ) - : this.defaultAllocationAmount - - // Queue reallocate actions to be picked up by the worker - await pMap(expiredAllocations, async allocation => { - await this.queueAction({ - params: { - allocationID: allocation.id, - deploymentID: deploymentAllocationDecision.deployment.ipfsHash, - amount: formatGRT(desiredAllocationAmount), - }, - type: ActionType.REALLOCATE, - reason: `${deploymentAllocationDecision.reasonString()}:allocationExpiring`, // Need to update to include 'ExpiringSoon' - }) - }) - } else { - logger.info( - `Skipping reallocating expired allocation since the corresponding rule has 'autoRenewal' = False`, - { - number: expiredAllocations.length, - expiredAllocations: expiredAllocations.map( - allocation => allocation.id, - ), - }, - ) - } - return - } - - async create(name: string): Promise { - try { - this.logger.info(`Create subgraph name`, { name }) - const response = await this.rpc.request('subgraph_create', { name }) - if (response.error) { - throw response.error - } - this.logger.info(`Successfully created subgraph name`, { name }) - } catch (error) { - if (error.message.includes('already exists')) { - this.logger.debug(`Subgraph name already exists`, { name }) - return - } - throw error - } - } - - async deploy( - name: string, - deployment: SubgraphDeploymentID, - node_id: string, - ): Promise { - try { - this.logger.info(`Deploy subgraph deployment`, { - name, - deployment: deployment.display, - }) - const response = await this.rpc.request('subgraph_deploy', { - name, - ipfs_hash: deployment.ipfsHash, - node_id: node_id, - }) - if (response.error) { - throw response.error - } - this.logger.info(`Successfully deployed subgraph deployment`, { - name, - deployment: deployment.display, - }) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE026, error) - this.logger.error(`Failed to deploy subgraph deployment`, { - name, - deployment: deployment.display, - err, - }) - throw err - } - } - - async remove(deployment: SubgraphDeploymentID): Promise { - try { - this.logger.info(`Remove subgraph deployment`, { - deployment: deployment.display, - }) - const response = await this.rpc.request('subgraph_reassign', { - node_id: 'removed', - ipfs_hash: deployment.ipfsHash, - }) - if (response.error) { - throw response.error - } - this.logger.info(`Successfully removed subgraph deployment`, { - deployment: deployment.display, - }) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE027, error) - this.logger.error(`Failed to remove subgraph deployment`, { - deployment: deployment.display, - err, - }) - } - } - - async reassign( - deployment: SubgraphDeploymentID, - node: string, - ): Promise { - try { - this.logger.info(`Reassign subgraph deployment`, { - deployment: deployment.display, - node, - }) - const response = await this.rpc.request('subgraph_reassign', { - node_id: node, - ipfs_hash: deployment.ipfsHash, - }) - if (response.error) { - throw response.error - } - } catch (error) { - if (error.message.includes('unchanged')) { - this.logger.debug(`Subgraph deployment assignment unchanged`, { - deployment: deployment.display, - node, - }) - return - } - const err = indexerError(IndexerErrorCode.IE028, error) - this.logger.error(`Failed to reassign subgraph deployment`, { - deployment: deployment.display, - err, - }) - throw err - } - } - - async ensure(name: string, deployment: SubgraphDeploymentID): Promise { - try { - // Randomly assign to unused nodes if they exist, - // otherwise use the node with lowest deployments assigned - const indexNodes = (await this.indexNodes()).filter( - (node: { id: string; deployments: Array }) => { - return node.id && node.id !== 'removed' - }, - ) - const usedIndexNodeIDs = indexNodes.map(node => node.id) - const unusedNodes = this.indexNodeIDs.filter( - nodeID => !(nodeID in usedIndexNodeIDs), - ) - - const targetNode = unusedNodes - ? unusedNodes[Math.floor(Math.random() * unusedNodes.length)] - : indexNodes.sort((nodeA, nodeB) => { - return nodeA.deployments.length - nodeB.deployments.length - })[0].id - await this.create(name) - await this.deploy(name, deployment, targetNode) - await this.reassign(deployment, targetNode) - } catch (error) { - const err = indexerError(IndexerErrorCode.IE020, error) - this.logger.error(`Failed to ensure subgraph deployment is indexing`, { - name, - deployment: deployment.display, - err, - }) - } - } -} diff --git a/packages/indexer-agent/src/syncing-server.ts b/packages/indexer-agent/src/syncing-server.ts index 94ed04b0e..af41a3fa6 100644 --- a/packages/indexer-agent/src/syncing-server.ts +++ b/packages/indexer-agent/src/syncing-server.ts @@ -4,17 +4,22 @@ import cors from 'cors' import bodyParser from 'body-parser' import morgan from 'morgan' import { Logger } from '@graphprotocol/common-ts' -import { NetworkSubgraph } from '@graphprotocol/indexer-common' +import { parse } from 'graphql' +import { + NetworkMapped, + NetworkSubgraph, + resolveChainId, +} from '@graphprotocol/indexer-common' export interface CreateSyncingServerOptions { logger: Logger - networkSubgraph: NetworkSubgraph + networkSubgraphs: NetworkMapped port: number } export const createSyncingServer = async ({ logger, - networkSubgraph, + networkSubgraphs, port, }: CreateSyncingServerOptions): Promise => { logger = logger.child({ component: 'SyncingServer' }) @@ -38,21 +43,57 @@ export const createSyncingServer = async ({ }) // Network subgraph endpoint - server.post('/network', bodyParser.json(), async (req, res) => { - const { query, variables } = req.body + server.post( + '/network/:networkIdentifier', + bodyParser.json(), + async (req, res) => { + const { query, variables } = req.body + const { networkIdentifier: unvalidatedNetworkIdentifier } = req.params - if (query.startsWith('mutation') || query.startsWith('subscription')) { - return res.status(405).send('Only queries are supported') - } + if (query.startsWith('mutation') || query.startsWith('subscription')) { + return res.status(405).send('Only queries are supported') + } - const result = await networkSubgraph.query(query, variables) + let networkIdentifier + try { + networkIdentifier = resolveChainId(unvalidatedNetworkIdentifier) + } catch (e) { + return res + .status(404) + .send(`Unknown network identifier: '${unvalidatedNetworkIdentifier}'`) + } - res.status(200).send({ - data: result.data, - errors: result.error ? result.error.graphQLErrors : null, - extensions: result.extensions, - }) - }) + const networkSubgraph = networkSubgraphs[networkIdentifier] + if (!networkSubgraph) { + return res + .status(404) + .send( + `Indexer Agent not configured for network '${networkIdentifier}'`, + ) + } + + let parsedQuery + try { + parsedQuery = parse(query) + } catch (e) { + return res.status(400).send('Malformed GraphQL query') + } + + let result + try { + result = await networkSubgraph.query(parsedQuery, variables) + } catch (err) { + logger.error(err) + return res.status(400).send({ error: err.message }) + } + + return res.status(200).send({ + data: result.data, + errors: result.error ? result.error.graphQLErrors : null, + extensions: result.extensions, + }) + }, + ) server.listen(port, () => { logger.debug(`Listening on port ${port}`) diff --git a/packages/indexer-agent/src/types.ts b/packages/indexer-agent/src/types.ts index 62d39d35a..be21b3327 100644 --- a/packages/indexer-agent/src/types.ts +++ b/packages/indexer-agent/src/types.ts @@ -3,14 +3,14 @@ import { Network, NetworkSubgraph, ReceiptCollector, + GraphNode, } from '@graphprotocol/indexer-common' -import { Indexer } from './indexer' import { NetworkMonitor } from '@graphprotocol/indexer-common' export interface AgentConfig { logger: Logger metrics: Metrics - indexer: Indexer + indexer: GraphNode network: Network networkMonitor: NetworkMonitor networkSubgraph: NetworkSubgraph diff --git a/packages/indexer-agent/src/utils/balance-monitor.ts b/packages/indexer-agent/src/utils/balance-monitor.ts deleted file mode 100644 index 6cef0f41a..000000000 --- a/packages/indexer-agent/src/utils/balance-monitor.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Wallet, utils } from 'ethers' - -import { Logger, Metrics, timer } from '@graphprotocol/common-ts' -import { indexerError, IndexerErrorCode } from '@graphprotocol/indexer-common' - -const registerMetrics = (metrics: Metrics) => ({ - operatorEthBalance: new metrics.client.Gauge({ - name: 'indexer_agent_operator_eth_balance', - help: 'Amount of ETH in the operator wallet; a low amount could cause transactions to fail', - registers: [metrics.registry], - }), -}) - -export async function monitorEthBalance( - logger: Logger, - wallet: Wallet, - metrics: Metrics, -): Promise { - logger = logger.child({ component: 'ETHBalanceMonitor' }) - - logger.info('Monitor operator ETH balance (refreshes every 120s)') - - const balanceMetrics = registerMetrics(metrics) - - timer(120_000).pipe(async () => { - try { - const balance = await wallet.getBalance() - const eth = parseFloat(utils.formatEther(balance)) - balanceMetrics.operatorEthBalance.set(eth) - logger.info('Current operator ETH balance', { - balance: eth, - }) - } catch (error) { - logger.warn(`Failed to check latest ETH balance`, { - err: indexerError(IndexerErrorCode.IE059), - }) - } - }) -} diff --git a/packages/indexer-agent/src/utils/index.ts b/packages/indexer-agent/src/utils/index.ts deleted file mode 100644 index ad983d80f..000000000 --- a/packages/indexer-agent/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './balance-monitor' From 7e046c5dfeb3a6b751023aa12a7057a3b57a98fb Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:24:20 -0300 Subject: [PATCH 11/16] agent: update tests --- .../indexer-agent/src/__tests__/indexer.ts | 152 +++++++++++------- 1 file changed, 98 insertions(+), 54 deletions(-) diff --git a/packages/indexer-agent/src/__tests__/indexer.ts b/packages/indexer-agent/src/__tests__/indexer.ts index bb5a56496..fcb2ad7fe 100644 --- a/packages/indexer-agent/src/__tests__/indexer.ts +++ b/packages/indexer-agent/src/__tests__/indexer.ts @@ -1,25 +1,27 @@ import { - connectContracts, connectDatabase, createLogger, + createMetrics, Logger, - NetworkContracts, + Metrics, parseGRT, - toAddress, } from '@graphprotocol/common-ts' import { - AllocationManagementMode, createIndexerManagementClient, defineIndexerManagementModels, IndexerManagementClient, IndexerManagementModels, - IndexingStatusResolver, - NetworkSubgraph, + GraphNode, + Operator, + Network, POIDisputeAttributes, + specification, + QueryFeeModels, + defineQueryFeeModels, + MultiNetworks, } from '@graphprotocol/indexer-common' -import { BigNumber, Wallet } from 'ethers' +import { BigNumber } from 'ethers' import { Sequelize } from 'sequelize' -import { Indexer } from '../indexer' const TEST_DISPUTE_1: POIDisputeAttributes = { allocationID: '0xbAd8935f75903A1eF5ea62199d98Fd7c3c1ab20C', @@ -40,6 +42,7 @@ const TEST_DISPUTE_1: POIDisputeAttributes = { previousEpochReferenceProof: '0xd04b5601739a1638719696d0735c92439267a89248c6fd21388d9600f5c942f6', status: 'potential', + protocolNetwork: 'eip155:5', } const TEST_DISPUTE_2: POIDisputeAttributes = { allocationID: '0x085fd2ADc1B96c26c266DecAb6A3098EA0eda619', @@ -60,6 +63,7 @@ const TEST_DISPUTE_2: POIDisputeAttributes = { previousEpochReferenceProof: '0xd04b5601739a1638719696d0735c92439267a89248c6fd21388d9600f5c942f6', status: 'potential', + protocolNetwork: 'eip155:5', } const POI_DISPUTES_CONVERTERS_FROM_GRAPHQL: Record< @@ -79,6 +83,7 @@ const POI_DISPUTES_CONVERTERS_FROM_GRAPHQL: Record< previousEpochStartBlockNumber: x => +x, previousEpochReferenceProof: x => x, status: x => x, + protocolNetwork: x => x, } /** @@ -104,49 +109,96 @@ declare const __DATABASE__: never let sequelize: Sequelize let models: IndexerManagementModels -let wallet: Wallet -let address: string -let contracts: NetworkContracts +let queryFeeModels: QueryFeeModels let logger: Logger let indexerManagementClient: IndexerManagementClient -let indexer: Indexer +let graphNode: GraphNode +let operator: Operator +let metrics: Metrics + +const PUBLIC_JSON_RPC_ENDPOINT = 'https://ethereum-goerli.publicnode.com' + +const testProviderUrl = + process.env.INDEXER_TEST_JRPC_PROVIDER_URL ?? PUBLIC_JSON_RPC_ENDPOINT + +const setupAll = async () => { + metrics = createMetrics() +} const setup = async () => { + // Clearing the registry prevents duplicate metric registration in the default registry. + metrics.registry.clear() logger = createLogger({ name: 'IndexerAgent', async: false, level: 'trace', }) - wallet = Wallet.createRandom() - sequelize = await connectDatabase(__DATABASE__) models = defineIndexerManagementModels(sequelize) - address = '0x3C17A4c7cD8929B83e4705e04020fA2B1bca2E55' - contracts = await connectContracts(wallet, 5) - await sequelize.sync({ force: true }) - - const statusEndpoint = 'http://localhost:8030/graphql' - const indexingStatusResolver = new IndexingStatusResolver({ - logger: logger, - statusEndpoint: 'statusEndpoint', - }) + queryFeeModels = defineQueryFeeModels(sequelize) + sequelize = await sequelize.sync({ force: true }) + + const indexNodeIDs = ['node_1'] - const networkSubgraph = await NetworkSubgraph.create({ + graphNode = new GraphNode( logger, - endpoint: - 'https://api.thegraph.com/subgraphs/name/graphprotocol/graph-network-testnet', - deployment: undefined, + 'http://test-admin-endpoint.xyz', + 'https://test-query-endpoint.xyz', + 'https://test-status-endpoint.xyz', + indexNodeIDs, + ) + + const networkSpecification = specification.NetworkSpecification.parse({ + networkIdentifier: 'eip155:5', + gateway: { + url: 'http://localhost:8030/', + }, + networkProvider: { + url: testProviderUrl, + }, + indexerOptions: { + address: '0xf56b5d582920E4527A818FBDd801C0D80A394CB8', + mnemonic: + 'famous aspect index polar tornado zero wedding electric floor chalk tenant junk', + url: 'http://test-indexer.xyz', + }, + subgraphs: { + networkSubgraph: { + url: 'http://test-url.xyz', + }, + epochSubgraph: { + url: 'http://test-url.xyz', + }, + }, + transactionMonitoring: { + gasIncreaseTimeout: 240000, + gasIncreaseFactor: 1.2, + baseFeePerGasMax: 100 * 10 ** 9, + maxTransactionAttempts: 0, + }, + dai: { + contractAddress: '0x4e8a4C63Df58bf59Fef513aB67a76319a9faf448', + inject: false, + }, }) - const indexNodeIDs = ['node_1'] + const network = await Network.create( + logger, + networkSpecification, + queryFeeModels, + graphNode, + metrics, + ) + + const multiNetworks = new MultiNetworks( + [network], + (n: Network) => n.specification.networkIdentifier, + ) + indexerManagementClient = await createIndexerManagementClient({ models, - address: toAddress(address), - contracts: contracts, - indexingStatusResolver, + graphNode, indexNodeIDs, - deploymentManagementEndpoint: statusEndpoint, - networkSubgraph, logger, defaults: { globalIndexingRule: { @@ -154,21 +206,10 @@ const setup = async () => { parallelAllocations: 1, }, }, - features: { - injectDai: false, - }, + multiNetworks, }) - indexer = new Indexer( - logger, - 'test', - indexingStatusResolver, - indexerManagementClient, - ['test'], - parseGRT('1000'), - address, - AllocationManagementMode.AUTO, - ) + operator = new Operator(logger, indexerManagementClient, networkSpecification) } const teardown = async () => { @@ -176,6 +217,8 @@ const teardown = async () => { } describe('Indexer tests', () => { + jest.setTimeout(60_000) + beforeAll(setupAll) beforeEach(setup) afterEach(teardown) @@ -200,11 +243,12 @@ describe('Indexer tests', () => { previousEpochReferenceProof: '0xd04b5601739a1638719696d0735c92439267a89248c6fd21388d9600f5c942f6', status: 'potential', + protocolNetwork: 'eip155:5', } const disputes = [badDispute] - await expect(indexer.storePoiDisputes(disputes)).rejects.toThrow( + await expect(operator.storePoiDisputes(disputes)).rejects.toThrow( 'Failed to store potential POI disputes', ) }) @@ -216,13 +260,13 @@ describe('Indexer tests', () => { const expectedResult = disputes.map((dispute: Record) => { return disputeFromGraphQL(dispute) }) - await expect(indexer.storePoiDisputes(disputes)).resolves.toEqual( + await expect(operator.storePoiDisputes(disputes)).resolves.toEqual( expectedResult, ) - await expect(indexer.storePoiDisputes(disputes)).resolves.toEqual( + await expect(operator.storePoiDisputes(disputes)).resolves.toEqual( expectedResult, ) - await expect(indexer.storePoiDisputes(disputes)).resolves.toEqual( + await expect(operator.storePoiDisputes(disputes)).resolves.toEqual( expectedResult, ) }) @@ -235,11 +279,11 @@ describe('Indexer tests', () => { return disputeFromGraphQL(dispute) }) const expectedFilteredResult = [disputeFromGraphQL(TEST_DISPUTE_2)] - await expect(indexer.storePoiDisputes(disputes)).resolves.toEqual( + await expect(operator.storePoiDisputes(disputes)).resolves.toEqual( expectedResult, ) - await expect(indexer.fetchPOIDisputes('potential', 205)).resolves.toEqual( - expectedFilteredResult, - ) + await expect( + operator.fetchPOIDisputes('potential', 205, 'eip155:5'), + ).resolves.toEqual(expectedFilteredResult) }) }) From 1e7ecb7eec322bd55fbd5f07c8ee1d64b5786e15 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:24:34 -0300 Subject: [PATCH 12/16] agent: new database migration to add the protocolNetwork field --- .../11-add-protocol-network-field.ts | 457 ++++++++++++++++++ 1 file changed, 457 insertions(+) create mode 100644 packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts diff --git a/packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts b/packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts new file mode 100644 index 000000000..049496408 --- /dev/null +++ b/packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts @@ -0,0 +1,457 @@ +import { Logger } from '@graphprotocol/common-ts' +import { specification } from '@graphprotocol/indexer-common' +import { QueryTypes, DataTypes, QueryInterface, Op } from 'sequelize' + +const MANUAL_CONSTRAINT_NAME_FRAGMENT = '_composite_manual' + +interface MigrationContext { + queryInterface: QueryInterface + logger: Logger + networkSpecifications: specification.NetworkSpecification[] +} + +interface Context { + context: MigrationContext +} + +interface ForeignKey { + table: string + // This table's column that holds the FK + columnName: string + // Could also be composite, but we don't have any of that kind at this moment + refColumnName: string +} + +interface MigrationInput { + table: string + oldPrimaryKeyColumns: string[] + newColumn: string + referencedBy?: ForeignKey[] +} + +interface MigrationTarget extends MigrationInput { + oldPrimaryKeyConstraint: string + newPrimaryKeyConstraint: string +} + +const defaults: Pick = { + newColumn: 'protocolNetwork', +} + +const migrationInputs: MigrationInput[] = [ + { + table: 'Actions', + oldPrimaryKeyColumns: ['id'], + }, + { + table: 'IndexingRules', + oldPrimaryKeyColumns: ['identifier'], + }, + { + table: 'POIDisputes', + oldPrimaryKeyColumns: ['allocationID'], + }, + { + table: 'allocation_receipts', + oldPrimaryKeyColumns: ['id', 'allocation'], + }, + { + table: 'vouchers', + oldPrimaryKeyColumns: ['allocation'], + }, + { + table: 'transfer_receipts', + oldPrimaryKeyColumns: ['id', 'signer'], + }, + { + table: 'transfers', + oldPrimaryKeyColumns: ['signer', 'routingId'], + }, + { + table: 'allocation_summaries', + oldPrimaryKeyColumns: ['allocation'], + referencedBy: [ + { + table: 'allocation_receipts', + columnName: 'allocation', + refColumnName: 'allocation', + }, + { + table: 'transfers', + columnName: 'allocation', + refColumnName: 'allocation', + }, + { + table: 'vouchers', + columnName: 'allocation', + refColumnName: 'allocation', + }, + ], + }, +].map(input => ({ ...input, ...defaults })) + +export async function up({ context }: Context): Promise { + const { queryInterface, networkSpecifications, logger } = context + const m = new Migration(queryInterface, logger) + + // This migration requires that just one network is used if the database holds previous data. + const hasExistingRows = await m.hasExistingRows(migrationInputs) + if (hasExistingRows && networkSpecifications.length !== 1) { + throw new Error( + `Migration expects only one network specification, but found ${networkSpecifications.length}. ` + + `Please avoid using other network specifications until this migration completes. ` + + `Make sure to use the same network specification that matches the data in the database.`, + ) + } + const networkChainId = networkSpecifications[0].networkIdentifier + + for (const input of migrationInputs) { + await m.addPrimaryKeyMigration(input, networkChainId) + } +} + +export async function down({ context }: Context): Promise { + const { queryInterface, networkSpecifications, logger } = context + const m = new Migration(queryInterface, logger) + + // This migration requires that just one network is used + if (networkSpecifications.length !== 1) { + throw new Error( + `Migration expects only one network specification, but found ${networkSpecifications.length}. ` + + `Please avoid using other network specifications until this migration completes. ` + + `Make sure to use the same network specification for the network data that will be left in the database.`, + ) + } + const networkChainId = networkSpecifications[0].networkIdentifier + + for (const input of migrationInputs) { + await m.removePrimaryKeyMigration(input, networkChainId) + + // TODO: Cascade the removal of primary keys + // TODO: Restore the foreign keys of cascaded constraint removals + } +} + +// Helper migration class +class Migration { + queryInterface: QueryInterface + logger: Logger + constructor(queryInterface: QueryInterface, logger: Logger) { + this.queryInterface = queryInterface + this.logger = logger + } + + // Main migration steps in the UP direction + async addPrimaryKeyMigration( + input: MigrationInput, + networkChainId: string, + ): Promise { + // Skip migration for this table if it doesn't exist. + const tableExists = await this.checkTableExists(input) + if (!tableExists) { + this.logger.info( + `Table '${input.table}' does not exist, migration not necessary`, + ) + return + } + // Skip migration for this table if it already has the 'protocolNetwork' column + const columnExists = await this.checkColumnExists(input) + if (columnExists) { + return + } + + // Infer primary key constraint names + const target = await this.processMigrationInput(input) + + // Add protocolNetwork columns + await this.addColumn(target) + + // Loosen constraints + await this.removeConstraint(target) + + // Populate the `protocolNetwork` columns with the provided network ID + await this.updateTable(target, networkChainId) + + // Restore constraints + await this.restorePrimaryKeyConstraint(target) + + // Alter the `protocolNetwork` columns to be NOT NULL + await this.alterColumn(target) + + // Restore broken foreign keys + await this.restoreBrokenForeignKeys(target) + } + + // Main migration steps in the DOWN direction + async removePrimaryKeyMigration( + input: MigrationInput, + networkChainId: string, + ): Promise { + // Infer primary key constraint names + const target = await this.processMigrationInputDown(input) + + // Drop the new primary key constraint + await this.removeNewConstraint(target) + + // Delete rows from other protocol networks + await this.deleteRowsFromOtherNetworks(target, networkChainId) + + // Drop the new columns + await this.dropColumn(target) + + // Restore the old primary key constraint + await this.restoreOldPrimaryKeyConstraint(target) + } + + async checkTableExists( + input: Pick, + ): Promise { + const exists = await this.queryInterface.tableExists(input.table) + if (!exists) { + return false + } + return true + } + + async checkColumnExists(input: MigrationInput): Promise { + this.logger.debug( + `Checking if table '${input.table}' has the '${input.newColumn}' column`, + ) + const tableSpecification = await this.queryInterface.describeTable( + input.table, + ) + const column = tableSpecification[input.newColumn] + if (!column) { + return false + } + // Check if the existing column is a primary key (it should be) + if (!column.primaryKey) { + throw new Error( + `Column '${input.newColumn}' of table '${input.table}' exists, but it is not a primary key.`, + ) + } + this.logger.info( + `Column '${input.newColumn}' of table '${input.table}' already exists, migration not necessary`, + ) + return true + } + + // Only for the UP step + async processMigrationInput(input: MigrationInput): Promise { + this.logger.debug(`Inferring primary key name for table '${input.table}'`) + const oldPrimaryKeyConstraint = await this.getPrimaryKeyConstraintName( + input, + ) + this.logger.debug( + `Table '${input.table}' existing primary key name is '${oldPrimaryKeyConstraint}'`, + ) + const newPrimaryKeyConstraint = + oldPrimaryKeyConstraint + MANUAL_CONSTRAINT_NAME_FRAGMENT + this.logger.debug( + `Table '${input.table}' updated primary key name will be '${newPrimaryKeyConstraint}'`, + ) + return { + ...input, + oldPrimaryKeyConstraint, + newPrimaryKeyConstraint, + } + } + // Only for the DOWN step + async processMigrationInputDown( + input: MigrationInput, + ): Promise { + const currentPrimaryKeyConstraint = await this.getPrimaryKeyConstraintName( + input, + ) + let previousPrimaryKeyConstraint + if (currentPrimaryKeyConstraint.endsWith(MANUAL_CONSTRAINT_NAME_FRAGMENT)) { + previousPrimaryKeyConstraint = currentPrimaryKeyConstraint.replace( + MANUAL_CONSTRAINT_NAME_FRAGMENT, + '', + ) + } else { + previousPrimaryKeyConstraint = `{input.table}_pkey` + } + + return { + ...input, + newPrimaryKeyConstraint: currentPrimaryKeyConstraint, + oldPrimaryKeyConstraint: previousPrimaryKeyConstraint, + } + } + + async getPrimaryKeyConstraintName(target: MigrationInput): Promise { + const result: null | { constraint?: string } = + await this.queryInterface.sequelize.query( + ` +SELECT + con.conname as constraint +FROM + pg_catalog.pg_constraint con + INNER JOIN pg_catalog.pg_class rel ON rel.oid = con.conrelid + INNER JOIN pg_catalog.pg_namespace nsp ON nsp.oid = connamespace +WHERE + nsp.nspname = 'public' + AND rel.relname = :table + AND con.contype = 'p'; +`, + { + type: QueryTypes.SELECT, + replacements: { table: target.table }, + plain: true, + raw: true, + }, + ) + + if (!result || !result.constraint) { + throw new Error( + `Failed to infer primary key constraint name for table '${target.table}'`, + ) + } + return result.constraint + } + + async addColumn(target: MigrationTarget): Promise { + this.logger.info( + `Add '${target.newColumn}' column to ${target.table} table`, + ) + + // Note: we set it to be nullable, but it is only for this migration. At the end we + // set it back to not null. + await this.queryInterface.addColumn(target.table, target.newColumn, { + type: DataTypes.STRING, + allowNull: true, + }) + } + + async dropColumn(target: MigrationTarget): Promise { + const tables = await this.queryInterface.showAllTables() + if (tables.includes(target.table)) { + this.logger.info( + `Drop '${target.newColumn}' column from ${target.table} table`, + ) + await this.queryInterface.removeColumn(target.table, target.newColumn) + } + } + + async updateTable(target: MigrationTarget, value: string) { + const values = { [target.newColumn]: value } + const where = { [target.newColumn]: null } + this.logger.info( + `Set '${target.table}' table '${target.newColumn}' column to '${value}'`, + ) + await this.queryInterface.bulkUpdate(target.table, values, where) + } + + async alterColumn(target: MigrationTarget) { + this.logger.info( + `Altering ${target.table} table ${target.newColumn} to be non-nullable`, + ) + await this.queryInterface.changeColumn(target.table, target.newColumn, { + type: DataTypes.STRING, + allowNull: false, + }) + } + + async removeConstraint(target: MigrationTarget) { + this.logger.info( + `Temporarily removing primary key constraints from ${target.table}`, + ) + const sql = `ALTER TABLE "${target.table}" DROP CONSTRAINT "${target.oldPrimaryKeyConstraint}" CASCADE` + await this.queryInterface.sequelize.query(sql) + } + + async restorePrimaryKeyConstraint(target: MigrationTarget) { + this.logger.info(`Restoring primary key constraints from ${target.table}`) + await this.queryInterface.addConstraint(target.table, { + fields: [...target.oldPrimaryKeyColumns, target.newColumn], + type: 'primary key', + name: target.newPrimaryKeyConstraint, + }) + } + + async restoreBrokenForeignKeys(target: MigrationTarget) { + if (!target.referencedBy || target.referencedBy.length === 0) { + return + } + this.logger.info( + `Restoring broken foreign keys for tables that depend on table '${target.table}'`, + ) + for (const dependent of target.referencedBy) { + this.logger.debug( + `Restoring foreing key between tables '${dependent.table}' and '${target.table}'`, + ) + const constraintName = '${dependent.table}_${target.table}_fkey' + + const createConstraintSql = ` + ALTER TABLE "${dependent.table}" + ADD CONSTRAINT "${constraintName}" FOREIGN KEY ("${dependent.columnName}", "${target.newColumn}") + REFERENCES "${target.table}" ("${dependent.refColumnName}", "${target.newColumn}") + ON UPDATE CASCADE ON DELETE CASCADE NOT VALID; +` + // PostgreSQL docs suggests doing this in two steps to avoid unecessary locks + const validateConstraintSql = `ALTER TABLE "${dependent.table}" VALIDATE CONSTRAINT "${constraintName}";` + + await this.queryInterface.sequelize.query(createConstraintSql) + await this.queryInterface.sequelize.query(validateConstraintSql) + } + } + + // Only for the DOWN step + async removeNewConstraint(target: MigrationTarget) { + await this.queryInterface.removeConstraint( + target.table, + target.newPrimaryKeyConstraint, + ) + } + + // Only for the DOWN step + async deleteRowsFromOtherNetworks( + target: MigrationTarget, + networkChainId: string, + ) { + await this.queryInterface.bulkDelete(target.table, { + [target.newColumn]: { + [Op.ne]: networkChainId, + }, + }) + } + + // Only for the DOWN step + async restoreOldPrimaryKeyConstraint(target: MigrationTarget) { + await this.queryInterface.addConstraint(target.table, { + fields: target.oldPrimaryKeyColumns, + type: 'primary key', + name: target.oldPrimaryKeyConstraint, + }) + } + + // Checks if a table has at least one row + async tableHaveRows(table: string): Promise { + if (!(await this.checkTableExists({ table }))) { + this.logger.trace(`Table '${table}' does not exist, ignoring row check.`) + return false + } + const result: null | { count?: number } = + await this.queryInterface.sequelize.query( + `SELECT COUNT(*) AS count FROM "${table}"`, + { + type: QueryTypes.SELECT, + plain: true, + raw: true, + }, + ) + if (!result || !result.count) { + throw new Error(`Invalid query result: ${result}`) + } + this.logger.debug(`Table '${table}' has ${result.count} row(s)`) + return result.count > 0 + } + + // Checks if input tables have at least one row + async hasExistingRows(targets: MigrationInput[]): Promise { + return ( + await Promise.all(targets.map(t => this.tableHaveRows(t.table))) + ).some(Boolean) + } +} From f8b7c709d7754ad83d84c2d26f0faaad304f2c59 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:25:31 -0300 Subject: [PATCH 13/16] ci: update github workflow files --- .github/workflows/check-formatting.yml | 3 +-- .github/workflows/ci.yml | 11 +++-------- .github/workflows/publish-native-binaries.yml | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml index 4f86c94d7..eecb6356c 100644 --- a/.github/workflows/check-formatting.yml +++ b/.github/workflows/check-formatting.yml @@ -3,8 +3,7 @@ name: "Check Formatting" on: push: branches: [main] - pull_request: - branches: [main] + pull_request: {} jobs: check: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfdcb8052..3e35e00a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,20 +6,15 @@ name: CI on: push: branches: [main] - pull_request: - branches: [main] + pull_request: {} jobs: build: strategy: matrix: - node-version: [14, 16, 17] + node-version: [16, 18] system: - - os: ubuntu-20.04 - include: - - node_version: 18 - system: - os: ubuntu-22.04 + - os: ubuntu-22.04 runs-on: ${{ matrix.system.os }} services: postgres: diff --git a/.github/workflows/publish-native-binaries.yml b/.github/workflows/publish-native-binaries.yml index 3638b7903..02ba08cb5 100644 --- a/.github/workflows/publish-native-binaries.yml +++ b/.github/workflows/publish-native-binaries.yml @@ -15,7 +15,7 @@ jobs: # (https://github.com/bchr02/node-pre-gyp-github/issues/42) fail-fast: false matrix: - node_version: [14, 16, 17] + node_version: [16, 17, 18, 19, 20] system: - os: macos-latest target: x86_64-apple-darwin From 713411b49801dd191303d1da6931511b3cd49195 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 14:25:57 -0300 Subject: [PATCH 14/16] service: minor updates for the new protocolNetwork field --- packages/indexer-service/src/allocations.ts | 4 +- .../indexer-service/src/commands/start.ts | 61 ++++++++++++------- .../src/query-fees/allocations.ts | 6 ++ .../src/server/__tests__/server.test.ts | 2 +- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/packages/indexer-service/src/allocations.ts b/packages/indexer-service/src/allocations.ts index 4e6d6e2fc..87b238334 100644 --- a/packages/indexer-service/src/allocations.ts +++ b/packages/indexer-service/src/allocations.ts @@ -20,12 +20,14 @@ export interface MonitorEligibleAllocationsOptions { logger: Logger networkSubgraph: NetworkSubgraph interval: number + protocolNetwork: string } export const monitorEligibleAllocations = ({ indexer, logger: parentLogger, networkSubgraph, + protocolNetwork, interval, }: MonitorEligibleAllocationsOptions): Eventual => { const logger = parentLogger.child({ component: 'AllocationMonitor' }) @@ -128,7 +130,7 @@ export const monitorEligibleAllocations = ({ return [ ...result.data.indexer.activeAllocations, ...result.data.indexer.recentlyClosedAllocations, - ].map(parseGraphQLAllocation) + ].map(x => parseGraphQLAllocation(x, protocolNetwork)) } catch (err) { logger.warn(`Failed to query indexer allocations, keeping existing`, { allocations: currentAllocations.map(allocation => allocation.id), diff --git a/packages/indexer-service/src/commands/start.ts b/packages/indexer-service/src/commands/start.ts index c9dc07717..a99528356 100644 --- a/packages/indexer-service/src/commands/start.ts +++ b/packages/indexer-service/src/commands/start.ts @@ -20,10 +20,12 @@ import { defineQueryFeeModels, indexerError, IndexerErrorCode, - IndexingStatusResolver, + GraphNode, Network, NetworkSubgraph, registerIndexerErrorMetrics, + resolveChainId, + validateProviderNetworkIdentifier, } from '@graphprotocol/indexer-common' import { createServer } from '../server' @@ -36,7 +38,9 @@ export default { describe: 'Start the service', builder: (yargs: Argv): Argv => { return yargs - .option('ethereum', { + .option('network-provider', { + //TODO:FIXME: uptrade this field as we did for Agent + alias: 'ethereum', description: 'Ethereum node or provider URL', type: 'string', required: true, @@ -177,6 +181,7 @@ export default { if (!argv['network-subgraph-endpoint'] && !argv['network-subgraph-deployment']) { return `At least one of --network-subgraph-endpoint and --network-subgraph-deployment must be provided` } + return true }) .config({ @@ -273,18 +278,23 @@ export default { logger.info('Successfully connected to database') logger.info(`Connect to network subgraph`) - const indexingStatusResolver = new IndexingStatusResolver({ + const graphNode = new GraphNode( logger, - statusEndpoint: argv.graphNodeStatusEndpoint, - }) + // We use a fake Graph Node admin endpoint here because we don't + // want the Indexer Service to perform management actions on + // Graph Node. + 'http://fake-graph-node-admin-endpoint', + argv.graphNodeQueryEndpoint, + argv.graphNodeStatusEndpoint, + argv.indexNodeIds, + ) const networkSubgraph = await NetworkSubgraph.create({ logger, endpoint: argv.networkSubgraphEndpoint, deployment: argv.networkSubgraphDeployment ? { - indexingStatusResolver, + graphNode, deployment: new SubgraphDeploymentID(argv.networkSubgraphDeployment), - graphNodeQueryEndpoint: argv.graphNodeQueryEndpoint, } : undefined, }) @@ -293,19 +303,32 @@ export default { const networkProvider = await Network.provider( logger, metrics, - argv.ethereum, + '_', + argv.networkProvider, argv.ethereumPollingInterval, ) - const network = await networkProvider.getNetwork() + const networkIdentifier = await networkProvider.getNetwork() + const protocolNetwork = resolveChainId(networkIdentifier.chainId) + + // If the network subgraph deployment is present, validate if the `chainId` we get from our + // provider is consistent. + if (argv.networkSubgraphDeployment) { + validateProviderNetworkIdentifier( + protocolNetwork, + argv.networkSubgraphDeployment, + graphNode, + logger, + ) + } logger.info('Connect to contracts', { - network: network.name, - chainId: network.chainId, + network: networkIdentifier.name, + chainId: networkIdentifier.chainId, }) let contracts = undefined try { - contracts = await connectContracts(networkProvider, network.chainId) + contracts = await connectContracts(networkProvider, networkIdentifier.chainId) } catch (error) { logger.error( `Failed to connect to contracts, please ensure you are using the intended Ethereum Network`, @@ -332,6 +355,7 @@ export default { queryFeeModels, logger, toAddress(argv.clientSignerAddress), + protocolNetwork, ) // Ensure the address is checksummed @@ -347,6 +371,7 @@ export default { indexer: indexerAddress, logger, networkSubgraph, + protocolNetwork, interval: argv.allocationSyncingInterval, }) @@ -355,7 +380,7 @@ export default { logger, allocations, wallet, - chainId: network.chainId, + chainId: networkIdentifier.chainId, disputeManagerAddress: contracts.disputeManager.address, }) @@ -371,12 +396,8 @@ export default { const indexerManagementClient = await createIndexerManagementClient({ models, - address, - contracts, - indexingStatusResolver, + graphNode, indexNodeIDs: ['node_1'], // This is just a dummy since the indexer-service doesn't manage deployments, - deploymentManagementEndpoint: argv.graphNodeStatusEndpoint, - networkSubgraph, logger, defaults: { // This is just a dummy, since we're never writing to the management @@ -385,9 +406,7 @@ export default { allocationAmount: BigNumber.from('0'), }, }, - features: { - injectDai: true, - }, + multiNetworks: undefined, }) // Spin up a basic webserver diff --git a/packages/indexer-service/src/query-fees/allocations.ts b/packages/indexer-service/src/query-fees/allocations.ts index e86fc7079..e75480f06 100644 --- a/packages/indexer-service/src/query-fees/allocations.ts +++ b/packages/indexer-service/src/query-fees/allocations.ts @@ -41,18 +41,21 @@ export class AllocationReceiptManager implements ReceiptManager { private readonly _cache: Map> = new Map() private readonly _flushQueue: string[] = [] private readonly _allocationReceiptVerifier: NativeSignatureVerifier + private readonly protocolNetwork: string constructor( sequelize: Sequelize, queryFeeModels: QueryFeeModels, logger: Logger, clientSignerAddress: Address, + protocolNetwork: string, ) { logger = logger.child({ component: 'ReceiptManager' }) this._sequelize = sequelize this._queryFeeModels = queryFeeModels this._allocationReceiptVerifier = new NativeSignatureVerifier(clientSignerAddress) + this.protocolNetwork = protocolNetwork timer(30_000).pipe(async () => { try { @@ -114,6 +117,7 @@ export class AllocationReceiptManager implements ReceiptManager { allocation: receipt.allocation, fees: receipt.fees.toString(), signature, + protocolNetwork: this.protocolNetwork, }) return receipt @@ -147,6 +151,7 @@ export class AllocationReceiptManager implements ReceiptManager { this._queryFeeModels, receipt.allocation, transaction, + this.protocolNetwork, ) if (isNewSummary) { await summary.save() @@ -160,6 +165,7 @@ export class AllocationReceiptManager implements ReceiptManager { allocation: receipt.allocation, signature: receipt.signature, fees: receipt.fees, + protocolNetwork: this.protocolNetwork, }, transaction, }) diff --git a/packages/indexer-service/src/server/__tests__/server.test.ts b/packages/indexer-service/src/server/__tests__/server.test.ts index b6feec5f7..8fd8941bf 100644 --- a/packages/indexer-service/src/server/__tests__/server.test.ts +++ b/packages/indexer-service/src/server/__tests__/server.test.ts @@ -62,7 +62,7 @@ const setup = async () => { models = defineIndexerManagementModels(sequelize) address = '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1' contracts = await connectContracts(getTestProvider('goerli'), 5) - await sequelize.sync({ force: true }) + sequelize = await sequelize.sync({ force: true }) const statusEndpoint = 'http://localhost:8030/graphql' indexingStatusResolver = new IndexingStatusResolver({ logger: logger, From 083f454266feff7aade25ab7675fe1dfe181e402 Mon Sep 17 00:00:00 2001 From: tilacog Date: Mon, 7 Aug 2023 15:06:48 -0300 Subject: [PATCH 15/16] all: changelog --- packages/indexer-agent/CHANGELOG.md | 25 +++++++++++++++++++++++++ packages/indexer-cli/CHANGELOG.md | 2 ++ packages/indexer-common/CHANGELOG.md | 14 ++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/packages/indexer-agent/CHANGELOG.md b/packages/indexer-agent/CHANGELOG.md index e8391bbd7..1fadc615e 100644 --- a/packages/indexer-agent/CHANGELOG.md +++ b/packages/indexer-agent/CHANGELOG.md @@ -5,6 +5,31 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- A new migration to add the `protocolNetwork` column as a composite primary key to the following tables: + - Actions + - IndexingRules + - POIDisputes + - allocation_receipts + - vouchers + - transfer_receipts + - transfers + - allocation_summaries + +- The Agent can now be configured for multiple protocol networks. + To enable this feature, start the agent with the environment variable `INDEXER_AGENT_MULTINETWORK_MODE` set to + `true` and specify a directory containing YAML network specification files, one per network. + +- The Agent can be configured to automatically support subgraph transfers from L1 to L2. To enable + this feature, set the `enable-auto-migration-support` startup option to `true`. + +### Changed + +- The Agent GraphQL API was updated to accept (and in some cases require) a `protocolNetwork` + parameter to determine which network should be used for queries or mutations. + +- The `/network` endpoint exposed by the Agent now requires an additional path segment to disambiguate + which protocol network it should target, like `/network/mainnet` or `/network/arbitrum-one`. ## [0.20.17] - 2023-06-19 ### Changed diff --git a/packages/indexer-cli/CHANGELOG.md b/packages/indexer-cli/CHANGELOG.md index a93f224e1..2bcb4e548 100644 --- a/packages/indexer-cli/CHANGELOG.md +++ b/packages/indexer-cli/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Most CLI commands now require the protocol network to be identified using the option `--network`. ## [0.20.12] - 2023-02-19 ### Added diff --git a/packages/indexer-common/CHANGELOG.md b/packages/indexer-common/CHANGELOG.md index f74559024..dfef356e8 100644 --- a/packages/indexer-common/CHANGELOG.md +++ b/packages/indexer-common/CHANGELOG.md @@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- The `Network` type now holds references to all network-specific components, such as the `NetworkMonitor` and `Contracts` classes. + +- Introduced `GraphNode` class to replace `Indexer`, `Subgraph`, and `IndexingStatus` classes. + +- New general purpose `parser` module, used for input validation. + +- New `NetworkSpecification` type, which holds all information required to represent a protocol network. + +### Changed +- Added `protocolNetwork` field to most types. + + ## [0.20.17] - 2023-06-19 ### Changed From 1fde62600c2cd37cf0a71a0006009b4a0261a460 Mon Sep 17 00:00:00 2001 From: tilacog Date: Fri, 11 Aug 2023 11:51:22 -0300 Subject: [PATCH 16/16] all: post review changes --- packages/indexer-agent/src/agent.ts | 29 ++++++++-------- .../src/commands/common-options.ts | 2 +- packages/indexer-agent/src/commands/start.ts | 5 ++- .../11-add-protocol-network-field.ts | 1 - packages/indexer-cli/package.json | 2 ++ packages/indexer-cli/src/__tests__/util.ts | 6 +++- .../__tests__/resolvers/cost-models.test.ts | 9 ++++- .../src/indexer-management/__tests__/util.ts | 5 +++ .../src/indexer-management/client.ts | 2 +- .../resolvers/allocations.ts | 10 +++++- .../resolvers/indexer-status.ts | 33 ++++++++++++++++--- .../indexer-service/src/commands/start.ts | 1 - yarn.lock | 12 +++++++ 13 files changed, 89 insertions(+), 28 deletions(-) diff --git a/packages/indexer-agent/src/agent.ts b/packages/indexer-agent/src/agent.ts index 67865c3e3..f36034d1b 100644 --- a/packages/indexer-agent/src/agent.ts +++ b/packages/indexer-agent/src/agent.ts @@ -44,12 +44,15 @@ import isEqual from 'lodash.isequal' import mapValues from 'lodash.mapvalues' import zip from 'lodash.zip' -// Temporary marker used to signal that the following contract calls have been deprecated in the -// network it appears: +// The new Exponential Rebates for Indexes brought changes to the protocol contracts that deprecated +// the following methods: // - channelDisputeEpochs // - claimRebateRewards -// Once Exponential Rebates have been deployed to all networks, these calls should be removed from -// the code. +// This variable acts as a signal to other parts of the code, showing that we've ignored the results +// of these calls early in the current process. +// This is important because the Exponential Rebates aren't active on the mainnet yet, which still +// uses their results. Once those contract changes have been deployed to all networks, these calls +// can be removed from the code. const EXPONENTIAL_REBATES_MARKER = -1 type ActionReconciliationContext = [AllocationDecision[], number, number] @@ -246,12 +249,12 @@ export class Agent { }, ) - this.buildEventualTree() + this.reconciliationLoop() return this } - buildEventualTree() { - const logger = this.logger.child({ context: 'reconciliation-loop' }) + reconciliationLoop() { + const logger = this.logger.child({ component: 'ReconciliationLoop' }) const currentEpochNumber: Eventual> = timer( 600_000, ).tryMap( @@ -315,7 +318,6 @@ export class Agent { return this.multiNetworks.map(async ({ network, operator }) => { logger.trace('Fetching indexing rules', { protocolNetwork: network.specification.networkIdentifier, - context: 'reconciliation-loop', }) let rules = await operator.indexingRules(true) const subgraphRuleIds = rules @@ -1007,7 +1009,7 @@ export class Agent { deploymentIDSet(targetDeployments), ) if (isReconciliationNeeded) { - // QUESTION: should we return early in here case reconciliation is not needed? + // TODO: Return early in here case reconciliation is not needed this.logger.debug('Reconcile deployments', { syncing: activeDeployments.map(id => id.display), target: targetDeployments.map(id => id.display), @@ -1027,8 +1029,9 @@ export class Agent { !deploymentInList(eligibleAllocationDeployments, deployment), ) - // QUESTION: Same as before: should we return early in here case reconciliation is - // not needed? + // TODO: Same as before: Should we return early in here case reconciliation is not needed? Also, + // this conditional seems the be doing the same work as the previous one, we should consolidate + // them. if (deploy.length + remove.length !== 0) { this.logger.info('Deployment changes', { deploy: deploy.map(id => id.display), @@ -1130,7 +1133,7 @@ export class Agent { epoch, }) - // QUESTION: Can we replace `filter` for `find` here? Is there such a case when we + // TODO: Can we replace `filter` for `find` here? Is there such a case when we // would have multiple allocations for the same subgraph? const activeDeploymentAllocations = activeAllocations.filter( allocation => @@ -1182,8 +1185,6 @@ export class Agent { } } - // QUESTION: the `activeAllocations` parameter is used only for logging. Should we - // remove it from this function? async reconcileActions( networkDeploymentAllocationDecisions: NetworkMapped, epoch: NetworkMapped, diff --git a/packages/indexer-agent/src/commands/common-options.ts b/packages/indexer-agent/src/commands/common-options.ts index be8c571ac..70fa008b6 100644 --- a/packages/indexer-agent/src/commands/common-options.ts +++ b/packages/indexer-agent/src/commands/common-options.ts @@ -132,7 +132,7 @@ export function injectCommonStartupOptions(argv: Argv): Argv { .check(argv => { // Unset arguments set to empty strings. // This can happen when users set their options as enviroment variables and don't - // assign any vaule to them. + // assign any value to them. // // For example: // ```sh diff --git a/packages/indexer-agent/src/commands/start.ts b/packages/indexer-agent/src/commands/start.ts index 7327faf8c..6a0be6eae 100644 --- a/packages/indexer-agent/src/commands/start.ts +++ b/packages/indexer-agent/src/commands/start.ts @@ -371,9 +371,6 @@ export async function createNetworkSpecification( } } -// TODO: Split this code into two functions: -// 1. [X] Create NetworkSpecification -// 2. [ ] Start Agent with NetworkSpecification as input. export async function run( argv: AgentOptions, networkSpecifications: spec.NetworkSpecification[], @@ -519,7 +516,9 @@ export async function run( multiNetworks, }) + // -------------------------------------------------------------------------------- // * Indexer Management Server + // -------------------------------------------------------------------------------- logger.info('Launch indexer management API server', { port: argv.indexerManagementPort, }) diff --git a/packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts b/packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts index 049496408..97d1a28ea 100644 --- a/packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts +++ b/packages/indexer-agent/src/db/migrations/11-add-protocol-network-field.ts @@ -18,7 +18,6 @@ interface ForeignKey { table: string // This table's column that holds the FK columnName: string - // Could also be composite, but we don't have any of that kind at this moment refColumnName: string } diff --git a/packages/indexer-cli/package.json b/packages/indexer-cli/package.json index 2c51aa356..11b5aeff2 100644 --- a/packages/indexer-cli/package.json +++ b/packages/indexer-cli/package.json @@ -42,10 +42,12 @@ }, "devDependencies": { "@types/isomorphic-fetch": "0.0.36", + "@types/lodash.clonedeep": "^4.5.7", "@typescript-eslint/eslint-plugin": "5.19.0", "@typescript-eslint/parser": "5.19.0", "eslint": "8.13.0", "eslint-config-prettier": "8.5.0", + "lodash.clonedeep": "^4.5.0", "prettier": "2.6.2", "ts-jest": "27.1.4", "typescript": "4.6.3" diff --git a/packages/indexer-cli/src/__tests__/util.ts b/packages/indexer-cli/src/__tests__/util.ts index 09233423f..ace57beac 100644 --- a/packages/indexer-cli/src/__tests__/util.ts +++ b/packages/indexer-cli/src/__tests__/util.ts @@ -28,6 +28,7 @@ import { Logger, parseGRT, } from '@graphprotocol/common-ts' +import cloneDeep from 'lodash.clonedeep' const INDEXER_SAVE_CLI_TEST_OUTPUT: boolean = !!process.env.INDEXER_SAVE_CLI_TEST_OUTPUT && @@ -119,8 +120,11 @@ export const setup = async () => { metrics, ) + const fakeMainnetNetwork = cloneDeep(network) as Network + fakeMainnetNetwork.specification.networkIdentifier = 'eip155:1' + const multiNetworks = new MultiNetworks( - [network], + [network, fakeMainnetNetwork], (n: Network) => n.specification.networkIdentifier, ) diff --git a/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.test.ts b/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.test.ts index dd4e96187..035264e76 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.test.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/resolvers/cost-models.test.ts @@ -92,7 +92,13 @@ const setupAll = async () => { level: __LOG_LEVEL__ ?? 'error', }) - client = await createTestManagementClient(__DATABASE__, logger, true, metrics) + client = await createTestManagementClient( + __DATABASE__, + logger, + true, + metrics, + 'eip155:1', // Override with mainnet to enable the Cost Model feature + ) } const teardownAll = async () => { @@ -718,6 +724,7 @@ describe('Feature: Inject $DAI variable', () => { logger, false, metrics, + 'eip155:1', // Override with mainnet to enable the Cost Model feature ) const initial = { diff --git a/packages/indexer-common/src/indexer-management/__tests__/util.ts b/packages/indexer-common/src/indexer-management/__tests__/util.ts index 974cfca16..4dac58992 100644 --- a/packages/indexer-common/src/indexer-management/__tests__/util.ts +++ b/packages/indexer-common/src/indexer-management/__tests__/util.ts @@ -60,6 +60,7 @@ export const createTestManagementClient = async ( logger: Logger, injectDai: boolean, metrics: Metrics, + networkIdentifierOverride?: string, ): Promise => { // Clearing the registry prevents duplicate metric registration in the default registry. metrics.registry.clear() @@ -100,6 +101,10 @@ export const createTestManagementClient = async ( metrics, ) + if (networkIdentifierOverride) { + network.specification.networkIdentifier = networkIdentifierOverride + } + const multiNetworks = new MultiNetworks( [network], (n: Network) => n.specification.networkIdentifier, diff --git a/packages/indexer-common/src/indexer-management/client.ts b/packages/indexer-common/src/indexer-management/client.ts index 9ca2d190a..b2c5acfe9 100644 --- a/packages/indexer-common/src/indexer-management/client.ts +++ b/packages/indexer-common/src/indexer-management/client.ts @@ -377,7 +377,7 @@ const SCHEMA_SDL = gql` indexerRegistration(protocolNetwork: String!): IndexerRegistration! indexerDeployments: [IndexerDeployment]! indexerAllocations(protocolNetwork: String!): [IndexerAllocation]! - indexerEndpoints(protocolNetwork: String!): [IndexerEndpoints!]! + indexerEndpoints(protocolNetwork: String): [IndexerEndpoints!]! costModels(deployments: [String!]): [CostModel!]! costModel(deployment: String!): CostModel diff --git a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts index 8fad58f4b..e2d59fdc0 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/allocations.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/allocations.ts @@ -398,7 +398,15 @@ export default { const [currentEpoch, disputeEpochs, maxAllocationEpochs, epochLength] = await Promise.all([ networkMonitor.networkCurrentEpoch(), - contracts.staking.channelDisputeEpochs(), + contracts.staking.channelDisputeEpochs().catch((error) => { + logger.warn( + 'Failed to fetch channel dispute epochs. Ignoring claimable allocations', + { error, protocolNetwork: network.specification.networkIdentifier }, + ) + // Remove this call to channelDisputeEpochs once Exponential Rebates are deployed to + // all networks. Using a default value of zero on failure for now. + return 0 + }), contracts.staking.maxAllocationEpochs(), contracts.epochManager.epochLength(), ]) diff --git a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts index 0e96333b3..6b4d83a1d 100644 --- a/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts +++ b/packages/indexer-common/src/indexer-management/resolvers/indexer-status.ts @@ -4,7 +4,12 @@ import geohash from 'ngeohash' import gql from 'graphql-tag' import { IndexerManagementResolverContext } from '../client' import { SubgraphDeploymentID } from '@graphprotocol/common-ts' -import { indexerError, IndexerErrorCode, Network } from '@graphprotocol/indexer-common' +import { + indexerError, + IndexerErrorCode, + Network, + validateNetworkIdentifier, +} from '@graphprotocol/indexer-common' import { extractNetwork } from './utils' interface Test { test: (url: string) => string @@ -57,7 +62,7 @@ const URL_VALIDATION_TEST: Test = { export default { indexerRegistration: async ( - { protocolNetwork: unvalidateProtocolNetwork }: { protocolNetwork: string }, + { protocolNetwork: unvalidatedProtocolNetwork }: { protocolNetwork: string }, { multiNetworks }: IndexerManagementResolverContext, ): Promise => { if (!multiNetworks) { @@ -66,7 +71,7 @@ export default { ) } - const network = extractNetwork(unvalidateProtocolNetwork, multiNetworks) + const network = extractNetwork(unvalidatedProtocolNetwork, multiNetworks) const protocolNetwork = network.specification.networkIdentifier const address = network.specification.indexerOptions.address const contracts = network.contracts @@ -163,7 +168,7 @@ export default { }, indexerEndpoints: async ( - { protocolNetwork }: { protocolNetwork: string }, + { protocolNetwork: unvalidatedProtocolNetwork }: { protocolNetwork: string | null }, { multiNetworks, logger }: IndexerManagementResolverContext, ): Promise => { if (!multiNetworks) { @@ -172,7 +177,27 @@ export default { ) } const endpoints: Endpoints[] = [] + let networkIdentifier: string | null = null + + // Validate protocol network + try { + if (unvalidatedProtocolNetwork) { + networkIdentifier = validateNetworkIdentifier(unvalidatedProtocolNetwork) + } + } catch (parseError) { + throw new Error( + `Invalid protocol network identifier: '${unvalidatedProtocolNetwork}'. Error: ${parseError}`, + ) + } + await multiNetworks.map(async (network: Network) => { + // Skip if this query asks for another protocol network + if ( + networkIdentifier && + networkIdentifier !== network.specification.networkIdentifier + ) { + return + } try { const networkEndpoints = await endpointForNetwork(network) endpoints.push(networkEndpoints) diff --git a/packages/indexer-service/src/commands/start.ts b/packages/indexer-service/src/commands/start.ts index a99528356..5ad7b642f 100644 --- a/packages/indexer-service/src/commands/start.ts +++ b/packages/indexer-service/src/commands/start.ts @@ -39,7 +39,6 @@ export default { builder: (yargs: Argv): Argv => { return yargs .option('network-provider', { - //TODO:FIXME: uptrade this field as we did for Agent alias: 'ethereum', description: 'Ethereum node or provider URL', type: 'string', diff --git a/yarn.lock b/yarn.lock index cec112ecd..3fb6a2120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2828,6 +2828,13 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/lodash.clonedeep@^4.5.7": + version "4.5.7" + resolved "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.7.tgz#0e119f582ed6f9e6b373c04a644651763214f197" + integrity sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw== + dependencies: + "@types/lodash" "*" + "@types/lodash.countby@^4.6.7": version "4.6.7" resolved "https://registry.npmjs.org/@types/lodash.countby/-/lodash.countby-4.6.7.tgz" @@ -7582,6 +7589,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + lodash.groupby@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz"