Skip to content

Commit

Permalink
add epoch boundary tracking for cardano delegation primitive
Browse files Browse the repository at this point in the history
  • Loading branch information
ecioppettini committed Dec 6, 2023
1 parent 91c3247 commit 78dcf4b
Show file tree
Hide file tree
Showing 12 changed files with 187 additions and 19 deletions.
10 changes: 8 additions & 2 deletions packages/engine/paima-funnel/src/cde/cardanoPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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,
Expand All @@ -22,13 +23,16 @@ export default async function getCdeData(
DEFAULT_FUNNEL_TIMEOUT
);

return events.map(e => eventToCdeDatum(e, extension, getBlockNumber(e.slot)));
return events.map(e =>
eventToCdeDatum(e, extension, getBlockNumber(e.slot), absoluteSlotToEpoch(e.slot))
);
}

function eventToCdeDatum(
event: DelegationForPoolResponse[0],
extension: ChainDataExtensionCardanoDelegation,
blockNumber: number
blockNumber: number,
epoch: number
): CdeCardanoPoolDatum {
return {
cdeId: extension.cdeId,
Expand All @@ -37,6 +41,8 @@ function eventToCdeDatum(
payload: {
address: event.credential,
pool: event.pool || undefined,
// TODO: get this from carp
epoch,
},
scheduledPrefix: extension.scheduledPrefix,
};
Expand Down
41 changes: 39 additions & 2 deletions packages/engine/paima-funnel/src/funnels/carp/funnel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
logError,
timeout,
} from '@paima/utils';
import type { ChainDataExtensionDatum } from '@paima/sm';
import type { ChainDataExtensionDatum, InternalEvent } from '@paima/sm';
import {
type ChainData,
type ChainDataExtension,
Expand All @@ -23,7 +23,7 @@ import type { ChainFunnel, ReadPresyncDataFrom } from '@paima/runtime';
import getCdePoolData from '../../cde/cardanoPool.js';
import { query } from '@dcspark/carp-client/client/src/index';
import { Routes } from '@dcspark/carp-client/shared/routes';
import { FUNNEL_PRESYNC_FINISHED } from '@paima/utils/src/constants';
import { FUNNEL_PRESYNC_FINISHED, InternalEventType } from '@paima/utils/src/constants';
import { CarpFunnelCacheEntry } from '../FunnelCache.js';

const delayForWaitingForFinalityLoop = 1000;
Expand All @@ -41,6 +41,30 @@ function knownShelleyTime(): number {
}
}

function shelleyEra(): { firstSlot: number; startEpoch: number; slotDuration: number } {
switch (ENV.CARDANO_NETWORK) {
case 'preview':
return { firstSlot: 0, startEpoch: 0, slotDuration: 86400 };
case 'preprod':
return { firstSlot: 86400, startEpoch: 4, slotDuration: 432000 };
case 'mainnet':
return { firstSlot: 4492800, startEpoch: 208, slotDuration: 432000 };
default:
throw new Error('unknown cardano network');
}
}

export function absoluteSlotToEpoch(slot: number): number {
const era = shelleyEra();
const slotRelativeToEra = slot - era.firstSlot;

if (slotRelativeToEra >= 0) {
return era.startEpoch + Math.floor(slotRelativeToEra / era.slotDuration);
} else {
throw new Error('TODO');
}
}

function timestampToAbsoluteSlot(timestamp: number, confirmationDepth: number): number {
const cardanoAvgBlockPeriod = 20;
// map timestamps with a delta, since we are waiting for blocks.
Expand Down Expand Up @@ -110,6 +134,19 @@ export class CarpFunnel extends BaseFunnel implements ChainFunnel {

this.bufferedData = null;

for (const data of composed) {
// TODO: this can be optimized, since if the epoch doesn't change we don't actually need to do anything
if (!data.internalEvents) {
data.internalEvents = [] as InternalEvent[];
data.internalEvents?.push({
type: InternalEventType.CardanoBestEpoch,
epoch: absoluteSlotToEpoch(
timestampToAbsoluteSlot(data.timestamp, this.confirmationDepth)
),
});
}
}

return composed;
}

Expand Down
15 changes: 13 additions & 2 deletions packages/engine/paima-sm/src/cde-cardano-pool.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ENV } from '@paima/utils';
import { createScheduledData, cdeCardanoPoolInsertData } from '@paima/db';
import { createScheduledData, cdeCardanoPoolInsertData, removeOldEntries } from '@paima/db';
import type { SQLUpdate } from '@paima/db';
import type { CdeCardanoPoolDatum } from './types.js';

Expand All @@ -16,7 +16,18 @@ export default async function processDatum(cdeDatum: CdeCardanoPoolDatum): Promi
createScheduledData(scheduledInputData, scheduledBlockHeight),
[
cdeCardanoPoolInsertData,
{ cde_id: cdeId, address: cdeDatum.payload.address, pool: cdeDatum.payload.pool },
{
cde_id: cdeId,
address: cdeDatum.payload.address,
pool: cdeDatum.payload.pool,
epoch: cdeDatum.payload.epoch,
},
],
[
removeOldEntries,
{
address: cdeDatum.payload.address,
},
],
];
return updateList;
Expand Down
22 changes: 22 additions & 0 deletions packages/engine/paima-sm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ChainDataExtensionDatumType,
doLog,
ENV,
InternalEventType,
Network,
SCHEDULED_DATA_ADDRESS,
} from '@paima/utils';
Expand All @@ -24,6 +25,7 @@ import {
getLatestProcessedCdeBlockheight,
getCardanoLatestProcessedCdeSlot,
markCardanoCdeSlotProcessed,
updateCardanoEpoch,
} from '@paima/db';
import Prando from '@paima/prando';

