diff --git a/.env.example b/.env.example deleted file mode 100644 index 5831f42..0000000 --- a/.env.example +++ /dev/null @@ -1,31 +0,0 @@ -API_URL=http://localhost:26657 -INDEXER_URL=http://localhost:42069 -LOOKBACK_TIME=40000 -AUTH_TOKEN=123456 - -OPTIMISM_RPC='https://opt-sepolia.g.alchemy.com/v2/jKvLhhXvtnWdNeZrKst0demxnwJcYH1o' -BASE_RPC='https://base-sepolia.g.alchemy.com/v2/776dC6qT-NTtupdnxlUJuXGbUIKWWhLe' - -# production -DISPATCHER_ADDRESS_OPTIMISM=0xE2029629f51ab994210d671Dc08b7Ec94899b278 -DISPATCHER_ADDRESS_OPTIMISM_SIMCLIENT=0x8957494cCD4B085133E9A8d3600b766427d4976a -DISPATCHER_ADDRESS_BASE=0x9fcd52449261F732d017F8BD1CaCDc3dFbcD0361 -DISPATCHER_ADDRESS_BASE_SIMCLIENT=0xb7B9f6f8CF00bf9fAe840A608600F47A665080f4 -DISPATCHER_ADDRESS_MOLTEN=0x12030CcD93ebA4C0C8F3B59ad70A05C042B0c8e1 -DISPATCHER_ADDRESS_MOLTEN_SIMCLIENT=0x02A3F40E6A66fDa056dA0757eacA1381753ecAA7 -OPTIMISM_CLIENT_NAME="optimism-proofs-2" -BASE_CLIENT_NAME="base-proofs-2" -BASE_CLIENT_SIMCLIENT_NAME="base-sim" -OPTIMISM_CLIENT_SIMCLIENT_NAME="optimism-sim" -MOLTEN_CLIENT_NAME="molten-proofs" -MOLTEN_CLIENT_SIMCLIENT_NAME="molten-sim" - -# staging -# DISPATCHER_ADDRESS_OPTIMISM=0x1Af056B8C6E21E1648D6822cD7026bDe6aE9DbE9 -# DISPATCHER_ADDRESS_OPTIMISM_SIMCLIENT=0xC6c8a05aa18327DCA7c9f32dCb7da64aBfd9592F -# DISPATCHER_ADDRESS_BASE=0x3bC743131dC1954BF1Bf76Ed19C154eC5e10AB3A -# DISPATCHER_ADDRESS_BASE_SIMCLIENT=0x9954587c5345d21A8051804804D2eB257C582127 -# OPTIMISM_CLIENT_NAME="optimism-proofs" -# BASE_CLIENT_NAME="base-proofs" -# OPTIMISM_CLIENT_SIMCLIENT_NAME="optimism-sim" -# BASE_CLIENT_SIMCLIENT_NAME="base-sim" diff --git a/app/api/cache/route.ts b/app/api/cache/route.ts deleted file mode 100644 index 855790d..0000000 --- a/app/api/cache/route.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { SimpleCache } from '@/api/utils/cache'; -import { isLocalEnv } from '@/api/utils/helpers'; -import { getChannelsConcurrently } from '@/api/utils/peptide'; -import { getPackets } from '@/api/packets/helpers'; - -export const dynamic = 'force-dynamic'; // defaults to auto - -export async function GET(request: NextRequest) { - const AUTH_TOKEN = process.env.AUTH_TOKEN; - - if (!isLocalEnv() && request.headers.get('Authorization') !== `Bearer ${AUTH_TOKEN}`) { - console.warn(`Unauthorized request to /api/cache with auth token ${request.headers.get('Authorization')}`); - return NextResponse.error(); - } - - const cache = SimpleCache.getInstance(); - const start = Date.now(); - try { - - // Run requests concurrently - const [packets] = await Promise.all([ - getPackets(), - ]); - - console.log("Saving packets to cache"); - - // Set cache concurrently - await Promise.all([ - cache.set('allPackets', packets, -1), - ]); - - console.log(`Fetched packets and channels in ${Date.now() - start}ms`); - return NextResponse.json({'packets': packets}); - } catch (e) { - console.error('Error fetching packets', e); - return NextResponse.error(); - } -} \ No newline at end of file diff --git a/app/api/channels/helpers.ts b/app/api/channels/helpers.ts index 97dc9af..7ae7f25 100644 --- a/app/api/channels/helpers.ts +++ b/app/api/channels/helpers.ts @@ -1,6 +1,7 @@ -import { IdentifiedChannel } from '@/api/utils/cosmos/_generated/ibc/core/channel/v1/channel'; +import { IdentifiedChannel } from 'api/utils/cosmos/_generated/ibc/core/channel/v1/channel'; import { State } from 'cosmjs-types/ibc/core/channel/v1/channel'; -function stateToString(state: string) { + +function stringToState(state: string) { switch (state) { case "OPEN": return State.STATE_OPEN case "INIT": return State.STATE_INIT @@ -28,7 +29,7 @@ async function getChannelByGQQuery(channelRequest: { const channel: IdentifiedChannel = { portId: item.portId, channelId: item.channelId, - state: stateToString(item.state), + state: stringToState(item.state), ordering: item.ordering, version: item.version, connectionHops: item.connectionHops, @@ -89,4 +90,4 @@ export async function getChannel(searchId: string) { variables: { searchId: searchId } }; return await getChannelByGQQuery(channelRequest); -} \ No newline at end of file +} diff --git a/app/api/channels/route.ts b/app/api/channels/route.ts index ff1c734..5db3a38 100644 --- a/app/api/channels/route.ts +++ b/app/api/channels/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -import { getChannel, getChannels } from '@/api/channels/helpers'; -import logger from '@/utils/logger'; +import { getChannel, getChannels } from 'api/channels/helpers'; +import logger from 'utils/logger'; export const dynamic = 'force-dynamic'; // defaults to auto diff --git a/app/api/ibc/[type]/route.ts b/app/api/ibc/[type]/route.ts index 71645ee..4564b1d 100644 --- a/app/api/ibc/[type]/route.ts +++ b/app/api/ibc/[type]/route.ts @@ -1,6 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; import { getClients, getConnections } from '@/api/utils/peptide'; -import { SimpleCache } from '@/api/utils/cache'; export const dynamic = 'force-dynamic' // defaults to auto @@ -8,7 +7,6 @@ export async function GET(request: NextRequest, {params}: {params: { type: "channels" | "connections" | "clients" }} ) { const reqType = params.type; - const cache = SimpleCache.getInstance(); try { switch (reqType) { @@ -21,5 +19,3 @@ export async function GET(request: NextRequest, return NextResponse.json({error: "An error occurred while fetching data"}); } } - - diff --git a/app/api/packets/helpers.ts b/app/api/packets/helpers.ts index 3a023ca..e710021 100644 --- a/app/api/packets/helpers.ts +++ b/app/api/packets/helpers.ts @@ -1,270 +1,100 @@ -import { pLimit } from 'plimit-lit'; -import { getConcurrencyLimit, getLookbackTime } from '@/api/utils/helpers'; -import { ethers } from 'ethers'; -import { CHAIN, CHAIN_CONFIGS } from '@/utils/chains/configs'; -import { CachingJsonRpcProvider } from '@/api/utils/provider'; -import Abi from '@/utils/dispatcher.json'; -import { GetTmClient } from '@/api/utils/cosmos'; -import { Packet, PacketStates } from '@/utils/types/packet'; -import { NextResponse } from 'next/server'; +import { Packet, PacketStates } from 'utils/types/packet'; +import logger from 'utils/logger'; -export async function getPackets() { - const limit = pLimit(getConcurrencyLimit()); // Adjust this number to the maximum concurrency you want - let sendLogs: Array<[ethers.EventLog, CHAIN, string]> = []; - let srcChainProviders: Record = {} as Record; - let srcChainContracts: Array<[ethers.Contract, CHAIN, number, string]> = []; - - let chainPromises = Object.keys(CHAIN_CONFIGS).map(async (chain) => { - const chainId = chain as CHAIN; - const dispatcherAddresses = CHAIN_CONFIGS[chainId].dispatchers; - const clients = CHAIN_CONFIGS[chainId].clients; - - const dispatcherPromises = dispatcherAddresses.map((dispatcherAddress, i) => limit(async () => { - let client = clients[i]; - const provider = new CachingJsonRpcProvider(CHAIN_CONFIGS[chainId].rpc, CHAIN_CONFIGS[chainId].id); - const block = await provider.getBlock('latest'); - const blockNumber = block!.number; - const fromBlock = blockNumber - getLookbackTime() / CHAIN_CONFIGS[chainId].blockTime; - - srcChainProviders[chainId] = provider; - const contract = new ethers.Contract(dispatcherAddress, Abi.abi, provider); - srcChainContracts.push([contract, chainId, fromBlock, client]); - - console.log(`Getting sent packets for chain ${chainId} from block ${fromBlock} for client ${client} and dispatcher ${dispatcherAddress}`); - const newSendLogs = (await contract.queryFilter('SendPacket', fromBlock, 'latest')) as Array; - sendLogs = sendLogs.concat(newSendLogs.map((eventLog) => [eventLog, chainId, client])); - })); - - await Promise.all(dispatcherPromises); - }); - - await Promise.all(chainPromises); - - console.log('Getting a tm client'); - const tmClient = await GetTmClient(); - - // Collect all packets and their properties from the send logs - const unprocessedPacketKeys = new Set(); - const packets: Record = {}; - - console.log(`Processing ${sendLogs.length} send logs...`); - - async function processSendLog(sendLog: [ethers.EventLog, CHAIN, string]) { - const [sendEvent, srcChain, client] = sendLog; - let [srcPortAddress, srcChannelId, packet, sequence, timeout, fee] = sendEvent.args; - srcChannelId = ethers.decodeBytes32String(srcChannelId); - const portId = `polyibc.${client}.${srcPortAddress.slice(2)}`; - - let channel; - try { - console.log(`Getting channel for port ${portId} and channel ${srcChannelId} and sequence ${sequence}`); - channel = await tmClient.ibc.channel.channel(portId, srcChannelId); - } catch (e) { - console.log('Skipping packet for channel: ', srcChannelId); - return; - } - - if (!channel.channel) { - console.warn('No channel found for packet: ', srcChannelId, srcPortAddress); - return; - } - - const key = `${portId}-${srcChannelId}-${sequence}`; - const srcProvider = srcChainProviders[srcChain]; - const srcBlock = await srcProvider.getBlock(sendEvent.blockNumber); - - packets[key] = { - sourcePortAddress: srcPortAddress, - sourceChannelId: srcChannelId, - destPortAddress: channel.channel.counterparty.portId, - destChannelId: channel.channel.counterparty.channelId, - sequence: sequence.toString(), - timeout: timeout.toString(), - id: key, - state: PacketStates.SENT, - createTime: srcBlock!.timestamp, - sendTx: sendEvent.transactionHash, - sourceChain: client, - destChain: channel.channel.counterparty.portId.split('.')[1] - }; - unprocessedPacketKeys.add(key); - } - - const processSendLogLimited = (sendLog: [ethers.EventLog, CHAIN, string]) => limit(() => processSendLog(sendLog)); - - await Promise.allSettled(sendLogs.map(processSendLogLimited)); - - // Start by searching for ack events on the source chains - const ackLogsPromises = srcChainContracts.map(async ([contract, chain, fromBlock, client]) => { - const newAckLogs = (await contract.queryFilter('Acknowledgement', fromBlock, 'latest')) as Array; - return newAckLogs.map((eventLog) => [eventLog, chain, client] as [ethers.EventLog, CHAIN, string]); - }); - - const ackLogsResults = await Promise.allSettled(ackLogsPromises); - let ackLogs: Array<[ethers.EventLog, CHAIN, string]> = []; - - for (const result of ackLogsResults) { - if (result.status === 'fulfilled') { - ackLogs = ackLogs.concat(result.value); - } +function stringToState(state: string) { + switch (state) { + case "ACK": + return PacketStates.ACK; + case "RECV": + case "WRITE_ACK": + return PacketStates.RECV; + default: + return PacketStates.SENT; } +} - async function processAckLog(ackLog: [ethers.EventLog, CHAIN, string]) { - const [ackEvent, srcChain, client] = ackLog; - let [srcPortAddress, srcChannelId, sequence] = ackEvent.args; - const key = `polyibc.${client}.${srcPortAddress.slice(2)}-${ethers.decodeBytes32String(srcChannelId)}-${sequence}`; - - if (!packets[key]) { - console.log('No packet found for ack: ', key); - return; - } - - const srcProvider = srcChainProviders[srcChain]; - const srcBlock = await srcProvider.getBlock(ackEvent.blockNumber); - if (srcBlock!.timestamp < packets[key].createTime) { - return; - } +async function processPacketRequest(packetRequest: { + query: string + variables: { txHash?: string, limit?: number } +}): Promise{ + const headers = {'content-type': 'application/json'}; + const packetOptions = { + method: 'POST', + headers, + body: JSON.stringify(packetRequest) + }; - packets[key].endTime = srcBlock!.timestamp; - packets[key].state = PacketStates.ACK; - packets[key].ackTx = ackEvent.transactionHash; - unprocessedPacketKeys.delete(key); + const packetRes = await (await fetch(process.env.INDEXER_URL!, packetOptions)).json(); + const responseItems = packetRes?.data?.packets?.items; + if (!responseItems) { + return []; } - // Wait for all promises to settle - await Promise.allSettled(ackLogs.map(processAckLog)); - - // Set up providers and contracts to interact with the destination chains - let destChainProviders: Record = {} as Record; - let destChainContracts: Array<[ethers.Contract, CHAIN, number, string]> = []; + const packets: Packet[] = []; - chainPromises = Object.keys(CHAIN_CONFIGS).map(async (chain) => { - const chainId = chain as CHAIN; - const dispatcherAddresses = CHAIN_CONFIGS[chainId].dispatchers; - const clients = CHAIN_CONFIGS[chainId].clients; - - const dispatcherPromises = dispatcherAddresses.map(async (dispatcherAddress, i) => { - let client = clients[i]; - const provider = new CachingJsonRpcProvider(CHAIN_CONFIGS[chainId].rpc, CHAIN_CONFIGS[chainId].id); - const block = await provider.getBlock('latest'); - const blockNumber = block!.number; - const fromBlock = blockNumber - getLookbackTime() / CHAIN_CONFIGS[chainId].blockTime; - - destChainProviders[chainId] = provider; - const contract = new ethers.Contract(dispatcherAddress, Abi.abi, provider); - destChainContracts.push([contract, chainId, fromBlock, client]); - }); - - await Promise.all(dispatcherPromises); - }); - - await Promise.all(chainPromises); - - const writeAckLogsPromises = destChainContracts.map(async ([destContract, destChain, fromBlock, client]) => { - const newWriteAckLogs = await destContract.queryFilter('WriteAckPacket', fromBlock, 'latest'); - return newWriteAckLogs.map((eventLog) => [eventLog, destChain, client] as [ethers.EventLog, CHAIN, string]); - }); - - let writeAckLogs: Array<[ethers.EventLog, CHAIN, string]> = []; - for (const result of await Promise.allSettled(writeAckLogsPromises)) { - if (result.status === 'fulfilled') { - writeAckLogs = writeAckLogs.concat(result.value); - } - } + for (const packet of responseItems) { + // Find channel associated with the packet to parse out src and dest chains + const sourceChannelId = packet.sendPacket?.sourceChannelId; - console.log(`Processing ${writeAckLogs.length} write ack logs...`); + const channelRequest = { + query: `query Channel($sourceChannelId:String!){ + channels(where:{channelId:$sourceChannelId}){ + items { + portId + counterpartyPortId + } + } + }`, + variables: { sourceChannelId } + }; - for (const writeAckLog of writeAckLogs) { - const [writeAckEvent, destChain, client] = writeAckLog; - let [receiver, destChannelId, sequence, ack] = writeAckEvent.args; - destChannelId = ethers.decodeBytes32String(destChannelId); + const channelOptions = { + method: 'POST', + headers, + body: JSON.stringify(channelRequest) + }; - let channel; + let channelResponse; try { - channel = await tmClient.ibc.channel.channel(`polyibc.${client}.${receiver.slice(2)}`, destChannelId); - } catch (e) { - console.log('Skipping packet for channel: ', destChannelId); - continue; - } - - if (!channel.channel) { - console.warn('No channel found for write ack: ', destChannelId, receiver); + const channelRes = await (await fetch(process.env.INDEXER_URL!, channelOptions)).json(); + channelResponse = channelRes?.data?.channels?.items[0]; + if (!channelResponse) { + logger.info(`Channel not found for packet ${packet.id}`); + continue; + } + } catch (err) { + logger.error(`Error processing packet ${packet.id}`); continue; } - const key = `${channel.channel.counterparty.portId}-${channel.channel.counterparty.channelId}-${sequence}`; - if (unprocessedPacketKeys.has(key)) { - packets[key].state = PacketStates.WRITE_ACK; - unprocessedPacketKeys.delete(key); - } - } - - // Match any recv packet events on destination chains to packets - const recvPacketPromises = destChainContracts.map(async (destChainContract) => { - const [destContract, destChain, fromBlock, client] = destChainContract; - const newRecvPacketLogs = await destContract.queryFilter('RecvPacket', fromBlock, 'latest'); - return newRecvPacketLogs.map((eventLog) => [eventLog, destChain, client] as [ethers.EventLog, CHAIN, string]); - }); + const state = stringToState(packet.state); + + const newPacket: Packet = { + sequence: packet.sendPacket?.sequence, + sourcePortAddress: packet.sendPacket?.sourcePortAddress, + sourceChannelId: packet.sendPacket?.sourceChannelId, + destPortAddress: packet.recvPacket?.destPortAddress, + destChannelId: packet.recvPacket?.destChannelId, + timeout: packet.sendPacket?.timeoutTimestamp, + id: packet.id, + state: state, + createTime: packet.sendPacket?.blockTimestamp, + recvTime: packet.recvPacket?.blockTimestamp, + endTime: packet.ackPacket?.blockTimestamp, + sendTx: packet.sendTx, + rcvTx: packet.recvTx, + ackTx: packet.ackTx, + sourceChain: channelResponse?.portId?.split('.')[1], + destChain: channelResponse?.counterpartyPortId?.split('.')[1] + }; - let recvPacketLogs: Array<[ethers.EventLog, CHAIN, string]> = []; - for (const result of await Promise.allSettled(recvPacketPromises)) { - if (result.status === 'fulfilled') { - recvPacketLogs = recvPacketLogs.concat(result.value); - } + packets.push(newPacket); } - console.log(`Processing ${recvPacketLogs.length} recv packet logs...`); - const promises = recvPacketLogs.map(async (recvPacketLog) => { - const [recvPacketEvent, destChain, client] = recvPacketLog; - let [destPortAddress, destChannelId, sequence] = recvPacketEvent.args; - destChannelId = ethers.decodeBytes32String(destChannelId); - - let channel; - try { - channel = await tmClient.ibc.channel.channel(`polyibc.${client}.${destPortAddress.slice(2)}`, destChannelId); - } catch (e) { - console.log('Skipping packet for channel: ', destChannelId); - return; - } - - if (!channel.channel) { - console.warn('No channel found for write ack: ', destChannelId, destPortAddress); - return; - } - - const key = `${channel.channel.counterparty.portId}-${channel.channel.counterparty.channelId}-${sequence}`; - - if (packets[key]) { - const recvBlock = await destChainProviders[destChain].getBlock(recvPacketEvent.blockNumber); - - if (recvBlock!.timestamp < packets[key].createTime) { - return; - } - - if (unprocessedPacketKeys.has(key)) { - packets[key].state = PacketStates.RECV; - unprocessedPacketKeys.delete(key); - } - - packets[key].rcvTx = recvPacketEvent.transactionHash; - } else { - console.log('No packet found for recv: ', key); - } - }); - - await Promise.allSettled(promises); - - const response: Packet[] = []; - Object.keys(packets).forEach((key) => { - response.push(packets[key]); - }); - return response; + return packets; } -export async function getPacket(txHash: string) { - const headers = {'content-type': 'application/json'}; - let packetResponse; - +export async function getPacket(txHash: string): Promise { const packetRequest = { query: `query Packet($txHash:String!){ packets(where:{ @@ -308,76 +138,45 @@ export async function getPacket(txHash: string) { variables: { txHash } }; - const packetOptions = { - method: 'POST', - headers, - body: JSON.stringify(packetRequest) - }; - - const packetRes = await (await fetch(process.env.INDEXER_URL!, packetOptions)).json(); - if (packetRes?.data?.packets?.items.length > 0) { - packetResponse = packetRes.data.packets.items[0]; - } else { - return []; - } - - // Find channel associated with the packet to parse out src and dest chains - const sourceChannelId = packetResponse.sendPacket?.sourceChannelId; - let channelResponse; + return await processPacketRequest(packetRequest); +} - const channelRequest = { - query: `query Channel($sourceChannelId:String!){ - channels(where:{channelId:$sourceChannelId}){ +export async function getRecentPackets(limit: number = 100): Promise { + const packetRequest = { + query: `query Packet($limit:Int!){ + packets(limit:$limit, orderBy: "sendBlockTimestamp", orderDirection: "desc") { items { - portId - counterpartyPortId + sendPacket { + sequence + sourcePortAddress + sourceChannelId + dispatcherAddress + blockTimestamp + timeoutTimestamp + } + recvPacket { + destPortAddress + destChannelId + blockTimestamp + } + writeAckPacket { + dispatcherAddress + blockTimestamp + } + ackPacket { + blockTimestamp + } + id + state + sendTx + recvTx + writeAckTx + ackTx } } }`, - variables: { sourceChannelId } - }; - - const channelOptions = { - method: 'POST', - headers, - body: JSON.stringify(channelRequest) - }; - - const channelRes = await (await fetch(process.env.INDEXER_URL!, channelOptions)).json(); - if (channelRes?.data?.channels?.items.length > 0) { - channelResponse = channelRes.data.channels.items[0]; - } - - let state: PacketStates = PacketStates.SENT; - switch (packetResponse.state) { - case "ACK": - state = PacketStates.ACK; - break; - case "RECV": - case "WRITE_ACK": - state = PacketStates.RECV; - break; - } - - - const packet: Packet = { - sequence: packetResponse.sendPacket?.sequence, - sourcePortAddress: packetResponse.sendPacket?.sourcePortAddress, - sourceChannelId: packetResponse.sendPacket?.sourceChannelId, - destPortAddress: packetResponse.recvPacket?.destPortAddress, - destChannelId: packetResponse.recvPacket?.destChannelId, - timeout: packetResponse.sendPacket?.timeoutTimestamp, - id: packetResponse.id, - state: state, - createTime: packetResponse.sendPacket?.blockTimestamp, - recvTime: packetResponse.recvPacket?.blockTimestamp, - endTime: packetResponse.ackPacket?.blockTimestamp, - sendTx: packetResponse.sendTx, - rcvTx: packetResponse.recvTx, - ackTx: packetResponse.ackTx, - sourceChain: channelResponse?.portId?.split('.')[1], - destChain: channelResponse?.counterpartyPortId?.split('.')[1] + variables: { limit } }; - return [packet]; + return await processPacketRequest(packetRequest); } diff --git a/app/api/packets/route.ts b/app/api/packets/route.ts index 776bb5a..b92a55a 100644 --- a/app/api/packets/route.ts +++ b/app/api/packets/route.ts @@ -1,22 +1,25 @@ import { NextRequest, NextResponse } from 'next/server'; -import { SimpleCache } from 'api/utils/cache'; -import { getPacket } from 'api/packets/helpers'; +import { getPacket, getRecentPackets } from 'api/packets/helpers'; +import logger from 'utils/logger'; export const dynamic = 'force-dynamic'; // defaults to auto export async function GET(request: NextRequest) { const txHash = request.nextUrl.searchParams.get('txHash'); + if (!txHash) { - const cache = SimpleCache.getInstance(); - const allPackets = await cache.get('allPackets'); - return NextResponse.json(allPackets || []); + try { + return NextResponse.json(await getRecentPackets()); + } catch (err) { + logger.error(`Error getting recent packets: ` + err); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } } try { - const packet = await getPacket(txHash); - return NextResponse.json(packet); - } - catch (err) { + return NextResponse.json(await getPacket(txHash)); + } catch (err) { + logger.error(`Error finding packet ${txHash}: ` + err); return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); } }