From 41782747b296837974d5e31bcf5b1bf144f23e28 Mon Sep 17 00:00:00 2001 From: ALPAC-4 Date: Mon, 16 Dec 2024 13:30:44 +0900 Subject: [PATCH] feat: update client to prevent client expiration --- src/db/controller/client.ts | 62 ++++++++++++++++++++++++++++++++++--- src/lib/eventParser.ts | 19 +++++++++++- src/msgs/updateClient.ts | 13 ++++---- src/workers/chain.ts | 28 ++++++++++++++++- src/workers/wallet.ts | 26 +++++++++------- 5 files changed, 123 insertions(+), 25 deletions(-) diff --git a/src/db/controller/client.ts b/src/db/controller/client.ts index cab0991..e985827 100644 --- a/src/db/controller/client.ts +++ b/src/db/controller/client.ts @@ -1,5 +1,5 @@ import { DB } from '..' -import { insert, selectOne } from '../utils' +import { insert, select, selectOne, update } from '../utils' import { Any } from 'cosmjs-types/google/protobuf/any' import { UpdateClientEvent, ClientTable } from 'src/types' import { Header } from 'cosmjs-types/ibc/lightclients/tendermint/v1/tendermint' @@ -51,15 +51,35 @@ export class ClientController { const client = await this.getClient(rest, chainId, clientId) // update client - client.revision_height = parseInt( + const revisionHeight = parseInt( event.consensusHeights.split(',')[0].split('-')[1] ) + if (revisionHeight > client.revision_height) { + client.revision_height = revisionHeight + } + if (header.signedHeader?.header?.time.seconds) { - client.last_update_time = Number(header.signedHeader.header.time.seconds) + const lastUpdateTime = Number(header.signedHeader.header.time.seconds) + if (lastUpdateTime > client.last_update_time) { + client.last_update_time = lastUpdateTime + } } - insert(DB, this.tableName, client) + update( + DB, + this.tableName, + { + revision_height: client.revision_height, + last_update_time: client.last_update_time, + }, + [ + { + chain_id: client.chain_id, + client_id: client.client_id, + }, + ] + ) } public static async getClient( @@ -77,4 +97,38 @@ export class ClientController { return client ?? this.addClient(rest, chainId, clientId) } + + public static getClientsToUpdate( + chainId: string, + counterpartyChainIds: string[] + ): ClientTable[] { + const clients = select( + DB, + this.tableName, + counterpartyChainIds.map((counterpartyChainId) => ({ + chain_id: chainId, + counterparty_chain_id: counterpartyChainId, + })) + ) + + // check need updates + const currentTimestamp = new Date().valueOf() / 1000 + + return clients.filter((client) => { + // check expired + if (client.last_update_time + client.trusting_period < currentTimestamp) { + return false + } + + // check need update + if ( + client.last_update_time + client.trusting_period * 0.666 < + currentTimestamp + ) { + return true + } + + return false + }) + } } diff --git a/src/lib/eventParser.ts b/src/lib/eventParser.ts index 8e34ba6..34a65d3 100644 --- a/src/lib/eventParser.ts +++ b/src/lib/eventParser.ts @@ -1,5 +1,10 @@ import { Event } from '@cosmjs/tendermint-rpc/build/comet38/responses' -import { ChannelOpenCloseInfo, PacketFeeEvent, PacketInfo } from 'src/types' +import { + ChannelOpenCloseInfo, + PacketFeeEvent, + PacketInfo, + UpdateClientEvent, +} from 'src/types' export function parsePacketEvent(event: Event, height: number): PacketInfo { const connectionId = getConnection(event) as string @@ -124,6 +129,18 @@ export function parsePacketFeeEvent(event: Event): PacketFeeEvent { } } +export function parseUpdateClientEvent(event: Event): UpdateClientEvent { + const clientId = find(event, 'client_id') as string + const header = find(event, 'header') as string + const consensusHeights = find(event, 'consensus_heights') as string + + return { + clientId, + header, + consensusHeights, + } +} + function getConnection(event: Event): string | undefined { return find(event, 'connection_id') || find(event, 'packet_connection') } diff --git a/src/msgs/updateClient.ts b/src/msgs/updateClient.ts index bf3cd0c..5300943 100644 --- a/src/msgs/updateClient.ts +++ b/src/msgs/updateClient.ts @@ -76,12 +76,10 @@ async function getValidatorSet( chain: ChainWorker, height: number ): Promise { - let block = await chain.rest.tendermint - .blockInfo(height) - .catch(() => undefined) + let header = await chain.rpc.header(height).catch(() => undefined) let count = 0 - while (block === undefined) { - block = await chain.rest.tendermint.blockInfo(height).catch((e) => { + while (header === undefined) { + header = await chain.rpc.header(height).catch((e) => { if (count > 5) { throw e } @@ -90,7 +88,7 @@ async function getValidatorSet( await delay(100) count++ } - const proposerAddress = block.block.header.proposer_address + const proposerAddress = header.header.proposer_address // we need to query the header to find out who the proposer was, and pull them out const validators = await chain.rpc.validatorsAll(height) let totalVotingPower = BigInt(0) @@ -117,7 +115,8 @@ async function getValidatorSet( }) const proposer: Validator | undefined = mappedValidators.find( - (val) => Buffer.from(val.address).toString('base64') === proposerAddress + (val) => + Buffer.from(val.address).toString('hex') === proposerAddress.toLowerCase() ) return ValidatorSet.fromPartial({ diff --git a/src/workers/chain.ts b/src/workers/chain.ts index 37d0d7d..cb72259 100644 --- a/src/workers/chain.ts +++ b/src/workers/chain.ts @@ -1,11 +1,17 @@ import { RPCClient } from 'src/lib/rpcClient' import { createLoggerWithPrefix } from 'src/lib/logger' -import { ChannelOpenCloseEvent, PacketEvent, PacketFeeEvent } from 'src/types' +import { + ChannelOpenCloseEvent, + PacketEvent, + PacketFeeEvent, + UpdateClientEvent, +} from 'src/types' import { parseChannelCloseEvent, parseChannelOpenEvent, parsePacketEvent, parsePacketFeeEvent, + parseUpdateClientEvent, } from 'src/lib/eventParser' import { DB } from 'src/db' import { SyncInfoController } from 'src/db/controller/syncInfo' @@ -16,6 +22,7 @@ import { RESTClient } from 'src/lib/restClient' import { ChannelController } from 'src/db/controller/channel' import { PacketFeeController } from 'src/db/controller/packetFee' import { PacketFee } from 'src/lib/config' +import { ClientController } from 'src/db/controller/client' export class ChainWorker { public latestHeight: number @@ -138,11 +145,23 @@ class SyncWorker { const packetEvents = events.map((e) => e.packetEvents).flat() const channelOpenEvents = events.map((e) => e.channelOpenEvents).flat() const packetFeeEvents = events.map((e) => e.packetFeeEvents).flat() + const updateClientEvents = events + .map((e) => e.updateClientEvents) + .flat() this.logger.debug( `Fetched block results for heights (${JSON.stringify(heights)})` ) + // `feedUpdateClient` does not need to be included in the db transaction + for (const event of updateClientEvents) { + await ClientController.feedUpdateClientEvent( + this.chain.rest, + this.chain.chainId, + event + ) + } + let finish = false const packetEventFeed = await PacketController.feedEvents( @@ -196,6 +215,7 @@ class SyncWorker { packetEvents: PacketEvent[] channelOpenEvents: ChannelOpenCloseEvent[] packetFeeEvents: PacketFeeEvent[] + updateClientEvents: UpdateClientEvent[] }> { this.logger.debug(`Fetch new block results (height - ${height})`) const blockResult = await this.chain.rpc.blockResults(height) @@ -204,6 +224,7 @@ class SyncWorker { const packetEvents: PacketEvent[] = [] const channelOpenEvents: ChannelOpenCloseEvent[] = [] const packetFeeEvents: PacketFeeEvent[] = [] + const updateClientEvents: UpdateClientEvent[] = [] txData.map((data) => { for (const event of data.events) { @@ -245,6 +266,10 @@ class SyncWorker { if (event.type === 'incentivized_ibc_packet') { packetFeeEvents.push(parsePacketFeeEvent(event)) } + + if (event.type === 'update_client') { + updateClientEvents.push(parseUpdateClientEvent(event)) + } } }) @@ -252,6 +277,7 @@ class SyncWorker { packetEvents, channelOpenEvents, packetFeeEvents, + updateClientEvents, } } } diff --git a/src/workers/wallet.ts b/src/workers/wallet.ts index fa9c64d..50b5e68 100644 --- a/src/workers/wallet.ts +++ b/src/workers/wallet.ts @@ -20,8 +20,8 @@ import { Logger } from 'winston' import { ChannelController } from 'src/db/controller/channel' import { State } from '@initia/initia.proto/ibc/core/channel/v1/channel' import { PacketFee } from 'src/lib/config' +import { ClientController } from 'src/db/controller/client' -// TODO: add update client worker export class WalletWorker { private sequence?: number private accountNumber?: number @@ -163,19 +163,10 @@ export class WalletWorker { await this.filterTimeoutPackets(timeoutPackets) const filteredChannelOpenCloseEvents = await this.filterChannelOpenCloseEvents(channelOpenEvents) - if ( - filteredSendPackets.length === 0 && - filteredWriteAckPackets.length === 0 && - filteredTimeoutPackets.length === 0 && - filteredChannelOpenCloseEvents.length === 0 - ) { - return - } // create msgs // generate update client msgs - // get unique client id const connections = [ ...filteredSendPackets.map((packet) => packet.dst_connection_id), ...filteredWriteAckPackets.map((packet) => packet.src_connection_id), @@ -183,6 +174,7 @@ export class WalletWorker { ...filteredChannelOpenCloseEvents.map((event) => event.connection_id), ].filter((v, i, a) => a.indexOf(v) === i) + // get client ids from connections const connectionClientMap: Record = {} await Promise.all( connections.map(async (connection) => { @@ -195,10 +187,18 @@ export class WalletWorker { }) ) - const clientIds = Object.values(connectionClientMap).filter( - (v, i, a) => a.indexOf(v) === i + // check clients that need to update + const clientsToUpdate = ClientController.getClientsToUpdate( + this.chain.chainId, + counterpartyChainIdsWithFeeFilter.map((f) => f.chainId) ) + // get unique client id + const clientIds = [ + ...Object.values(connectionClientMap), + ...clientsToUpdate.map((c) => c.client_id), + ].filter((v, i, a) => a.indexOf(v) === i) + // generate msgs const updateClientMsgs: Record< string, @@ -303,6 +303,8 @@ export class WalletWorker { ...channelOpenMsgs, ] + if (msgs.length === 0) return + // init sequence if (!this.sequence) { await this.initAccInfo()