From d258c23b8ed238d3418705d9e7d4b4e2ffe4bdfd Mon Sep 17 00:00:00 2001 From: RetricSu Date: Fri, 19 Aug 2022 15:24:26 +0800 Subject: [PATCH] feat: add poly_getHealthStatus api and health-check script --- docs/apis.md | 1 + docs/poly-apis.md | 54 +++++++++++++++++++ packages/api-server/src/db/query.ts | 11 ++++ packages/api-server/src/methods/constant.ts | 3 ++ .../api-server/src/methods/modules/poly.ts | 44 +++++++++++++++ packages/godwoken/src/client.ts | 15 ++++++ scripts/health-check.sh | 13 +++++ 7 files changed, 141 insertions(+) create mode 100644 scripts/health-check.sh diff --git a/docs/apis.md b/docs/apis.md index e3d2236e..0384c742 100644 --- a/docs/apis.md +++ b/docs/apis.md @@ -110,6 +110,7 @@ Get details at [Godwoken Docs](https://github.com/nervosnetwork/godwoken/blob/de - poly_version - poly_getEthTxHashByGwTxHash - poly_getGwTxHashByEthTxHash +- poly_getHealthStatus #### Usage diff --git a/docs/poly-apis.md b/docs/poly-apis.md index a228c2b7..b724b6dc 100644 --- a/docs/poly-apis.md +++ b/docs/poly-apis.md @@ -29,6 +29,7 @@ * [Type `ScriptInfo`](#type-scriptinfo) * [Type `BackendInfo`](#type-backendinfo) * [Type `AccountInfo`](#type-accountinfo) + * [Type `HealthStatus`](#type-healthstatus) ## RPC Methods @@ -409,6 +410,42 @@ Response } ``` +#### Method `poly_getHealthStatus` +* `poly_getHealthStatus()` +* result: [`HealthStatus`](#type-h256) + +Get web3 server health status + +##### Examples + +Request + +```json +{ + "id": 2, + "jsonrpc": "2.0", + "method": "poly_getHealthStatus", + "params": [] +} +``` + +Response + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "status": true, + "pingNode": "pong", + "pingFullNode": "pong", + "pingRedis": "PONG", + "isDBConnected": true, + "syncBlocksDiff": 0 + } +} +``` + ## RPC Types ### Type `Uint32` @@ -671,3 +708,20 @@ Describes the accounts web3 used. * `defaultFrom`: [`AccountInfo`](#type-accountinfo) - Default from account used in `eth_call` and `eth_estimateGas` +### Type `HealthStatus` + +Describes the web3 server health status. + +#### Fields + +* `status`: `boolean` - Health status, should be true + +* `pingNode`: `string` - Godwoken readonly node ping result, should be "pong" + +* `pingFullNode`: `string` - Godwoken fullnode node ping result, should be "pong" + +* `pingRedis`: `string` - Redis server ping result, should be "PONG" + +* `isDBConnected`: `boolean` - Database connection status, should be true + +* `syncBlocksDiff`: `number` - Web3 sync behind godwoken blocks count, eg 2 means sync behind 2 blocks, 0 means sync to the latest diff --git a/packages/api-server/src/db/query.ts b/packages/api-server/src/db/query.ts index 83a93c59..10eceab5 100644 --- a/packages/api-server/src/db/query.ts +++ b/packages/api-server/src/db/query.ts @@ -28,6 +28,7 @@ import { import { LimitExceedError } from "../methods/error"; import { FilterParams } from "../base/filter"; import KnexTimeoutError = knex.KnexTimeoutError; +import { logger } from "../base/logger"; const poolMax = envConfig.pgPoolMax || 20; const GLOBAL_KNEX = Knex({ @@ -46,6 +47,16 @@ export class Query { this.knex = GLOBAL_KNEX; } + async isConnected() { + try { + await this.knex.raw("SELECT 1"); + return true; + } catch (error: any) { + logger.error(error.message); + return false; + } + } + async getEthTxHashByGwTxHash(gwTxHash: Hash): Promise { const ethTxHash = await this.knex("transactions") .select("eth_tx_hash") diff --git a/packages/api-server/src/methods/constant.ts b/packages/api-server/src/methods/constant.ts index 15fd9cef..9c4fa34a 100644 --- a/packages/api-server/src/methods/constant.ts +++ b/packages/api-server/src/methods/constant.ts @@ -38,3 +38,6 @@ export const MAX_TRANSACTION_SIZE = BigInt("131072"); export const MAX_ADDRESS_SIZE_PER_REGISTER_BATCH = 50; export const AUTO_CREATE_ACCOUNT_FROM_ID = "0x0"; + +// if sync behind 3 blocks, something went wrong +export const MAX_ALLOW_SYNC_BLOCKS_DIFF = 3; diff --git a/packages/api-server/src/methods/modules/poly.ts b/packages/api-server/src/methods/modules/poly.ts index 0f75cccc..c66cbb90 100644 --- a/packages/api-server/src/methods/modules/poly.ts +++ b/packages/api-server/src/methods/modules/poly.ts @@ -8,6 +8,7 @@ import { CACHE_EXPIRED_TIME_MILSECS } from "../../cache/constant"; import { Query } from "../../db"; import { ethTxHashToGwTxHash, gwTxHashToEthTxHash } from "../../cache/tx-hash"; import { middleware, validators } from "../validator"; +import { MAX_ALLOW_SYNC_BLOCKS_DIFF } from "../constant"; const { version: web3Version } = require("../../../package.json"); export class Poly { @@ -105,4 +106,47 @@ export class Poly { const gwTxHash = args[0]; return await gwTxHashToEthTxHash(gwTxHash, this.query, this.cacheStore); } + + async getHealthStatus(_args: []) { + const [pingNode, pingFullNode, pingRedis, isDBConnected, syncBlocksDiff] = + await Promise.all([ + this.rpc.ping(), + this.rpc.pingFullNode(), + this.cacheStore.client.PING(), + this.query.isConnected(), + this.syncBlocksDiff(), + ]); + + const status = + pingNode === "pong" && + pingFullNode === "pong" && + pingRedis === "PONG" && + isDBConnected && + syncBlocksDiff <= MAX_ALLOW_SYNC_BLOCKS_DIFF; + + return { + status, + pingNode, + pingFullNode, + pingRedis, + isDBConnected, + syncBlocksDiff, + }; + } + + // get block data sync status + private async syncBlocksDiff(): Promise { + const blockNum = await this.query.getTipBlockNumber(); + if (blockNum == null) { + throw new Error("db tipBlockNumber is null"); + } + const dbTipBlockNumber = +blockNum.toString(); + + const tipBlockHash = await this.rpc.getTipBlockHash(); + const tipBlock = await this.rpc.getBlock(tipBlockHash); + const gwTipBlockNumber = +tipBlock.block.raw.number; + + const diff = gwTipBlockNumber - dbTipBlockNumber; + return diff; + } } diff --git a/packages/godwoken/src/client.ts b/packages/godwoken/src/client.ts index 806fb8db..c32a4abf 100644 --- a/packages/godwoken/src/client.ts +++ b/packages/godwoken/src/client.ts @@ -199,6 +199,21 @@ export class GodwokenClient { return await this.rpcCall("get_mem_pool_state_root"); } + public async ping(): Promise { + const result = await this.rpcCall("ping"); + return result; + } + + public async pingFullNode(): Promise { + const result = await this.writeRpcCall("ping"); + return result; + } + + public async getBlock(blockHash: HexString): Promise { + const result = await this.rpcCall("get_block", blockHash); + return result; + } + private async rpcCall(methodName: string, ...args: any[]): Promise { const name = "gw_" + methodName; try { diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100644 index 00000000..fd590a67 --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Query whether web3 api is ready to serve +echo '{ + "id": 42, + "jsonrpc": "2.0", + "method": "poly_getHealthStatus", + "params": [] +}' \ +| tr -d '\n' \ +| curl --silent -H 'content-type: application/json' -d @- \ +http://127.0.0.1:8024 \ +| jq '.result.status' | egrep "true" || exit 1