diff --git a/package-lock.json b/package-lock.json index 8c16140d..3a10e576 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20367,6 +20367,18 @@ "node": ">= 0.4" } }, + "node_modules/postgres": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.4.tgz", + "integrity": "sha512-IbyN+9KslkqcXa8AO9fxpk97PA4pzewvpi2B3Dwy9u4zpV32QicaEdgmF3eSQUzdRk7ttDHQejNgAEr4XoeH4A==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, "node_modules/postgres-array": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", @@ -26338,7 +26350,8 @@ "dependencies": { "@dcspark/cardano-multiplatform-lib-nodejs": "5.2.0", "@dcspark/carp-client": "^3.1.0", - "assert-never": "^1.2.1" + "assert-never": "^1.2.1", + "postgres": "^3.3.5" }, "devDependencies": { "typescript": "^5.3.3" diff --git a/packages/engine/paima-funnel/package.json b/packages/engine/paima-funnel/package.json index d1f33c65..a9d1dad5 100644 --- a/packages/engine/paima-funnel/package.json +++ b/packages/engine/paima-funnel/package.json @@ -19,6 +19,7 @@ "dependencies": { "assert-never": "^1.2.1", "@dcspark/carp-client": "^3.1.0", - "@dcspark/cardano-multiplatform-lib-nodejs": "5.2.0" + "@dcspark/cardano-multiplatform-lib-nodejs": "5.2.0", + "postgres": "^3.3.5" } } diff --git a/packages/engine/paima-funnel/src/funnels/FunnelCache.ts b/packages/engine/paima-funnel/src/funnels/FunnelCache.ts index 78a42383..3c9ba1a8 100644 --- a/packages/engine/paima-funnel/src/funnels/FunnelCache.ts +++ b/packages/engine/paima-funnel/src/funnels/FunnelCache.ts @@ -1,4 +1,5 @@ import type { ChainData } from '@paima/sm'; +import postgres from 'postgres'; export interface FunnelCacheEntry { /** @@ -184,6 +185,7 @@ export type MinaFunnelCacheEntryState = { startingSlotTimestamp: number; lastPoint: { timestamp: number } | undefined; genesisTime: number; + pg: postgres.Sql; cursors: | { [cdeId: number]: { cursor: string; finished: boolean }; @@ -195,12 +197,17 @@ export class MinaFunnelCacheEntry implements FunnelCacheEntry { private state: MinaFunnelCacheEntryState | null = null; public static readonly SYMBOL = Symbol('MinaFunnelStartingSlot'); - public updateStartingSlot(startingSlotTimestamp: number, genesisTime: number): void { + public updateStartingSlot( + startingSlotTimestamp: number, + genesisTime: number, + pg: postgres.Sql + ): void { this.state = { startingSlotTimestamp, genesisTime, lastPoint: this.state?.lastPoint, cursors: this.state?.cursors, + pg, }; } diff --git a/packages/engine/paima-funnel/src/funnels/mina/funnel.ts b/packages/engine/paima-funnel/src/funnels/mina/funnel.ts index 3bb46e50..f69cec2d 100644 --- a/packages/engine/paima-funnel/src/funnels/mina/funnel.ts +++ b/packages/engine/paima-funnel/src/funnels/mina/funnel.ts @@ -15,33 +15,19 @@ import { getPaginationCursors } from '@paima/db'; import { getActionCdeData, getEventCdeData } from '../../cde/minaGeneric.js'; import type { MinaConfig } from '@paima/utils'; import { MinaFunnelCacheEntry } from '../FunnelCache.js'; +import postgres from 'postgres'; -async function getGenesisTime(graphql: string): Promise { - const genesisTime = (await fetch(graphql, { - method: 'POST', - - headers: { - 'Content-Type': 'application/json', - }, - - body: JSON.stringify({ - query: ` - { - genesisBlock { - protocolState { - blockchainState { - utcDate - } - } - } - } - `, - }), - }) - .then(res => res.json()) - .then(res => res.data.genesisBlock.protocolState.blockchainState.utcDate)) as string; +async function getGenesisTime(pg: postgres.Sql): Promise { + const row = await pg`select timestamp from blocks where height = 1;`; - return Number.parseInt(genesisTime, 10); + return Number.parseInt(row[0]['timestamp'], 10); +} + +async function findMinaConfirmedSlot(pg: postgres.Sql): Promise { + const row = + await pg`select global_slot_since_genesis from blocks where chain_status = 'canonical' order by height desc limit 1;`; + + return Number.parseInt(row[0]['global_slot_since_genesis'], 10); } function slotToMinaTimestamp(slot: number, genesisTime: number, slotDuration: number): number { @@ -60,44 +46,6 @@ function baseChainTimestampToMina( return Math.max(baseChainTimestamp * 1000 - slotDuration * 1000 * confirmationDepth, 0); } -// TODO: maybe using the node's rpc here it's not really safe? if it's out of -// sync with the archive db we could end up skipping events -// it would be better to have an endpoint for this on the archive api -// either that, or the archive node api should take a block hash, and if it's -// not there it should return an error. -async function findMinaConfirmedSlot(graphql: string, confirmationDepth: number): Promise { - const body = JSON.stringify({ - query: ` - { - bestChain(maxLength: ${confirmationDepth}) { - stateHash - protocolState { - consensusState { - blockHeight - slotSinceGenesis - } - previousStateHash - } - } - } - `, - }); - - const confirmedSlot = await fetch(graphql, { - method: 'POST', - - headers: { - 'Content-Type': 'application/json', - }, - - body, - }) - .then(res => res.json()) - .then(res => res.data.bestChain[0].protocolState.consensusState.slotSinceGenesis); - - return Number.parseInt(confirmedSlot, 10); -} - export class MinaFunnel extends BaseFunnel implements ChainFunnel { config: MinaConfig; chainName: string; @@ -127,10 +75,7 @@ export class MinaFunnel extends BaseFunnel implements ChainFunnel { let cachedState = this.cache.getState(); - const confirmedSlot = await findMinaConfirmedSlot( - this.config.graphql, - this.config.confirmationDepth - ); + const confirmedSlot = await findMinaConfirmedSlot(cachedState.pg); const confirmedTimestamp = slotToMinaTimestamp( confirmedSlot, @@ -391,7 +336,9 @@ export class MinaFunnel extends BaseFunnel implements ChainFunnel { const newEntry = new MinaFunnelCacheEntry(); sharedData.cacheManager.cacheEntries[MinaFunnelCacheEntry.SYMBOL] = newEntry; - const genesisTime = await getGenesisTime(config.graphql); + const pg = postgres(config.archiveConnectionString); + + const genesisTime = await getGenesisTime(pg); const startingBlockTimestamp = (await sharedData.web3.eth.getBlock(startingBlockHeight)) .timestamp as number; @@ -408,7 +355,7 @@ export class MinaFunnel extends BaseFunnel implements ChainFunnel { const slotAsMinaTimestamp = slotToMinaTimestamp(slot, genesisTime, config.slotDuration); - newEntry.updateStartingSlot(slotAsMinaTimestamp, genesisTime); + newEntry.updateStartingSlot(slotAsMinaTimestamp, genesisTime, pg); const cursors = await getPaginationCursors.run(undefined, dbTx); diff --git a/packages/paima-sdk/paima-utils/src/config/loading.ts b/packages/paima-sdk/paima-utils/src/config/loading.ts index 1c6c1140..576efcef 100644 --- a/packages/paima-sdk/paima-utils/src/config/loading.ts +++ b/packages/paima-sdk/paima-utils/src/config/loading.ts @@ -62,8 +62,8 @@ export const CardanoConfigSchema = Type.Object({ export type CardanoConfig = Static; export const MinaConfigSchema = Type.Object({ + archiveConnectionString: Type.String(), archive: Type.String(), - graphql: Type.String(), // k confirmationDepth: Type.Number(), paginationLimit: Type.Number({ default: 50 }),