diff --git a/bots/.envrc_sample b/bots/.envrc_sample index 41a2a02c..3eeef656 100644 --- a/bots/.envrc_sample +++ b/bots/.envrc_sample @@ -12,9 +12,12 @@ export USE_LOG_FILE=false export EXECUTOR_PORT=5000 export BATCH_PORT=5001 +# slack notification +export SLACK_WEB_HOOK= + # l2 setup (need challenger, output, executor mnemonic) export SUBMISSION_INTERVAL=10000 -export FINALIZED_TIME=10000 +export FINALIZATION_PERIOD=10000 export IBC_METADATA='channel-1' export L1_LCD_URI=http://localhost:1317 @@ -33,4 +36,5 @@ export BATCH_SUBMITTER_MNEMONIC='' export CHALLENGER_MNEMONIC='' # output submitter config -export OUTPUT_SUBMITTER_MNEMONIC='' \ No newline at end of file +export OUTPUT_SUBMITTER_MNEMONIC='' +export DELETE_OUTPUT_PROPOSAL='false' \ No newline at end of file diff --git a/bots/README.md b/bots/README.md index d765891f..2696b869 100644 --- a/bots/README.md +++ b/bots/README.md @@ -42,6 +42,8 @@ const msg = new MsgCreateBridge(executor.key.accAddress, bridgeConfig); | BATCH_SUBMITTER_MNEMONIC | Mnemonic seed for submitter | '' | | OUTPUT_SUBMITTER_MNEMONIC | Mnemonic seed for output submitter | '' | | CHALLENGER_MNEMONIC | Mnemonic seed for challenger | '' | +| SLACK_WEB_HOOK | Slack web hook for notification | '' | + > In OPinit bots, we use [direnv](https://direnv.net) for managing environment variable for development. See [sample of .envrc](.envrc_sample). diff --git a/bots/package-lock.json b/bots/package-lock.json index 5777d1f6..b6d152c0 100644 --- a/bots/package-lock.json +++ b/bots/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@initia/builder.js": "^0.0.9", - "@initia/initia.js": "^0.1.19", + "@initia/initia.js": "^0.1.22", "@koa/cors": "^4.0.0", "@sentry/node": "^7.60.0", "@types/bluebird": "^3.5.38", @@ -1130,11 +1130,11 @@ } }, "node_modules/@initia/initia.js": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@initia/initia.js/-/initia.js-0.1.19.tgz", - "integrity": "sha512-OKehNvBE4hbeM4HbqOEucb4IHWSOg8S9GnvcmItHqIhqKcDiX6ZkzthqdZz2q9E1xkiUHUckEbwkcZC/gf473g==", + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@initia/initia.js/-/initia.js-0.1.22.tgz", + "integrity": "sha512-YUYBGM0l8e88a4ZkqbZsSj8zvFhrIVTRgc7W0S8YKSF+s4obpmEAyvyS3TyU1hlPWsQYzxEBo2SqoScdnUhZvw==", "dependencies": { - "@initia/initia.proto": "^0.1.18", + "@initia/initia.proto": "^0.1.20", "@initia/opinit.proto": "^0.0.1", "@ledgerhq/hw-transport": "^6.27.12", "@ledgerhq/hw-transport-webhid": "^6.27.12", @@ -1146,6 +1146,7 @@ "bip32": "^2.0.6", "bip39": "^3.0.4", "jscrypto": "^1.0.3", + "keccak256": "^1.0.6", "long": "^5.2.0", "ripemd160": "^2.0.2", "secp256k1": "^4.0.3", @@ -1165,9 +1166,9 @@ } }, "node_modules/@initia/initia.proto": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@initia/initia.proto/-/initia.proto-0.1.18.tgz", - "integrity": "sha512-98hSjstgjjzfasHoGIixWP0DiC1kNONyfpNCZxQ21DJAmK6wn1L/Ae51tF5N79J4UAstwTTXkmKrPDRGla9fJA==", + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/@initia/initia.proto/-/initia.proto-0.1.20.tgz", + "integrity": "sha512-J57d5o1ikF37D2T3TEsPU+pMe1c7U+qD8nASPUfa33Fv1V/AAJV1j2OnM/QbQChxlqDaOsEmxmvlo7+1TRU38w==", "dependencies": { "@improbable-eng/grpc-web": "^0.15.0", "google-protobuf": "^3.21.0", @@ -10190,6 +10191,40 @@ "node": ">=0.6.0" } }, + "node_modules/keccak": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keccak/node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node_modules/keccak256": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz", + "integrity": "sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==", + "dependencies": { + "bn.js": "^5.2.0", + "buffer": "^6.0.3", + "keccak": "^3.0.2" + } + }, + "node_modules/keccak256/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + }, "node_modules/keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", @@ -17875,11 +17910,11 @@ } }, "@initia/initia.js": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@initia/initia.js/-/initia.js-0.1.19.tgz", - "integrity": "sha512-OKehNvBE4hbeM4HbqOEucb4IHWSOg8S9GnvcmItHqIhqKcDiX6ZkzthqdZz2q9E1xkiUHUckEbwkcZC/gf473g==", + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@initia/initia.js/-/initia.js-0.1.22.tgz", + "integrity": "sha512-YUYBGM0l8e88a4ZkqbZsSj8zvFhrIVTRgc7W0S8YKSF+s4obpmEAyvyS3TyU1hlPWsQYzxEBo2SqoScdnUhZvw==", "requires": { - "@initia/initia.proto": "^0.1.18", + "@initia/initia.proto": "^0.1.20", "@initia/opinit.proto": "^0.0.1", "@ledgerhq/hw-transport": "^6.27.12", "@ledgerhq/hw-transport-webhid": "^6.27.12", @@ -17891,6 +17926,7 @@ "bip32": "^2.0.6", "bip39": "^3.0.4", "jscrypto": "^1.0.3", + "keccak256": "^1.0.6", "long": "^5.2.0", "ripemd160": "^2.0.2", "secp256k1": "^4.0.3", @@ -17909,9 +17945,9 @@ } }, "@initia/initia.proto": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@initia/initia.proto/-/initia.proto-0.1.18.tgz", - "integrity": "sha512-98hSjstgjjzfasHoGIixWP0DiC1kNONyfpNCZxQ21DJAmK6wn1L/Ae51tF5N79J4UAstwTTXkmKrPDRGla9fJA==", + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/@initia/initia.proto/-/initia.proto-0.1.20.tgz", + "integrity": "sha512-J57d5o1ikF37D2T3TEsPU+pMe1c7U+qD8nASPUfa33Fv1V/AAJV1j2OnM/QbQChxlqDaOsEmxmvlo7+1TRU38w==", "requires": { "@improbable-eng/grpc-web": "^0.15.0", "google-protobuf": "^3.21.0", @@ -24915,6 +24951,40 @@ "verror": "1.10.0" } }, + "keccak": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", + "requires": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + } + } + }, + "keccak256": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/keccak256/-/keccak256-1.0.6.tgz", + "integrity": "sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==", + "requires": { + "bn.js": "^5.2.0", + "buffer": "^6.0.3", + "keccak": "^3.0.2" + }, + "dependencies": { + "bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" + } + } + }, "keygrip": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", diff --git a/bots/package.json b/bots/package.json index 634ce1ec..ba71c7ea 100644 --- a/bots/package.json +++ b/bots/package.json @@ -68,7 +68,7 @@ }, "dependencies": { "@initia/builder.js": "^0.0.9", - "@initia/initia.js": "^0.1.19", + "@initia/initia.js": "^0.1.22", "@koa/cors": "^4.0.0", "@sentry/node": "^7.60.0", "@types/bluebird": "^3.5.38", diff --git a/bots/src/lib/query.ts b/bots/src/lib/query.ts index 6127358d..0298a8a9 100644 --- a/bots/src/lib/query.ts +++ b/bots/src/lib/query.ts @@ -21,11 +21,15 @@ const config = getConfig(); export async function getLastOutputInfo( bridgeId: number ): Promise { - const [outputInfos, pagination] = await config.l1lcd.ophost.outputInfos( - bridgeId + const [outputInfos, _pagination] = await config.l1lcd.ophost.outputInfos( + bridgeId, + { + 'pagination.limit': '1', + 'pagination.reverse': 'true' + } ); if (outputInfos.length === 0) return null; - return await config.l1lcd.ophost.outputInfo(bridgeId, pagination.total); + return outputInfos[0]; } // get the output by index from L1 chain @@ -40,15 +44,6 @@ export async function getBridgeInfo(bridgeId: number): Promise { return await config.l1lcd.ophost.bridgeInfo(bridgeId); } -export async function getBalanceByDenom( - lcd: LCDClient, - account: string, - denom: string -): Promise { - const [coins, _pagination] = await lcd.bank.balance(account); - return coins.get(denom); -} - export async function getTokenPairByL1Denom(denom: string): Promise { return await config.l1lcd.ophost.tokenPairByL1Denom(config.BRIDGE_ID, denom); } diff --git a/bots/src/lib/slack.ts b/bots/src/lib/slack.ts index 2d7be80c..83994d9d 100644 --- a/bots/src/lib/slack.ts +++ b/bots/src/lib/slack.ts @@ -5,11 +5,25 @@ import { getConfig } from 'config'; import * as http from 'http'; import * as https from 'https'; import FailedTxEntity from 'orm/executor/FailedTxEntity'; +import { ChallengedOutputEntity } from 'orm/index'; const config = getConfig(); const { SLACK_WEB_HOOK } = process.env; +const ax = axios.create({ + httpAgent: new http.Agent({ keepAlive: true }), + httpsAgent: new https.Agent({ keepAlive: true }), + timeout: 15000 +}); + +export async function notifySlack(text: { text: string }) { + if (SLACK_WEB_HOOK == undefined || SLACK_WEB_HOOK == '') return; + await ax.post(SLACK_WEB_HOOK, text).catch(() => { + console.error('Slack Notification Error'); + }); +} + export function buildNotEnoughBalanceNotification( wallet: Wallet, balance: number, @@ -57,15 +71,19 @@ export function buildFailedTxNotification(data: FailedTxEntity): { }; } -const ax = axios.create({ - httpAgent: new http.Agent({ keepAlive: true }), - httpsAgent: new https.Agent({ keepAlive: true }), - timeout: 15000 -}); +export function buildChallengerNotification( + challengedOutput: ChallengedOutputEntity +): { text: string } { + let notification = '```'; + notification += `[WARN] Challenger Notification\n`; + notification += `\n`; + notification += `Bridge ID : ${challengedOutput.bridgeId}\n`; + notification += `OutputIndex : ${challengedOutput.outputIndex}\n`; + notification += `Reason : ${challengedOutput.reason}\n`; + notification += '```'; + const text = `${notification}`; -export async function notifySlack(text: { text: string }) { - if (SLACK_WEB_HOOK == undefined || SLACK_WEB_HOOK == '') return; - await ax.post(SLACK_WEB_HOOK, text).catch(() => { - console.error('Slack Notification Error'); - }); + return { + text + }; } diff --git a/bots/src/lib/storage.ts b/bots/src/lib/storage.ts index 11f81cc4..0207e9cf 100644 --- a/bots/src/lib/storage.ts +++ b/bots/src/lib/storage.ts @@ -25,8 +25,8 @@ export class WithdrawStorage { Buffer.concat([ bridge_id_buf, sequence_buf, - Buffer.from(AccAddress.toHex(tx.sender).replace('0x', ''), 'hex'), - Buffer.from(AccAddress.toHex(tx.receiver).replace('0x', ''), 'hex'), + AccAddress.toBuffer(tx.sender), + AccAddress.toBuffer(tx.receiver), Buffer.from(tx.l1_denom, 'utf8'), amount_buf ]) @@ -56,8 +56,8 @@ export class WithdrawStorage { Buffer.concat([ bridge_id_buf, sequence_buf, - Buffer.from(AccAddress.toHex(tx.sender).replace('0x', ''), 'hex'), - Buffer.from(AccAddress.toHex(tx.receiver).replace('0x', ''), 'hex'), + AccAddress.toBuffer(tx.sender), + AccAddress.toBuffer(tx.receiver), Buffer.from(tx.l1_denom, 'utf8'), amount_buf ]) @@ -90,8 +90,8 @@ export class WithdrawStorage { Buffer.concat([ bridge_id_buf, sequence_buf, - Buffer.from(AccAddress.toHex(tx.sender).replace('0x', ''), 'hex'), - Buffer.from(AccAddress.toHex(tx.receiver).replace('0x', ''), 'hex'), + AccAddress.toBuffer(tx.sender), + AccAddress.toBuffer(tx.receiver), Buffer.from(tx.l1_denom, 'utf8'), amount_buf ]) diff --git a/bots/src/lib/wallet.ts b/bots/src/lib/wallet.ts index b470fde2..1ba1527e 100644 --- a/bots/src/lib/wallet.ts +++ b/bots/src/lib/wallet.ts @@ -9,7 +9,6 @@ import { } from '@initia/initia.js'; import { sendTx } from './tx'; import { getConfig } from 'config'; -import { getBalanceByDenom } from './query'; import { buildNotEnoughBalanceNotification, notifySlack } from './slack'; const config = getConfig(); @@ -84,13 +83,13 @@ export class TxWallet extends Wallet { async checkEnoughBalance() { const gasPrices = new Coins(this.lcd.config.gasPrices); const denom = gasPrices.denoms()[0]; - const balance = await getBalanceByDenom( - this.lcd, + + const balance = await this.lcd.bank.balanceByDenom( this.key.accAddress, denom ); - if (balance?.amount && parseInt(balance.amount) < 100_000_000) { + if (balance.amount && parseInt(balance.amount) < 100_000_000) { await notifySlack( buildNotEnoughBalanceNotification(this, parseInt(balance.amount), denom) ); diff --git a/bots/src/orm/challenger/ChallengeEntity.ts b/bots/src/orm/challenger/ChallengeEntity.ts new file mode 100644 index 00000000..92b324c7 --- /dev/null +++ b/bots/src/orm/challenger/ChallengeEntity.ts @@ -0,0 +1,13 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity('challenge') +export default class ChallengeEntity { + @PrimaryColumn('text') + name: string; + + @Column('int') + l1DepositSequenceToChallenge: number; + + @Column('int') + l2OutputIndexToChallenge: number; +} diff --git a/bots/src/orm/challenger/DeletedOutputEntity.ts b/bots/src/orm/challenger/DeletedOutputEntity.ts index a62a4648..6d1e7040 100644 --- a/bots/src/orm/challenger/DeletedOutputEntity.ts +++ b/bots/src/orm/challenger/DeletedOutputEntity.ts @@ -1,7 +1,7 @@ import { Column, Entity, Index, PrimaryColumn } from 'typeorm'; @Entity('challenger_deleted_output') -export default class ChallengerDeletedOutputEntity { +export default class ChallengedOutputEntity { @PrimaryColumn('bigint') outputIndex: number; diff --git a/bots/src/orm/index.ts b/bots/src/orm/index.ts index bbccc1ce..cb93bb16 100644 --- a/bots/src/orm/index.ts +++ b/bots/src/orm/index.ts @@ -12,7 +12,8 @@ import ChallengerFinalizeDepositTxEntity from './challenger/FinalizeDepositTxEnt import ChallengerFinalizeWithdrawalTxEntity from './challenger/FinalizeWithdrawalTxEntity'; import ChallengerOutputEntity from './challenger/OutputEntity'; -import ChallengerDeletedOutputEntity from './challenger/DeletedOutputEntity'; +import ChallengedOutputEntity from './challenger/DeletedOutputEntity'; +import ChallengeEntity from './challenger/ChallengeEntity'; export * from './RecordEntity'; export * from './StateEntity'; @@ -23,6 +24,7 @@ export * from './challenger/FinalizeDepositTxEntity'; export * from './challenger/FinalizeWithdrawalTxEntity'; export * from './challenger/OutputEntity'; export * from './challenger/DeletedOutputEntity'; +export * from './challenger/ChallengeEntity'; export * from './executor/OutputEntity'; export * from './executor/DepositTxEntity'; @@ -41,5 +43,6 @@ export { ChallengerOutputEntity, ChallengerFinalizeDepositTxEntity, ChallengerFinalizeWithdrawalTxEntity, - ChallengerDeletedOutputEntity + ChallengedOutputEntity, + ChallengeEntity }; diff --git a/bots/src/scripts/setupL2.ts b/bots/src/scripts/setupL2.ts index 1091f667..543a6a89 100644 --- a/bots/src/scripts/setupL2.ts +++ b/bots/src/scripts/setupL2.ts @@ -5,7 +5,7 @@ import { executor, challenger, outputSubmitter } from 'test/utils/helper'; const config = getConfig(); const SUBMISSION_INTERVAL = parseInt(process.env.SUBMISSION_INTERVAL ?? '3600'); -const FINALIZED_TIME = parseInt(process.env.SUBMISSION_INTERVAL ?? '3600'); +const FINALIZATION_PERIOD = parseInt(process.env.SUBMISSION_INTERVAL ?? '3600'); const IBC_METADATA = process.env.IBC_METADATA ?? ''; // ibc channel name class L2Initializer { @@ -42,7 +42,7 @@ async function main() { try { const initializer = new L2Initializer( SUBMISSION_INTERVAL, - FINALIZED_TIME, + FINALIZATION_PERIOD, IBC_METADATA ); console.log('=========Initializing L2========='); diff --git a/bots/src/test/integration.ts b/bots/src/test/integration.ts index 8938a18b..237daabc 100644 --- a/bots/src/test/integration.ts +++ b/bots/src/test/integration.ts @@ -6,11 +6,11 @@ import { startBatch } from 'worker/batchSubmitter'; import { startExecutor } from 'worker/bridgeExecutor'; import { startOutput } from 'worker/outputSubmitter'; import { delay } from 'bluebird'; -import { getBalanceByDenom, getTokenPairByL1Denom } from 'lib/query'; +import { getTokenPairByL1Denom } from 'lib/query'; const config = Config.getConfig(); const SUBMISSION_INTERVAL = 5; -const FINALIZED_TIME = 5; +const FINALIZATION_PERIOD = 5; const DEPOSIT_AMOUNT = 1_000_000; const DEPOSIT_INTERVAL_MS = 100; @@ -34,8 +34,7 @@ async function startDepositTxBot() { const txBot = new TxBot(config.BRIDGE_ID); const pair = await getTokenPairByL1Denom('uinit'); for (;;) { - const balance = await getBalanceByDenom( - config.l2lcd, + const balance = await config.l2lcd.bank.balanceByDenom( txBot.l2receiver.key.accAddress, pair.l2_denom ); @@ -53,7 +52,7 @@ async function startDepositTxBot() { async function main() { try { - await setupBridge(SUBMISSION_INTERVAL, FINALIZED_TIME); + await setupBridge(SUBMISSION_INTERVAL, FINALIZATION_PERIOD); await startBot(); await startDepositTxBot(); } catch (err) { diff --git a/bots/src/test/utils/Bridge.ts b/bots/src/test/utils/Bridge.ts index 939169f4..b0d378bb 100644 --- a/bots/src/test/utils/Bridge.ts +++ b/bots/src/test/utils/Bridge.ts @@ -23,8 +23,9 @@ import { ChallengerFinalizeWithdrawalTxEntity, ChallengerOutputEntity, ChallengerWithdrawalTxEntity, - ChallengerDeletedOutputEntity, - RecordEntity + ChallengedOutputEntity, + RecordEntity, + ChallengeEntity } from 'orm'; import { executor, challenger, outputSubmitter } from './helper'; import { sendTx } from 'lib/tx'; @@ -65,7 +66,8 @@ class Bridge { await manager.getRepository(ChallengerFinalizeWithdrawalTxEntity).clear(); await manager.getRepository(ChallengerOutputEntity).clear(); await manager.getRepository(ChallengerWithdrawalTxEntity).clear(); - await manager.getRepository(ChallengerDeletedOutputEntity).clear(); + await manager.getRepository(ChallengedOutputEntity).clear(); + await manager.getRepository(ChallengeEntity).clear(); }); await this.batchDB.transaction(async (manager: EntityManager) => { diff --git a/bots/src/worker/challenger/challenger.ts b/bots/src/worker/challenger/challenger.ts index 40cf6f6a..72ce9d88 100644 --- a/bots/src/worker/challenger/challenger.ts +++ b/bots/src/worker/challenger/challenger.ts @@ -1,4 +1,4 @@ -import { BridgeInfo } from '@initia/initia.js'; +import { BridgeInfo, MsgDeleteOutput } from '@initia/initia.js'; import { DataSource, MoreThan } from 'typeorm'; import { getDB } from './db'; import { @@ -6,7 +6,8 @@ import { ChallengerFinalizeDepositTxEntity, ChallengerOutputEntity, ChallengerWithdrawalTxEntity, - ChallengerDeletedOutputEntity + ChallengedOutputEntity, + ChallengeEntity } from 'orm'; import { delay } from 'bluebird'; import { challengerLogger as logger } from 'lib/logger'; @@ -19,8 +20,10 @@ import { } from 'lib/query'; import MonitorHelper from 'worker/bridgeExecutor/MonitorHelper'; import winston from 'winston'; +import { TxWallet, WalletType, getWallet, initWallet } from 'lib/wallet'; const config = getConfig(); +const { DELETE_OUTPUT_PROPOSAL } = process.env; const THRESHOLD_MISS_INTERVAL = 5; export class Challenger { @@ -35,16 +38,21 @@ export class Challenger { missCount: number; // count of miss interval to finalize deposit tx threshold: number; // threshold of miss interval to finalize deposit tx helper: MonitorHelper; + challenger: TxWallet; constructor(public logger: winston.Logger) { [this.db] = getDB(); this.bridgeId = config.BRIDGE_ID; this.isRunning = true; - this.l1DepositSequenceToChallenge = 1; - this.l2OutputIndexToChallenge = 1; this.missCount = 0; this.helper = new MonitorHelper(); + initWallet(WalletType.Challenger, config.l1lcd); + this.challenger = getWallet(WalletType.Challenger); + } + + public name(): string { + return 'challenge'; } async init(): Promise { @@ -52,6 +60,24 @@ export class Challenger { this.submissionIntervalMs = this.bridgeInfo.bridge_config.submission_interval.seconds.toNumber() * 1000; + + const state = await this.db.getRepository(ChallengeEntity).findOne({ + where: { + name: this.name() + } + }); + + if (!state) { + await this.db.getRepository(ChallengeEntity).save({ + name: this.name(), + l1DepositSequenceToChallenge: 1, + l2OutputIndexToChallenge: 1 + }); + } + this.l1DepositSequenceToChallenge = + state?.l1DepositSequenceToChallenge || 1; + this.l2OutputIndexToChallenge = state?.l2OutputIndexToChallenge || 1; + this.l1LastChallengedSequence = this.l1DepositSequenceToChallenge - 1; } public async run(): Promise { @@ -79,13 +105,14 @@ export class Challenger { where: { sequence: this.l1DepositSequenceToChallenge } as any }); - if (!depositTxFromChallenger) { - return; - } + if (!depositTxFromChallenger) return; this.l1DepositSequenceToChallenge = Number( depositTxFromChallenger.sequence ); + if (this.l1LastChallengedSequence == this.l1DepositSequenceToChallenge) + return; + // case 1. not finalized deposit tx const depositFinalizeTxFromChallenger = await manager .getRepository(ChallengerFinalizeDepositTxEntity) @@ -96,12 +123,12 @@ export class Challenger { if (!depositFinalizeTxFromChallenger) { this.missCount += 1; this.logger.warn( - `[L1 Challenger] not finalized deposit tx in sequence : ${this.l1DepositSequenceToChallenge}` + `[L1 Challenger] deposit tx with sequence "${this.l1DepositSequenceToChallenge}" is not finialized` ); if (this.missCount <= THRESHOLD_MISS_INTERVAL || !lastOutputInfo) { return await delay(this.submissionIntervalMs); } - return await this.deleteOutputProposal( + return await this.handleChallengedOutputProposal( manager, lastOutputInfo.output_index, `not finalized deposit tx within ${THRESHOLD_MISS_INTERVAL} submission interval ${depositFinalizeTxFromChallenger}` @@ -123,18 +150,16 @@ export class Challenger { pair.l2_denom === depositFinalizeTxFromChallenger.l2Denom; if (!isEqaul && lastOutputInfo) { - await this.deleteOutputProposal( + await this.handleChallengedOutputProposal( manager, lastOutputInfo.output_index, `not equal deposit tx between L1 and L2` ); } - if (this.l1LastChallengedSequence != this.l1DepositSequenceToChallenge) { - logger.info( - `[L1 Challenger] deposit tx matched in sequence : ${this.l1DepositSequenceToChallenge}` - ); - } + logger.info( + `[L1 Challenger] deposit tx matched in sequence : ${this.l1DepositSequenceToChallenge}` + ); this.missCount = 0; this.l1LastChallengedSequence = this.l1DepositSequenceToChallenge; @@ -150,6 +175,12 @@ export class Challenger { this.l1DepositSequenceToChallenge = Number( nextDepositSequenceToChallenge[0].sequence ); + await manager.getRepository(ChallengeEntity).update( + { name: this.name() }, + { + l1DepositSequenceToChallenge: this.l1DepositSequenceToChallenge + } + ); } public stop(): void { @@ -233,7 +264,7 @@ export class Challenger { if (!outputRootFromContract || !outputRootFromChallenger) return; if (outputRootFromContract !== outputRootFromChallenger) { - await this.deleteOutputProposal( + await this.handleChallengedOutputProposal( manager, this.l2OutputIndexToChallenge, `not equal output root from contract: ${outputRootFromContract}, from challenger: ${outputRootFromChallenger}` @@ -244,22 +275,41 @@ export class Challenger { `[L2 Challenger] output root matched in output index : ${this.l2OutputIndexToChallenge}` ); this.l2OutputIndexToChallenge += 1; + await manager.getRepository(ChallengeEntity).update( + { name: this.name() }, + { + l2OutputIndexToChallenge: this.l2OutputIndexToChallenge + } + ); + } + + async deleteOutputProposal(outputIndex: number) { + const msg = new MsgDeleteOutput( + this.challenger.key.accAddress, + this.bridgeId, + outputIndex + ); + + await this.challenger.transaction([msg]); } - async deleteOutputProposal( + async handleChallengedOutputProposal( manager: EntityManager, outputIndex: number, reason?: string ) { - const deletedOutput: ChallengerDeletedOutputEntity = { + const challengedOutput: ChallengedOutputEntity = { outputIndex, bridgeId: this.bridgeId.toString(), reason: reason ?? 'unknown' }; - await manager - .getRepository(ChallengerDeletedOutputEntity) - .save(deletedOutput); + await manager.getRepository(ChallengedOutputEntity).save(challengedOutput); + + if (DELETE_OUTPUT_PROPOSAL === 'true') { + await this.deleteOutputProposal(outputIndex); + } logger.info(`output index ${outputIndex} is deleted, reason: ${reason}`); + process.exit(); } } diff --git a/bots/src/worker/challenger/db.ts b/bots/src/worker/challenger/db.ts index 0b8b23d6..4d2c4c35 100644 --- a/bots/src/worker/challenger/db.ts +++ b/bots/src/worker/challenger/db.ts @@ -15,9 +15,10 @@ import { ChallengerDepositTxEntity, StateEntity, ChallengerWithdrawalTxEntity, - ChallengerDeletedOutputEntity, + ChallengedOutputEntity, ChallengerFinalizeDepositTxEntity, - ChallengerFinalizeWithdrawalTxEntity + ChallengerFinalizeWithdrawalTxEntity, + ChallengeEntity } from 'orm'; const staticOptions = { @@ -30,7 +31,8 @@ const staticOptions = { ChallengerWithdrawalTxEntity, ChallengerDepositTxEntity, ChallengerOutputEntity, - ChallengerDeletedOutputEntity + ChallengedOutputEntity, + ChallengeEntity ] };