diff --git a/packages/engine/paima-funnel/src/cde/cardanoPool.ts b/packages/engine/paima-funnel/src/cde/cardanoPool.ts index 36f476783..0197dffa4 100644 --- a/packages/engine/paima-funnel/src/cde/cardanoPool.ts +++ b/packages/engine/paima-funnel/src/cde/cardanoPool.ts @@ -6,14 +6,14 @@ import type { import { ChainDataExtensionDatumType, DEFAULT_FUNNEL_TIMEOUT, timeout } from '@paima/utils'; import { Routes, query } from '@dcspark/carp-client/client/src'; import type { DelegationForPoolResponse } from '@dcspark/carp-client/shared/models/DelegationForPool'; -import { absoluteSlotToEpoch } from '../funnels/carp/funnel.js'; export default async function getCdeData( url: string, extension: ChainDataExtensionCardanoDelegation, fromAbsoluteSlot: number, toAbsoluteSlot: number, - getBlockNumber: (slot: number) => number + getBlockNumber: (slot: number) => number, + absoluteSlotToEpoch: (slot: number) => number ): Promise { const events = await timeout( query(url, Routes.delegationForPool, { diff --git a/packages/engine/paima-funnel/src/funnels/carp/funnel.ts b/packages/engine/paima-funnel/src/funnels/carp/funnel.ts index 37487ecc5..494e87aaf 100644 --- a/packages/engine/paima-funnel/src/funnels/carp/funnel.ts +++ b/packages/engine/paima-funnel/src/funnels/carp/funnel.ts @@ -29,36 +29,42 @@ import { getCardanoEpoch } from '@paima/db'; const delayForWaitingForFinalityLoop = 1000; -// This returns the unix timestamp of the first block in the Shelley era of the -// configured network, and the slot of the corresponding block. -function knownShelleyTime(): { timestamp: number; absoluteSlot: number } { +type Era = { + firstSlot: number; + startEpoch: number; + slotsPerEpoch: number; + timestamp: number; +}; + +function shelleyEra(): Era { switch (ENV.CARDANO_NETWORK) { case 'preview': - return { timestamp: 1666656000, absoluteSlot: 0 }; + return { + firstSlot: 0, + startEpoch: 0, + slotsPerEpoch: 86400, + timestamp: 1666656000, + }; case 'preprod': - return { timestamp: 1655769600, absoluteSlot: 86400 }; + return { + firstSlot: 86400, + startEpoch: 4, + slotsPerEpoch: 432000, + timestamp: 1655769600, + }; case 'mainnet': - return { timestamp: 1596059091, absoluteSlot: 4492800 }; + return { + firstSlot: 4492800, + startEpoch: 208, + slotsPerEpoch: 432000, + timestamp: 1596059091, + }; default: throw new Error('unknown cardano network'); } } -function shelleyEra(): { firstSlot: number; startEpoch: number; slotsPerEpoch: number } { - switch (ENV.CARDANO_NETWORK) { - case 'preview': - return { firstSlot: 0, startEpoch: 0, slotsPerEpoch: 86400 }; - case 'preprod': - return { firstSlot: 86400, startEpoch: 4, slotsPerEpoch: 432000 }; - case 'mainnet': - return { firstSlot: 4492800, startEpoch: 208, slotsPerEpoch: 432000 }; - default: - throw new Error('unknown cardano network'); - } -} - -export function absoluteSlotToEpoch(slot: number): number { - const era = shelleyEra(); +function absoluteSlotToEpoch(era: Era, slot: number): number { const slotRelativeToEra = slot - era.firstSlot; if (slotRelativeToEra >= 0) { @@ -86,14 +92,12 @@ Note: The state pairing only matters after the presync stage is done, so as long as the timestamp of the block specified in START_BLOCKHEIGHT happens after the first Shelley block, we don't need to consider the previous Cardano era (if any). */ -function timestampToAbsoluteSlot(timestamp: number, confirmationDepth: number): number { +function timestampToAbsoluteSlot(era: Era, timestamp: number, confirmationDepth: number): number { const cardanoAvgBlockPeriod = 20; // map timestamps with a delta, since we are waiting for blocks. const confirmationTimeDelta = cardanoAvgBlockPeriod * confirmationDepth; - const era = knownShelleyTime(); - - return timestamp - confirmationTimeDelta - era.timestamp + era.absoluteSlot; + return timestamp - confirmationTimeDelta - era.timestamp + era.firstSlot; } export class CarpFunnel extends BaseFunnel implements ChainFunnel { @@ -111,9 +115,11 @@ export class CarpFunnel extends BaseFunnel implements ChainFunnel { this.readPresyncData.bind(this); this.getDbTx.bind(this); this.bufferedData = null; + this.era = shelleyEra(); } private bufferedData: ChainData[] | null; + private era: Era; public override async readData(blockHeight: number): Promise { if (!this.bufferedData || this.bufferedData[0].blockNumber != blockHeight) { @@ -150,7 +156,8 @@ export class CarpFunnel extends BaseFunnel implements ChainFunnel { this.sharedData.extensions, lastTimestamp, this.cache, - this.confirmationDepth + this.confirmationDepth, + this.era ); const composed = composeChainData(this.bufferedData, grouped); @@ -160,7 +167,8 @@ export class CarpFunnel extends BaseFunnel implements ChainFunnel { data.internalEvents = [] as InternalEvent[]; const epoch = absoluteSlotToEpoch( - timestampToAbsoluteSlot(data.timestamp, this.confirmationDepth) + this.era, + timestampToAbsoluteSlot(this.era, data.timestamp, this.confirmationDepth) ); const prevEpoch = this.cache.getState().epoch; @@ -201,7 +209,8 @@ export class CarpFunnel extends BaseFunnel implements ChainFunnel { Math.min(arg.to, this.cache.getState().startingSlot - 1), slot => { return slot; - } + }, + slot => absoluteSlotToEpoch(this.era, slot) ); return data; }) @@ -249,6 +258,7 @@ export class CarpFunnel extends BaseFunnel implements ChainFunnel { newEntry.updateStartingSlot( timestampToAbsoluteSlot( + shelleyEra(), (await sharedData.web3.eth.getBlock(startingBlockHeight)).timestamp as number, confirmationDepth ) @@ -280,14 +290,15 @@ async function readDataInternal( extensions: ChainDataExtension[], lastTimestamp: number, cache: CarpFunnelCacheEntry, - confirmationDepth: number + confirmationDepth: number, + era: Era ): Promise { // the lower range is exclusive - const min = timestampToAbsoluteSlot(lastTimestamp, confirmationDepth); + const min = timestampToAbsoluteSlot(era, lastTimestamp, confirmationDepth); // the upper range is inclusive const maxElement = data[data.length - 1]; - const max = timestampToAbsoluteSlot(maxElement.timestamp, confirmationDepth); + const max = timestampToAbsoluteSlot(era, maxElement.timestamp, confirmationDepth); cache.updateLastPoint(maxElement.blockNumber, maxElement.timestamp); @@ -312,7 +323,7 @@ async function readDataInternal( const blockNumbers = data.reduce( (dict, data) => { - dict[timestampToAbsoluteSlot(data.timestamp, confirmationDepth)] = data.blockNumber; + dict[timestampToAbsoluteSlot(era, data.timestamp, confirmationDepth)] = data.blockNumber; return dict; }, {} as { [slot: number]: number } @@ -343,7 +354,8 @@ async function readDataInternal( extension, min, Math.min(max, extension.stopSlot || max), - mapSlotToBlockNumber + mapSlotToBlockNumber, + slot => absoluteSlotToEpoch(era, slot) ); return data;