diff --git a/third_party/pyth/price-service/package-lock.json b/third_party/pyth/price-service/package-lock.json index e3f2e092..6137fadc 100644 --- a/third_party/pyth/price-service/package-lock.json +++ b/third_party/pyth/price-service/package-lock.json @@ -15,12 +15,17 @@ "@pythnetwork/pyth-sdk-js": "^0.1.0", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/morgan": "^1.9.3", + "@types/response-time": "^2.3.5", "cors": "^2.8.5", "dotenv": "^10.0.0", "ethers": "^5.4.4", "express": "^4.17.2", + "express-validation": "^4.0.1", "http-status-codes": "^2.2.0", + "morgan": "^1.10.0", "prom-client": "^14.0.1", + "response-time": "^2.3.2", "winston": "^3.3.3" }, "devDependencies": { @@ -1067,6 +1072,19 @@ "node": ">=6" } }, + "node_modules/@hapi/hoek": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz", + "integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@improbable-eng/grpc-web": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz", @@ -1137,6 +1155,24 @@ "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.1.0.tgz", "integrity": "sha512-fsGx2vkXncoIpsrcjx6WY7JN0R74YG/lX2UA1Wz/m6MPgJrde1LHkmikOSdMZUU3KkpWGHT1ZdYoW+Ikv7Nv9g==" }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@solana/buffer-layout": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", @@ -1484,6 +1520,11 @@ "@types/range-parser": "*" } }, + "node_modules/@types/hapi__joi": { + "version": "16.0.12", + "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-16.0.12.tgz", + "integrity": "sha512-xJYifuz59jXdWY5JMS15uvA3ycS3nQYOGqoIIE0+fwQ0qI3/4CxBc6RHsOTp6wk9M0NWEdpcTl02lOQOKMifbQ==" + }, "node_modules/@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -1499,6 +1540,14 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, + "node_modules/@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "16.11.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz", @@ -1514,6 +1563,15 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "node_modules/@types/response-time": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.5.tgz", + "integrity": "sha512-4ANzp+I3K7sztFFAGPALWBvSl4ayaDSKzI2Bok+WNz+en2eB2Pvk6VCjR47PBXBWOkEg2r4uWpZOlXA5DNINOQ==", + "dependencies": { + "@types/express": "*", + "@types/node": "*" + } + }, "node_modules/@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -1803,6 +1861,22 @@ } ] }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/bech32": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", @@ -2440,6 +2514,16 @@ "node": ">= 0.10.0" } }, + "node_modules/express-validation": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/express-validation/-/express-validation-4.0.1.tgz", + "integrity": "sha512-DA8Nkl82ISo2Vi+eYjcokISz0eom1mcKW8t44Ns4MvsSEeg0WU0Ofoc0uWSZ0tv0J2Z5u7+jJ5nVmXN/tYRpfg==", + "dependencies": { + "@types/express": "^4.17.13", + "@types/hapi__joi": "16.x.x", + "joi": "^17.6.0" + } + }, "node_modules/eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -2801,6 +2885,18 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.48.tgz", "integrity": "sha512-4kxzqkrpwYtn6okJUcb2lfUu9ilnb3yhUOH6qX3nug8D2DupZ2drIkff2yJzYcNJVl3begnlcaBJ7tqiTTzjnQ==" }, + "node_modules/joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-base64": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", @@ -3034,6 +3130,29 @@ "resolved": "https://registry.npmjs.org/mobile-detect/-/mobile-detect-1.4.5.tgz", "integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g==" }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -3105,6 +3224,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3407,6 +3534,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/response-time": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", + "integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=", + "dependencies": { + "depd": "~1.1.0", + "on-headers": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4794,6 +4933,19 @@ "yargs": "^16.2.0" } }, + "@hapi/hoek": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz", + "integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@improbable-eng/grpc-web": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz", @@ -4861,6 +5013,24 @@ "resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.1.0.tgz", "integrity": "sha512-fsGx2vkXncoIpsrcjx6WY7JN0R74YG/lX2UA1Wz/m6MPgJrde1LHkmikOSdMZUU3KkpWGHT1ZdYoW+Ikv7Nv9g==" }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", + "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@solana/buffer-layout": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz", @@ -5112,6 +5282,11 @@ "@types/range-parser": "*" } }, + "@types/hapi__joi": { + "version": "16.0.12", + "resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-16.0.12.tgz", + "integrity": "sha512-xJYifuz59jXdWY5JMS15uvA3ycS3nQYOGqoIIE0+fwQ0qI3/4CxBc6RHsOTp6wk9M0NWEdpcTl02lOQOKMifbQ==" + }, "@types/lodash": { "version": "4.14.182", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", @@ -5127,6 +5302,14 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, + "@types/morgan": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz", + "integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "16.11.27", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz", @@ -5142,6 +5325,15 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, + "@types/response-time": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.5.tgz", + "integrity": "sha512-4ANzp+I3K7sztFFAGPALWBvSl4ayaDSKzI2Bok+WNz+en2eB2Pvk6VCjR47PBXBWOkEg2r4uWpZOlXA5DNINOQ==", + "requires": { + "@types/express": "*", + "@types/node": "*" + } + }, "@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -5395,6 +5587,21 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "bech32": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", @@ -5936,6 +6143,16 @@ "vary": "~1.1.2" } }, + "express-validation": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/express-validation/-/express-validation-4.0.1.tgz", + "integrity": "sha512-DA8Nkl82ISo2Vi+eYjcokISz0eom1mcKW8t44Ns4MvsSEeg0WU0Ofoc0uWSZ0tv0J2Z5u7+jJ5nVmXN/tYRpfg==", + "requires": { + "@types/express": "^4.17.13", + "@types/hapi__joi": "16.x.x", + "joi": "^17.6.0" + } + }, "eyes": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", @@ -6208,6 +6425,18 @@ } } }, + "joi": { + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", + "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "js-base64": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz", @@ -6395,6 +6624,25 @@ "resolved": "https://registry.npmjs.org/mobile-detect/-/mobile-detect-1.4.5.tgz", "integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g==" }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -6441,6 +6689,11 @@ "ee-first": "1.1.1" } }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6671,6 +6924,15 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, + "response-time": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz", + "integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=", + "requires": { + "depd": "~1.1.0", + "on-headers": "~1.0.1" + } + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", diff --git a/third_party/pyth/price-service/package.json b/third_party/pyth/price-service/package.json index fdee9915..f3784b94 100644 --- a/third_party/pyth/price-service/package.json +++ b/third_party/pyth/price-service/package.json @@ -24,12 +24,17 @@ "@pythnetwork/pyth-sdk-js": "^0.1.0", "@types/cors": "^2.8.12", "@types/express": "^4.17.13", + "@types/morgan": "^1.9.3", + "@types/response-time": "^2.3.5", "cors": "^2.8.5", "dotenv": "^10.0.0", "ethers": "^5.4.4", "express": "^4.17.2", + "express-validation": "^4.0.1", "http-status-codes": "^2.2.0", + "morgan": "^1.10.0", "prom-client": "^14.0.1", + "response-time": "^2.3.2", "winston": "^3.3.3" }, "directories": { diff --git a/third_party/pyth/price-service/src/helpers.ts b/third_party/pyth/price-service/src/helpers.ts index 10b16675..062960ed 100644 --- a/third_party/pyth/price-service/src/helpers.ts +++ b/third_party/pyth/price-service/src/helpers.ts @@ -1,6 +1,7 @@ // Time in seconds export type TimestampInSec = number; export type DurationInSec = number; +export type DurationInMs = number; export function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); diff --git a/third_party/pyth/price-service/src/promClient.ts b/third_party/pyth/price-service/src/promClient.ts index 702cb6ff..af6de7d3 100644 --- a/third_party/pyth/price-service/src/promClient.ts +++ b/third_party/pyth/price-service/src/promClient.ts @@ -1,6 +1,7 @@ +import { stat } from "fs"; import http = require("http"); import client = require("prom-client"); -import { DurationInSec } from "./helpers"; +import { DurationInMs, DurationInSec } from "./helpers"; import { logger } from "./logging"; // NOTE: To create a new metric: @@ -8,7 +9,7 @@ import { logger } from "./logging"; // 2) Create a method to set the metric to a value (such as `incIncoming` function below) // 3) Register the metric using `register.registerMetric` function. -const SERVICE_PREFIX = "price_service__"; +const SERVICE_PREFIX = "pyth__price_service__"; export class PromClient { private register = new client.Registry(); @@ -19,41 +20,17 @@ export class PromClient { name: `${SERVICE_PREFIX}vaas_received`, help: "number of Pyth VAAs received", }); - private apiLatestVaaRequestsCounter = new client.Counter({ - name: `${SERVICE_PREFIX}api_latest_vaa_requests_received`, - help: "Number of requests for latest vaa of a price feed" + private apiResponseTimeSummary = new client.Summary({ + name: `${SERVICE_PREFIX}api_response_time_ms`, + help: "Response time of a VAA", + labelNames: ["path", "status"] }); - private apiLatestVaaNotFoundResponseCounter = new client.Counter({ - name: `${SERVICE_PREFIX}api_latest_vaa_not_found_response`, - help: "Number of not found responses for latest vaa of a price feed request" - }); - private apiLatestVaaSuccessResponseCounter = new client.Counter({ - name: `${SERVICE_PREFIX}api_latest_vaa_success_response`, - help: "Number of successful responses for latest vaa of a price feed request" - }); - private apiLatestVaaFreshnessHistogram = new client.Histogram({ - name: `${SERVICE_PREFIX}api_latest_vaa_freshness`, + private apiRequestsPriceFreshnessHistogram = new client.Histogram({ + name: `${SERVICE_PREFIX}api_requests_price_freshness_seconds`, help: "Freshness time of Vaa (time difference of Vaa and request time)", - buckets: [1, 5, 10, 15, 30, 60, 120, 180] + buckets: [1, 5, 10, 15, 30, 60, 120, 180], + labelNames: ["path", "price_id"] }); - private apiLatestPriceFeedRequestsCounter = new client.Counter({ - name: `${SERVICE_PREFIX}api_latest_price_feed_requests_received`, - help: "Number of requests for latest Price Feed of a price feed id" - }); - private apiLatestPriceFeedNotFoundResponseCounter = new client.Counter({ - name: `${SERVICE_PREFIX}api_latest_price_feed_not_found_response`, - help: "Number of not found responses for latest Price Feed of a price feed id" - }); - private apiLatestPriceFeedSuccessResponseCounter = new client.Counter({ - name: `${SERVICE_PREFIX}api_latest_price_feed_success_response`, - help: "Number of successful responses for latest vaa of a price feed id" - }); - private apiLatestPriceFeedFreshnessHistogram = new client.Histogram({ - name: `${SERVICE_PREFIX}api_latest_price_feed_freshness`, - help: "Freshness time of Vaa (time difference of retrieval time and request time)", - buckets: [1, 5, 10, 15, 30, 60, 120, 180] - }); - // End metrics private server = http.createServer(async (req, res) => { @@ -72,16 +49,8 @@ export class PromClient { this.collectDefaultMetrics({ register: this.register, prefix: SERVICE_PREFIX }); // Register each metric this.register.registerMetric(this.receivedVaaCounter); - - this.register.registerMetric(this.apiLatestVaaRequestsCounter); - this.register.registerMetric(this.apiLatestVaaNotFoundResponseCounter); - this.register.registerMetric(this.apiLatestVaaSuccessResponseCounter); - this.register.registerMetric(this.apiLatestVaaFreshnessHistogram); - - this.register.registerMetric(this.apiLatestPriceFeedRequestsCounter); - this.register.registerMetric(this.apiLatestPriceFeedNotFoundResponseCounter); - this.register.registerMetric(this.apiLatestPriceFeedSuccessResponseCounter); - this.register.registerMetric(this.apiLatestPriceFeedFreshnessHistogram); + this.register.registerMetric(this.apiResponseTimeSummary) + this.register.registerMetric(this.apiRequestsPriceFreshnessHistogram); // End registering metric logger.info("prometheus client listening on port " + config.port); @@ -92,35 +61,17 @@ export class PromClient { this.receivedVaaCounter.inc(); } - incApiLatestVaaRequests() { - this.apiLatestVaaRequestsCounter.inc(); + addResponseTime(path: string, status: number, duration: DurationInMs) { + this.apiResponseTimeSummary.observe({ + path: path, + status: status + }, duration); } - incApiLatestVaaNotFoundResponse() { - this.apiLatestVaaNotFoundResponseCounter.inc(); - } - - incApiLatestVaaSuccessResponse() { - this.apiLatestVaaSuccessResponseCounter.inc(); - } - - addApiLatestVaaFreshness(duration: DurationInSec) { - this.apiLatestVaaFreshnessHistogram.observe(duration); - } - - incApiLatestPriceFeedRequests() { - this.apiLatestPriceFeedRequestsCounter.inc(); - } - - incApiLatestPriceFeedNotFoundResponse() { - this.apiLatestPriceFeedNotFoundResponseCounter.inc(); - } - - incApiLatestPriceFeedSuccessResponse() { - this.apiLatestPriceFeedSuccessResponseCounter.inc(); - } - - addApiLatestPriceFeedFreshness(duration: DurationInSec) { - this.apiLatestPriceFeedFreshnessHistogram.observe(duration); + addApiRequestsPriceFreshness(path: string, priceId: string, duration: DurationInSec) { + this.apiRequestsPriceFreshnessHistogram.observe({ + path: path, + price_id: priceId, + }, duration); } } diff --git a/third_party/pyth/price-service/src/rest.ts b/third_party/pyth/price-service/src/rest.ts index 1142ea3f..c1ac4af5 100644 --- a/third_party/pyth/price-service/src/rest.ts +++ b/third_party/pyth/price-service/src/rest.ts @@ -1,11 +1,17 @@ import express from "express"; import cors from "cors"; -import { Request, Response } from "express"; +import morgan from "morgan"; +import responseTime from "response-time"; +import { Request, Response, NextFunction } from "express"; import { PriceFeedPriceInfo } from "./listen"; import { logger } from "./logging"; import { PromClient } from "./promClient"; -import { DurationInSec } from "./helpers"; +import { DurationInMs, DurationInSec } from "./helpers"; import { StatusCodes } from "http-status-codes"; +import { validate, ValidationError, Joi, schema } from "express-validation"; + +const MORGAN_LOG_FORMAT = ':remote-addr - :remote-user ":method :url HTTP/:http-version"' + + ' :status :res[content-length] :response-time ms ":referrer" ":user-agent"'; export class RestAPI { private port: number; @@ -13,7 +19,7 @@ export class RestAPI { private isReady: (() => boolean) | undefined; private promClient: PromClient | undefined; - constructor(config: { port: number; }, + constructor(config: { port: number; }, priceFeedVaaInfo: PriceFeedPriceInfo, isReady?: () => boolean, promClient?: PromClient) { @@ -28,82 +34,75 @@ export class RestAPI { const app = express(); app.use(cors()); + const winstonStream = { + write: (text: string) => { + logger.info(text); + } + }; + + app.use(morgan(MORGAN_LOG_FORMAT, { stream: winstonStream })); + + app.use(responseTime((req: Request, res: Response, time: DurationInMs) => { + if (res.statusCode !== StatusCodes.NOT_FOUND) { + this.promClient?.addResponseTime(req.path, res.statusCode, time); + } + })) + app.listen(this.port, () => logger.debug("listening on REST port " + this.port) ); let endpoints: string[] = []; + + const latestVaaBytesInputSchema: schema = { + query: Joi.object({ + id: Joi.string().regex(/^[a-f0-9]{64}$/) + }) + } + app.get("/latest_vaa_bytes", validate(latestVaaBytesInputSchema), (req: Request, res: Response) => { + let priceId = req.query.id as string; - app.get("/latest_vaa_bytes/:price_feed_id", (req: Request, res: Response) => { - this.promClient?.incApiLatestVaaRequests(); - logger.info(`Received latest_vaa_bytes request for ${req.params.price_feed_id}`) - - let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(req.params.price_feed_id); + let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(priceId); if (latestPriceInfo === undefined) { - this.promClient?.incApiLatestVaaNotFoundResponse(); - res.sendStatus(StatusCodes.NOT_FOUND); + res.sendStatus(StatusCodes.BAD_REQUEST); return; } - this.promClient?.incApiLatestVaaSuccessResponse(); - - const freshness: DurationInSec = (new Date).getTime()/1000 - latestPriceInfo.receiveTime; - this.promClient?.addApiLatestVaaFreshness(freshness); + const freshness: DurationInSec = (new Date).getTime() / 1000 - latestPriceInfo.receiveTime; + this.promClient?.addApiRequestsPriceFreshness(req.path, priceId, freshness); res.send(latestPriceInfo.vaaBytes); }); - endpoints.push("latest_vaa_bytes/"); + endpoints.push("latest_vaa_bytes?id="); - // It will be called with query param `id` such as: `/latest_price_feed?id=xyz&id=abc - app.get("/latest_price_feed", (req: Request, res: Response) => { - this.promClient?.incApiLatestPriceFeedRequests(); - logger.info(`Received latest_price_feed request for query: ${req.query}`); + const latestPriceFeedInputSchema: schema = { + query: Joi.object({ + id: Joi.array().items(Joi.string().regex(/^[a-f0-9]{64}$/)) + }) + } + app.get("/latest_price_feed", validate(latestPriceFeedInputSchema), (req: Request, res: Response) => { + let priceIds = req.query.id as string[]; - if (req.query.id === undefined) { - res.status(StatusCodes.BAD_REQUEST).send("No id is provided"); - return; - } - - let priceIds: string[] = []; - if (typeof(req.query.id) === "string") { - priceIds.push(req.query.id); - } else if (Array.isArray(req.query.id)) { - for (let entry of req.query.id) { - if (typeof(entry) === "string") { - priceIds.push(entry); - } else { - res.status(StatusCodes.BAD_REQUEST).send("id is expected to be a hex string or an array of hex strings"); - return; - } - } - } else { - res.status(StatusCodes.BAD_REQUEST).send("id is expected to be a hex string or an array of hex strings"); - return; - } - - let responseJson = [] + let responseJson = []; for (let id of priceIds) { let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id); if (latestPriceInfo === undefined) { - this.promClient?.incApiLatestPriceFeedNotFoundResponse(); - res.status(StatusCodes.NOT_FOUND).send(`Price Feed with id ${id} not found`); + res.status(StatusCodes.BAD_REQUEST).send(`Price Feed with id ${id} not found`); return; } - - const freshness: DurationInSec = (new Date).getTime()/1000 - latestPriceInfo.receiveTime; - this.promClient?.addApiLatestPriceFeedFreshness(freshness); - + + const freshness: DurationInSec = (new Date).getTime() / 1000 - latestPriceInfo.receiveTime; + this.promClient?.addApiRequestsPriceFreshness(req.path, id, freshness); + responseJson.push(latestPriceInfo.priceFeed.toJson()); } - this.promClient?.incApiLatestPriceFeedSuccessResponse(); - res.json(responseJson); }); - endpoints.push("latest_price_feed/"); + endpoints.push("latest_price_feed?id[]=&id[]=&.."); app.get("/ready", (_, res: Response) => { @@ -124,5 +123,13 @@ export class RestAPI { app.get("/", (_, res: Response) => res.json(endpoints) ); + + app.use(function(err: any, _: Request, res: Response, next: NextFunction) { + if (err instanceof ValidationError) { + return res.status(err.statusCode).json(err); + } + + return next(err); + }) } }