diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml new file mode 100644 index 0000000..217a012 --- /dev/null +++ b/.github/workflows/cd.yaml @@ -0,0 +1,62 @@ +name: deploy +run-name: Deploy artifacts + +on: + # Only run this workflow automatically after ci workflow on main branch. + workflow_run: + workflows: [ci] + types: [completed] + branches: [main, develop] + +jobs: + deploy-to-mainnet: + runs-on: sg + if: github.event.workflow_run.conclusion == 'success' && github.ref_name == 'main' + environment: mainnet + strategy: + matrix: + target: [SERVER_1, SERVER_2] + steps: + - name: Download Artifact + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ci + workflow_conclusion: success + run_id: ${{ github.event.workflow_run.id }} + run_number: ${{ github.event.workflow_run.run_number }} + name: thq-${{ github.event.workflow_run.head_commit.id }}.zip + + - name: Unzip artifact + run: rm -rf ./dist && unzip -q thq-${{ github.event.workflow_run.head_commit.id }}.zip -d ./dist + + - name: Deploy to server contract + run: ls -la ./dist && rsync -a ./dist/ ${{ vars[matrix.target] }}:/mnt/ckb/ckb-time-generator + + - name: Restart pm2 on server contract + run: ssh ${{ vars[matrix.target] }} 'source ~/.zshrc && cd /mnt/ckb/ckb-time-generator && npm install --omit=dev && npm run reload_mainnet' + + deploy-to-testnet: + runs-on: sg + if: github.event.workflow_run.conclusion == 'success' && github.ref_name == 'develop' + environment: testnet + strategy: + matrix: + target: [SERVER_1, SERVER_2] + steps: + - name: Download Artifact + uses: dawidd6/action-download-artifact@v2 + with: + workflow: ci + workflow_conclusion: success + run_id: ${{ github.event.workflow_run.id }} + run_number: ${{ github.event.workflow_run.run_number }} + name: thq-${{ github.event.workflow_run.head_commit.id }}.zip + + - name: Unzip artifact + run: rm -rf ./dist && unzip -q thq-${{ github.event.workflow_run.head_commit.id }}.zip -d ./dist + + - name: Deploy to server contract + run: ls -la ./dist && rsync -a ./dist/ ${{ vars[matrix.target] }}:/mnt/ckb/ckb-time-generator + + - name: Restart pm2 on server contract + run: ssh ${{ vars[matrix.target] }} 'source ~/.zshrc && cd /mnt/ckb/ckb-time-generator && npm install --omit=dev && npm run reload_mainnet' diff --git a/.github/workflows/deploy-to-mainnet.yaml b/.github/workflows/deploy-to-mainnet.yaml deleted file mode 100644 index f96134e..0000000 --- a/.github/workflows/deploy-to-mainnet.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: deploy-to-mainnet -run-name: Deploy artifacts to mainnet environment - -on: - # Only run this workflow automatically after ci workflow on main branch. - workflow_run: - workflows: [ci] - types: [completed] - branches: [main] - -jobs: - deploy-and-restart: - runs-on: sg - environment: mainnet - strategy: - matrix: - target: [SERVER_1, SERVER_2] - if: github.event.workflow_run.conclusion == 'success' - steps: - - name: Download Artifact - uses: dawidd6/action-download-artifact@v2 - with: - workflow: ci - workflow_conclusion: success - run_id: ${{ github.event.workflow_run.id }} - run_number: ${{ github.event.workflow_run.run_number }} - name: thq-${{ github.event.workflow_run.head_commit.id }}.zip - - - name: Unzip artifact - run: rm -rf ./dist && unzip -q thq-${{ github.event.workflow_run.head_commit.id }}.zip -d ./dist - - - name: Deploy to server contract - run: ls -la ./dist && rsync -a ./dist/ ${{ vars[matrix.target] }}:/mnt/ckb/ckb-time-generator - - - name: Restart pm2 on server contract - run: ssh ${{ vars[matrix.target] }} 'source ~/.zshrc && cd /mnt/ckb/ckb-time-generator && npm install --omit=dev && npm run reload_mainnet' diff --git a/.github/workflows/deploy-to-testnet.yaml b/.github/workflows/deploy-to-testnet.yaml deleted file mode 100644 index ec27e1d..0000000 --- a/.github/workflows/deploy-to-testnet.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: deploy-to-testnet -run-name: Deploy artifacts to testnet environment - -on: - # Only run this workflow automatically after ci workflow on main branch. - workflow_run: - workflows: [ci] - types: [completed] - branches: [develop] - -jobs: - deploy-and-restart: - runs-on: sg - environment: testnet - strategy: - matrix: - target: [SERVER_1, SERVER_2] - if: github.event.workflow_run.conclusion == 'success' - steps: - - name: Download Artifact - uses: dawidd6/action-download-artifact@v2 - with: - workflow: ci - workflow_conclusion: success - run_id: ${{ github.event.workflow_run.id }} - run_number: ${{ github.event.workflow_run.run_number }} - name: thq-${{ github.event.workflow_run.head_commit.id }}.zip - - - name: Unzip artifact - run: rm -rf ./dist && unzip -q thq-${{ github.event.workflow_run.head_commit.id }}.zip -d ./dist - - - name: Deploy to server contract - run: ls -la ./dist && rsync -a ./dist/ ${{ vars[matrix.target] }}:/mnt/ckb/ckb-time-generator - - - name: Restart pm2 on server contract - run: ssh ${{ vars[matrix.target] }} 'source ~/.zshrc && cd /mnt/ckb/ckb-time-generator && npm install --omit=dev && npm run reload_testnet' diff --git a/README.md b/README.md index f662f05..88343b4 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,18 @@ # ckb-time-generator +[![ci](https://github.com/dotbitHQ/ckb-time-generator/actions/workflows/ci.yaml/badge.svg)](https://github.com/dotbitHQ/ckb-time-generator/actions/workflows/ci.yaml) + The generator of [ckb-time-scripts](https://github.com/DeAccountSystems/ckb-time-script). ## How to Work -- Clone and install every dependencies. -- Copy `config/default.ts` to `config/local.ts`, edit configs as needed. -- Build with `npm run build`, each time you update `config/local.ts` it is needed to rebuild. -- Run with `npm run reload_testnet` or `npm run reload_production` base on environment. +- Clone and install every dependencies with [npm](https://www.npmjs.com/). +- Install [pm2](https://pm2.keymetrics.io/docs/usage/quick-start/) globally. +- Copy `config/{env}.yaml` to `config/local-{env}.yaml`, edit configs as needed. +- Run with `npm run reload_testnet` or `npm run reload_mainnet` base on environment. + +> If it is needed to kown more details of the config file loading order, click this link [node-config](https://github.com/node-config/node-config/wiki/Configuration-Files#file-load-order) . ## Tool Commands @@ -24,21 +28,16 @@ Same as `timestamp` other option of `-t` can be `blocknumber` and `quote`, for m ## Development -First, you need to create `config/local.ts`, input private keys like below: +First, you need to create `config/local-{env}.yaml`, input private keys like below: ```typescript -export default { - loglevel: 'debug', - timestamp: { - PayersPrivateKey: '0x000000000...', - }, - blocknumber: { - PayersPrivateKey: '0x000000000...', - }, - quote: { - PayersPrivateKey: '0x000000000...', - } -} +loglevel: 'debug' +timestamp: + PayersPrivateKey: '0x000000000...' +blocknumber: + PayersPrivateKey: '0x000000000...' +quote: + PayersPrivateKey: '0x000000000...' ``` Then, you will be able to run commands in testnet environment like below: diff --git a/ecosystem.config.js b/ecosystem.config.js index a72d582..8d24652 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -1,7 +1,7 @@ const packageJson = require('./package.json') function generateApp (name, entry, args, env) { - const isProd = env === 'production' + const isProd = env === 'mainnet' const appName = `${packageJson.name}_${name}` return { diff --git a/src/controller/update.ts b/src/controller/update.ts index ec9e093..1491206 100644 --- a/src/controller/update.ts +++ b/src/controller/update.ts @@ -260,10 +260,11 @@ class Server extends EventEmitter { protected ws: WebSocket protected heartbeatStatus: { id: number, timer: any, history: any[] } - protected eventStatus: { timer: any, history: any[] } + protected eventStatus: { timer_notify: any, timer_warn: any, history: any[] } protected txStatus: { txHash: string, waitedBlocks: number } - protected eventTimeoutLimit = TIME_1_M * 5 + protected newBlockNotifyLimit = TIME_1_M * 3 + protected newBlockWarnLimit = TIME_1_M * 9 constructor (url: string, logger: Logger) { super() @@ -283,7 +284,7 @@ class Server extends EventEmitter { this.ws = ws this.heartbeatStatus = { id: 1, timer: null, history: [] } - this.eventStatus = { timer: null, history: [] } + this.eventStatus = { timer_notify: null, timer_warn: null, history: [] } this.txStatus = { txHash: '', waitedBlocks: 0 } } @@ -330,16 +331,28 @@ class Server extends EventEmitter { this.logger.debug(`Received new block[${BigInt(result.number)}]`) const status = this.eventStatus - clearTimeout(status.timer) + clearTimeout(status.timer_notify) + clearTimeout(status.timer_warn) this.emit('update', result) - status.timer = setTimeout(async () => { - this.logger.error(`There is no new block for ${this.eventTimeoutLimit / 1000} seconds.`) - // This error reports only once if no message received, so DO NOT use notifyWithThrottle to report. - // The key point here is eventTimeoutLimit, enlarge it is the only way - await notifyLark(`There is no new block for ${this.eventTimeoutLimit / 1000} seconds.`, 'Check if CKB node is offline and the get_tip_header interface is reachable.') - }, this.eventTimeoutLimit) + // This error reports only once if no message received, so DO NOT use notifyWithThrottle to report. + // The key point here is eventTimeoutLimit, enlarge it is the only way + status.timer_notify = setTimeout(async () => { + await notifyLark( + `There is no new block for ${this.newBlockNotifyLimit / 1000} seconds.`, + `This problem occasionally occurs and can be safely ignored, a formal warning will be triggered afer ${this.newBlockWarnLimit / 1000} seconds.`, + false, + ) + }, this.newBlockNotifyLimit) + + status.timer_warn = setTimeout(async () => { + await notifyLark( + `There is no new block for ${this.newBlockWarnLimit / 1000} seconds.`, + 'Check if CKB node is offline and the get_tip_header interface is reachable.', + true, + ) + }, this.newBlockWarnLimit) } } @@ -347,7 +360,7 @@ class Server extends EventEmitter { this.logger.warn('Connection closed, will retry connecting later.', { code, reason }) setTimeout(() => { - clearTimeout(this.eventStatus.timer) + clearTimeout(this.eventStatus.timer_notify) clearTimeout(this.heartbeatStatus.timer) this.connect() }, TIME_30_S) diff --git a/src/utils/helper.ts b/src/utils/helper.ts index cab377d..34c4538 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -168,7 +168,7 @@ export async function notifyWithThrottle(source: string, duration: number, msg: await notifyLark(msg, how_to_fix) } -export async function notifyLark(msg: string, how_to_fix = '') { +export async function notifyLark(msg: string, how_to_fix = '', should_at = true) { try { const content: any[] = [ [{tag: 'text', un_escaped: true, text: `server: ${getCurrentServer()}`}], @@ -176,28 +176,30 @@ export async function notifyLark(msg: string, how_to_fix = '') { [{tag: 'text', un_escaped: true, text: `reason: ${msg}`}], [{tag: 'text', un_escaped: true, text: `how to fix: ${how_to_fix}`}], ] - if (process.env.NODE_ENV === 'production') { + if (process.env.NODE_ENV === 'mainnet' && should_at) { content.push([{tag: 'at', user_id: 'all'}]) - } - - const res = await fetch(`https://open.larksuite.com/open-apis/bot/v2/hook/${config.LARK_API_KEY}`, { - method: 'post', - body: JSON.stringify({ - email: 'xieaolin@gmail.com', - msg_type: 'post', - content: { - post: { - zh_cn: { - title: `=== THQ Node 服务告警 (${process.env.NODE_ENV}) ===`, - content, - } - }, - } + const res = await fetch(`https://open.larksuite.com/open-apis/bot/v2/hook/${config.LARK_API_KEY}`, { + method: 'post', + body: JSON.stringify({ + email: 'xieaolin@gmail.com', + msg_type: 'post', + content: { + post: { + zh_cn: { + title: `=== THQ Node 服务告警 (${process.env.NODE_ENV}) ===`, + content, + } + }, + } + }) }) - }) - if (res.status >= 400) { - console.error(`helper: send Lark notify failed, response ${res.status} ${res.statusText}`) + if (res.status >= 400) { + console.error(`helper: send Lark notify failed, response ${res.status} ${res.statusText}`) + } + } else { + const logger = rootLogger.child({ command: 'update', cell_type: 'unknown' }) + logger.warn(`msg: ${msg}, how_to_fix: ${how_to_fix}`) } } catch (e) { console.error('helper: send Lark notify failed:', e)