Expand All @@ -36,6 +38,7 @@ import type {
ChainDataExtensionDatum,
GameStateTransitionFunction,
GameStateMachineInitializer,
InternalEvent,
} from './types.js';
export * from './types.js';
export type * from './types.js';
Expand Down Expand Up @@ -168,6 +171,8 @@ const SM: GameStateMachineInitializer = {
dbTx
);

await processInternalEvents(latestChainData.internalEvents, dbTx);

const checkpointName = `game_sm_start`;
await dbTx.query(`SAVEPOINT ${checkpointName}`);
try {
Expand Down Expand Up @@ -359,4 +364,21 @@ async function processUserInputs(
return latestChainData.submittedData.length;
}

async function processInternalEvents(
events: InternalEvent[] | undefined,
dbTx: PoolClient
): Promise<void> {
if (!events) {
return;
}

for (const event of events) {
switch (event.type) {
case InternalEventType.CardanoBestEpoch:
await updateCardanoEpoch.run({ epoch: event.epoch }, dbTx);
break;
}
}
}

export default SM;
6 changes: 6 additions & 0 deletions packages/engine/paima-sm/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
OldERC6551RegistryContract,
ERC6551RegistryContract,
Network,
InternalEventType,
} from '@paima/utils';
import { Type } from '@sinclair/typebox';
import type { Static } from '@sinclair/typebox';
Expand All @@ -25,8 +26,12 @@ export interface ChainData {
blockNumber: number;
submittedData: SubmittedData[];
extensionDatums?: ChainDataExtensionDatum[];
internalEvents?: InternalEvent[];
}

export type InternalEvent = CardanoEpochEvent;
export type CardanoEpochEvent = { type: InternalEventType.CardanoBestEpoch; epoch: number };

