Skip to content

Commit

Permalink
refactor: Sync assets using chain-sync protocol
Browse files Browse the repository at this point in the history
Implements the Ogmios TypeScript client to follow the chain,
removing the heavy-handed solution of diffing the projected data from
`cardano-db-sync`.

- Implements a persistent queue, backed by the PostgreSQL database
- Replaces `DataSyncController` with `ChainFollower` and `Worker`
- Asset table is no-longer dropped in the migration routine

BREAKING CHANGE: Configuration of the asset metadata fetching is now
a single value. `POLLING_INTERVAL_METADATA_SYNC_INITIAL` and
`POLLING_INTERVAL_METADATA_SYNC_ONGOING` are replaced by
'ASSET_METADATA_UPDATE_INTERVAL', which is the number of seconds
before the service checks the registry for updates.
  • Loading branch information
rhyslbw committed Jun 15, 2021
1 parent 3d7ef11 commit 784509e
Show file tree
Hide file tree
Showing 46 changed files with 660 additions and 613 deletions.
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "cardano-ogmios"]
path = cardano-ogmios
url = https://github.com/KtorZ/cardano-ogmios
[submodule "ogmios"]
path = ogmios
url = https://github.com/CardanoSolutions/ogmios.git
1 change: 0 additions & 1 deletion cardano-ogmios
Submodule cardano-ogmios deleted from 6096ae
10 changes: 6 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,13 @@ services:
max-file: "10"
ogmios:
build:
context: cardano-ogmios/server
context: ogmios
target: ogmios
cache_from:
- ktorz/ogmios:latest
- ktorz/ogmios:3.2.0
image: ktorz/ogmios:3.2.0
- cardanosolutions/ogmios:v4.0.0-beta.3
image: cardanosolutions/ogmios:v4.0.0-beta.3
ports:
- ${OGMIOS_PORT:-1337}:1337
restart: on-failure
environment:
- OGMIOS_NETWORK=${NETWORK:-mainnet}
Expand Down
9 changes: 2 additions & 7 deletions nix/nixos/cardano-graphql-service.nix
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,7 @@ in {
type = lib.types.nullOr lib.types.int;
default = null;
};
pollingIntervalMetadataSyncInitial = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
};
pollingIntervalMetadataSyncOngoing = lib.mkOption {
assetMetadataUpdateInterval = lib.mkOption {
type = lib.types.nullOr lib.types.int;
default = null;
};
Expand Down Expand Up @@ -168,8 +164,7 @@ in {
(lib.optionalAttrs (cfg.listenAddress != null) { LISTEN_ADDRESS = cfg.listenAddress; }) //
(lib.optionalAttrs (cfg.metadataServerUri != null) { METADATA_SERVER_URI = toString cfg.metadataServerUri; }) //
(lib.optionalAttrs (cfg.pollingIntervalAdaSupply != null) { POLLING_INTERVAL_ADA_SUPPLY = toString cfg.pollingIntervalAdaSupply; }) //
(lib.optionalAttrs (cfg.pollingIntervalMetadataSyncInitial != null) { POLLING_INTERVAL_METADATA_SYNC_INITIAL = toString cfg.pollingIntervalMetadataSyncInitial; }) //
(lib.optionalAttrs (cfg.pollingIntervalMetadataSyncOngoing != null) { POLLING_INTERVAL_METADATA_SYNC_ONGOING = toString cfg.pollingIntervalMetadataSyncOngoing; }) //
(lib.optionalAttrs (cfg.assetMetadataUpdateInterval != null) { ASSET_METADATA_UPDATE_INTERVAL = toString cfg.assetMetadataUpdateInterval; }) //
(lib.optionalAttrs (cfg.queryDepthLimit != null) { QUERY_DEPTH_LIMIT = toString cfg.queryDepthLimit; }) //
(lib.optionalAttrs (cfg.allowListPath != null) { ALLOW_LIST_PATH = cfg.allowListPath; });
path = with pkgs; [ netcat curl postgresql jq frontend hasura-cli glibc.bin patchelf ];
Expand Down
1 change: 1 addition & 0 deletions ogmios
Submodule ogmios added at 1b7dea
Binary file removed packages-cache/@cardano-ogmios-client-3.2.0.tgz
Binary file not shown.
Binary file not shown.
Binary file removed packages-cache/@cardano-ogmios-schema-3.2.0.tgz
Binary file not shown.
Binary file not shown.
Binary file added packages-cache/aggregate-error-3.1.0.tgz
Binary file not shown.
Binary file added packages-cache/call-bind-1.0.2.tgz
Binary file not shown.
Binary file added packages-cache/cron-parser-3.5.0.tgz
Binary file not shown.
Binary file added packages-cache/delay-5.0.0.tgz
Binary file not shown.
Binary file added packages-cache/fastq-1.11.0.tgz
Binary file not shown.
Binary file added packages-cache/get-intrinsic-1.1.1.tgz
Binary file not shown.
Binary file added packages-cache/is-nan-1.3.2.tgz
Binary file not shown.
Binary file added packages-cache/luxon-1.27.0.tgz
Binary file not shown.
Binary file removed packages-cache/object-hash-2.1.1.tgz
Binary file not shown.
Binary file added packages-cache/object-hash-2.2.0.tgz
Binary file not shown.
Binary file added packages-cache/p-map-4.0.0.tgz
Binary file not shown.
Binary file added packages-cache/pg-boss-6.0.1.tgz
Binary file not shown.
Binary file added packages-cache/uuid-8.3.2.tgz
Binary file not shown.
Binary file added packages-cache/ws-7.4.6.tgz
Binary file not shown.
26 changes: 20 additions & 6 deletions packages/api-cardano-db-hasura/hasura/project/metadata/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,18 @@
insert: insert_assets
select_aggregate: assets_aggregate
select: assets
delete: delete_assets
update: update_assets
custom_column_names: {}
object_relationships:
- name: firstAppearedInBlock
using:
manual_configuration:
remote_table:
schema: public
name: Block
column_mapping:
firstAppearedInSlot: slotNo
array_relationships:
- name: tokenMints
using:
Expand All @@ -75,32 +85,32 @@
permission:
check: {}
columns:
- ticker
- assetId
- assetName
- description
- fingerprint
- firstAppearedInSlot
- logo
- metadataFetchAttempts
- metadataHash
- name
- policyId
- ticker
- url
backend_only: false
select_permissions:
- role: cardano-graphql
permission:
columns:
- ticker
- assetId
- assetName
- description
- fingerprint
- firstAppearedInSlot
- logo
- metadataFetchAttempts
- metadataHash
- name
- policyId
- ticker
- url
filter: {}
limit: 2500
Expand All @@ -109,19 +119,23 @@
- role: cardano-graphql
permission:
columns:
- ticker
- assetId
- assetName
- description
- fingerprint
- firstAppearedInSlot
- logo
- metadataFetchAttempts
- metadataHash
- name
- policyId
- ticker
- url
filter: {}
check: {}
delete_permissions:
- role: cardano-graphql
permission:
filter: {}
- table:
schema: public
name: Block
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ DROP VIEW IF EXISTS
"TransactionOutput",
"Utxo",
"Withdrawal" CASCADE;
DROP TABLE IF EXISTS
"Asset";
DROP INDEX IF EXISTS
idx_block_hash,
idx_tx_hash,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,19 @@ CREATE VIEW "AdaPots" AS
utxo
FROM ada_pots;

CREATE TABLE IF NOT EXISTS "Asset"
AS
SELECT
DISTINCT CONCAT(RIGHT(CONCAT(E'\\', policy), -3), RIGHT(CONCAT(E'\\', name), -3)) as "assetId",
RIGHT(CONCAT(E'\\', name), -3) as "assetName",
CAST(NULL AS TEXT) AS "description",
CAST(NULL AS CHAR(44)) as "fingerprint",
CAST(NULL AS TEXT) AS "logo",
0 AS "metadataFetchAttempts",
CAST(NULL AS CHAR(40)) AS "metadataHash",
CAST(NULL AS TEXT) AS "name",
policy as "policyId",
CAST(NULL AS TEXT) AS "ticker",
CAST(NULL AS TEXT) AS "url"
FROM ma_tx_mint;

ALTER TABLE "Asset" ADD PRIMARY KEY ("assetId");
CREATE TABLE IF NOT EXISTS "Asset" (
"assetId" TEXT PRIMARY KEY,
"assetName" TEXT,
"description" VARCHAR(500),
"fingerprint" CHAR(44),
"firstAppearedInSlot" INT,
"logo" VARCHAR(65536),
"metadataHash" CHAR(40),
"name" VARCHAR(50),
"policyId" TEXT,
"ticker" VARCHAR(5),
"url" VARCHAR(250)
);

CREATE VIEW "Block" AS
SELECT (COALESCE(( SELECT sum((tx.fee)::bigint) AS sum
Expand Down
5 changes: 3 additions & 2 deletions packages/api-cardano-db-hasura/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
],
"dependencies": {
"@cardano-graphql/util": "4.0.0",
"@cardano-ogmios/client": "^3.2.0",
"@cardano-ogmios/client": "^4.0.0-beta.1",
"@emurgo/cardano-serialization-lib-nodejs": "^6.0.0",
"@emurgo/cip14-js": "^2.0.0",
"@graphql-tools/delegate": "^6.0.10",
Expand All @@ -53,9 +53,10 @@
"graphql-bigint": "^1.0.0",
"graphql-request": "^3.4.0",
"graphql-scalars": "^1.2.1",
"object-hash": "^2.1.1",
"object-hash": "^2.2.0",
"p-retry": "^4.2.0",
"pg": "^8.5.1",
"pg-boss": "^6.0.1",
"pg-listen": "^1.6.0",
"set-interval-async": "^2.0.3",
"ts-custom-error": "^3.2.0",
Expand Down
34 changes: 34 additions & 0 deletions packages/api-cardano-db-hasura/src/AssetMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@

export interface Signature {
signature: string
publicKey: string
}

export interface AssetMetadata {
description?: {
value: string
anSignatures: Signature[]
}
logo?: {
value: string
anSignatures: Signature[]
}
name?: {
value: string
anSignatures: Signature[]
}
owner?: Signature
preImage?: {
value: string
hashFn: string
}
subject: string
ticker?: {
value: string
anSignatures: Signature[]
}
url?: {
value: string
anSignatures: Signature[]
}
}
3 changes: 2 additions & 1 deletion packages/api-cardano-db-hasura/src/CardanoNodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ConnectionConfig,
createStateQueryClient,
createTxSubmissionClient,
Schema,
StateQueryClient,
TxSubmissionClient
} from '@cardano-ogmios/client'
Expand Down Expand Up @@ -33,7 +34,7 @@ export class CardanoNodeClient {
return slotNo
}

public async getProtocolParams () {
public async getProtocolParams (): Promise<Schema.ProtocolParametersShelley> {
const protocolParams = await this.stateQueryClient.currentProtocolParameters()
this.logger.debug({ module: 'CardanoNodeClient', protocolParams }, 'getProtocolParams')
return protocolParams
Expand Down
101 changes: 101 additions & 0 deletions packages/api-cardano-db-hasura/src/ChainFollower.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import AssetFingerprint from '@emurgo/cip14-js'
import {
ChainSyncClient,
createChainSyncClient,
isMaryBlock,
Schema
} from '@cardano-ogmios/client'
import { Config } from './Config'
import { Asset } from './graphql_types'
import { HasuraClient } from './HasuraClient'
import PgBoss from 'pg-boss'
import { dummyLogger, Logger } from 'ts-log'

export const assetFingerprint = (policyId: Asset['policyId'], assetName?: Asset['assetName']) =>
new AssetFingerprint(
Buffer.from(policyId, 'hex'),
assetName !== undefined ? Buffer.from(assetName, 'hex') : undefined)
.fingerprint()

export class ChainFollower {
private chainSyncClient: ChainSyncClient
private queue: PgBoss

constructor (
readonly hasuraClient: HasuraClient,
private logger: Logger = dummyLogger,
queueConfig: Config['db']
) {
this.queue = new PgBoss({
application_name: 'cardano-graphql',
...queueConfig
})
}

public async initialize (ogmiosConfig: Config['ogmios']) {
this.logger.info({ module: 'ChainFollower' }, 'Initializing')
this.chainSyncClient = await createChainSyncClient({
rollBackward: async ({ point, tip }, requestNext) => {
if (point !== 'origin') {
this.logger.info(
{ module: 'ChainFollower', tip, rollbackPoint: point }, 'Rolling back'
)
const deleteResult = await this.hasuraClient.deleteAssetsAfterSlot(point.slot)
this.logger.info({ module: 'ChainFollower' }, `Deleted ${deleteResult} assets`)
} else {
this.logger.info({ module: 'ChainFollower' }, 'Rolling back to genesis')
const deleteResult = await this.hasuraClient.deleteAssetsAfterSlot(0)
this.logger.info({ module: 'ChainFollower' }, `Deleted ${deleteResult} assets`)
}
requestNext()
},
rollForward: async ({ block }, requestNext) => {
let b: Schema.BlockMary
if (isMaryBlock(block)) {
b = block.mary as Schema.BlockMary
}
if (b !== undefined) {
for (const tx of b.body) {
for (const entry of Object.entries(tx.body.mint.assets)) {
const [policyId, assetName] = entry[0].split('.')
const assetId = policyId.concat(assetName)
if (!(await this.hasuraClient.hasAsset(assetId))) {
const asset = {
assetId,
assetName,
firstAppearedInSlot: b.header.slot,
fingerprint: assetFingerprint(policyId, assetName),
policyId: `\\x${policyId}`
}
await this.hasuraClient.insertAssets([asset])
const SIX_HOURS = 21600
const THREE_MONTHS = 365
await this.queue.publish('asset-metadata-fetch-initial', { assetId }, {
retryDelay: SIX_HOURS,
retryLimit: THREE_MONTHS
})
}
}
}
}
requestNext()
}
}, { connection: ogmiosConfig })
this.logger.info({ module: 'ChainFollower' }, 'Initialized')
}

public async start (points: Schema.Point[]) {
this.logger.info({ module: 'ChainFollower' }, 'Starting')
await this.queue.start()
await this.chainSyncClient.startSync(points)
}

public async shutdown () {
this.logger.info({ module: 'ChainFollower' }, 'Shutting down')
await this.chainSyncClient.shutdown()
await this.queue.stop()
this.logger.info(
{ module: 'ChainFollower' },
'Shutdown complete')
}
}
7 changes: 3 additions & 4 deletions packages/api-cardano-db-hasura/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ export interface Config {
hasuraUri: string,
jqPath: string,
metadataServerUri: string,
metadataUpdateInterval?: {
assets: number
},
ogmios?: {
host?: string
port?: number
},
pollingInterval: {
adaSupply: number
metadataSync: {
initial: number
ongoing: number
}
}
}
Loading

0 comments on commit 784509e

Please sign in to comment.