export interface PresyncChainData {
network: Network;
blockNumber: number;
Expand Down Expand Up @@ -69,6 +74,7 @@ interface CdeDatumErc6551RegistryPayload {
interface CdeDatumCardanoPoolPayload {
address: string;
pool: string | undefined;
epoch: number;
}

type ChainDataExtensionPayload =
Expand Down
8 changes: 7 additions & 1 deletion packages/node-sdk/paima-db/migrations/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,13 @@ CREATE TABLE emulated_block_heights (

CREATE TABLE cde_cardano_pool_delegation (
cde_id INTEGER NOT NULL,
epoch INTEGER NOT NULL,
address TEXT NOT NULL,
pool TEXT,
PRIMARY KEY (cde_id, address)
PRIMARY KEY (cde_id, epoch, address)
);

CREATE TABLE cardano_last_epoch (
id INTEGER PRIMARY KEY,
epoch INTEGER NOT NULL
);
2 changes: 2 additions & 0 deletions packages/node-sdk/paima-db/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export * from './sql/cde-cardano-pool-delegation.queries.js';
export type * from './sql/cde-cardano-pool-delegation.queries.js';
export * from './sql/cde-tracking-cardano.queries.js';
export type * from './sql/cde-tracking-cardano.queries.js';
export * from './sql/cardano-best-block.queries.js';
export type * from './sql/cardano-best-block.queries.js';

export {
tx,
Expand Down
25 changes: 23 additions & 2 deletions packages/node-sdk/paima-db/src/paima-tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,24 +259,44 @@ const TABLE_DATA_CDE_ERC6551_REGISTRY: TableData = {
const QUERY_CREATE_TABLE_CDE_CARDANO_POOL = `
CREATE TABLE cde_cardano_pool_delegation (
cde_id INTEGER NOT NULL,
epoch INTEGER NOT NULL,
address TEXT NOT NULL,
pool TEXT,
PRIMARY KEY (cde_id, address)
PRIMARY KEY (cde_id, epoch, address)
);
`;

const TABLE_DATA_CDE_CARDANO_POOL: TableData = {
tableName: 'cde_cardano_pool_delegation',
primaryKeyColumns: ['cde_id', 'address'],
primaryKeyColumns: ['cde_id', 'epoch', 'address'],
columnData: packTuples([
['cde_id', 'integer', 'NO', ''],
['epoch', 'integer', 'NO', ''],
['address', 'text', 'NO', ''],
['pool', 'text', 'YES', ''],
]),
serialColumns: [],
creationQuery: QUERY_CREATE_TABLE_CDE_CARDANO_POOL,
};

const QUERY_CREATE_TABLE_CARDANO_LAST_EPOCH = `
CREATE TABLE cardano_last_epoch (
id INTEGER PRIMARY KEY,
epoch INTEGER NOT NULL
);
`;

const TABLE_DATA_CARDANO_LAST_EPOCH: TableData = {
tableName: 'cardano_last_epoch',
primaryKeyColumns: ['id'],
columnData: packTuples([
['id', 'integer', 'NO', ''],
['epoch', 'integer', 'NO', ''],
]),
serialColumns: [],
creationQuery: QUERY_CREATE_TABLE_CARDANO_LAST_EPOCH,
};

const QUERY_CREATE_TABLE_EMULATED = `
CREATE TABLE emulated_block_heights (
deployment_chain_block_height INTEGER PRIMARY KEY,
Expand Down Expand Up @@ -312,4 +332,5 @@ export const TABLES: TableData[] = [
TABLE_DATA_CDE_ERC6551_REGISTRY,
TABLE_DATA_CDE_CARDANO_POOL,
TABLE_DATA_EMULATED,
TABLE_DATA_CARDANO_LAST_EPOCH,
];
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface ICdeCardanoPoolGetAddressDelegationParams {
export interface ICdeCardanoPoolGetAddressDelegationResult {
address: string;
cde_id: number;
epoch: number;
pool: string | null;
}

Expand All @@ -35,6 +36,7 @@ export const cdeCardanoPoolGetAddressDelegation = new PreparedQuery<ICdeCardanoP
export interface ICdeCardanoPoolInsertDataParams {
address: string;
cde_id: number;
epoch: number;
pool: string;
}

Expand All @@ -47,23 +49,59 @@ export interface ICdeCardanoPoolInsertDataQuery {
result: ICdeCardanoPoolInsertDataResult;
}

const cdeCardanoPoolInsertDataIR: any = {"usedParamSet":{"cde_id":true,"address":true,"pool":true},"params":[{"name":"cde_id","required":true,"transform":{"type":"scalar"},"locs":[{"a":90,"b":97}]},{"name":"address","required":true,"transform":{"type":"scalar"},"locs":[{"a":104,"b":112}]},{"name":"pool","required":true,"transform":{"type":"scalar"},"locs":[{"a":119,"b":124},{"a":181,"b":186}]}],"statement":"INSERT INTO cde_cardano_pool_delegation(\n cde_id,\n address,\n pool\n) VALUES (\n :cde_id!,\n :address!,\n :pool!\n) ON CONFLICT (cde_id, address) DO\n UPDATE SET pool = :pool!"};
const cdeCardanoPoolInsertDataIR: any = {"usedParamSet":{"cde_id":true,"address":true,"pool":true,"epoch":true},"params":[{"name":"cde_id","required":true,"transform":{"type":"scalar"},"locs":[{"a":101,"b":108}]},{"name":"address","required":true,"transform":{"type":"scalar"},"locs":[{"a":115,"b":123}]},{"name":"pool","required":true,"transform":{"type":"scalar"},"locs":[{"a":130,"b":135},{"a":212,"b":217}]},{"name":"epoch","required":true,"transform":{"type":"scalar"},"locs":[{"a":142,"b":148}]}],"statement":"INSERT INTO cde_cardano_pool_delegation(\n cde_id,\n address,\n pool,\n epoch\n) VALUES (\n :cde_id!,\n :address!,\n :pool!,\n :epoch!\n) ON CONFLICT (cde_id, epoch, address) DO\n UPDATE SET pool = :pool!"};

/**
* Query generated from SQL:
* ```
* INSERT INTO cde_cardano_pool_delegation(
* cde_id,
* address,
* pool
* pool,
* epoch
* ) VALUES (
* :cde_id!,
* :address!,
* :pool!
* ) ON CONFLICT (cde_id, address) DO
* :pool!,
* :epoch!
* ) ON CONFLICT (cde_id, epoch, address) DO
* UPDATE SET pool = :pool!
* ```
*/
export const cdeCardanoPoolInsertData = new PreparedQuery<ICdeCardanoPoolInsertDataParams,ICdeCardanoPoolInsertDataResult>(cdeCardanoPoolInsertDataIR);


/** 'RemoveOldEntries' parameters type */
export interface IRemoveOldEntriesParams {
address: string;
}

/** 'RemoveOldEntries' return type */
export type IRemoveOldEntriesResult = void;

/** 'RemoveOldEntries' query type */
export interface IRemoveOldEntriesQuery {
params: IRemoveOldEntriesParams;
result: IRemoveOldEntriesResult;
}

const removeOldEntriesIR: any = {"usedParamSet":{"address":true},"params":[{"name":"address","required":true,"transform":{"type":"scalar"},"locs":[{"a":179,"b":187},{"a":238,"b":246}]}],"statement":"DELETE FROM cde_cardano_pool_delegation\nWHERE (cde_id, epoch, address) NOT IN (\n SELECT\n cde_id, epoch, address\n FROM cde_cardano_pool_delegation\n WHERE address = :address!\n ORDER BY epoch DESC\n\tLIMIT 2\n)\nAND address = :address!"};

/**
* Query generated from SQL:
* ```
* DELETE FROM cde_cardano_pool_delegation
* WHERE (cde_id, epoch, address) NOT IN (
* SELECT
* cde_id, epoch, address
* FROM cde_cardano_pool_delegation
* WHERE address = :address!
* ORDER BY epoch DESC
* LIMIT 2
* )
* AND address = :address!
* ```
*/
export const removeOldEntries = new PreparedQuery<IRemoveOldEntriesParams,IRemoveOldEntriesResult>(removeOldEntriesIR);


Loading

0 comments on commit 78dcf4b

Please sign in to comment.