diff --git a/definitions/migrations/2.0.0/README.md b/definitions/migrations/2.0.0/README.md index a92837f5..6bf21512 100644 --- a/definitions/migrations/2.0.0/README.md +++ b/definitions/migrations/2.0.0/README.md @@ -1,42 +1,5 @@ # ECA 2.0.0 — AtomicAssets v2 migration notes -## `holder` backfill (release blocker, run out-of-band) - -`2.0.0/atomicassets.sql` adds `atomicassets_assets.holder` as a nullable column -(instant, metadata-only). Existing rows have `holder = NULL` until backfilled to -equal `owner`. Do **not** run a single full-table `UPDATE` — on WAX mainnet -`atomicassets_assets` is ~475M rows / ~211 GB and one statement would rewrite the -whole table inside a transaction and bust the cluster `statement_timeout`. - -Run a batched backfill out-of-band (filler paused or during a low-traffic -window), e.g.: - -```sql --- repeat until 0 rows affected; tune the batch size to keep each statement well --- under statement_timeout. Run as the atomicassets app user (owner), not postgres. -DO $$ -DECLARE - affected integer; -BEGIN - LOOP - UPDATE atomicassets_assets a - SET holder = owner - WHERE ctid IN ( - SELECT ctid FROM atomicassets_assets - WHERE holder IS NULL AND owner IS NOT NULL - LIMIT 50000 - ); - GET DIAGNOSTICS affected = ROW_COUNT; - RAISE NOTICE 'backfilled % rows', affected; - EXIT WHEN affected = 0; - COMMIT; - END LOOP; -END $$; -``` - -Smaller chains (testnet, small mainnets) can run the simple -`UPDATE atomicassets_assets SET holder = owner WHERE holder IS NULL;` directly. - ## Deferred from the upstream OIG PR (handled separately / flagged for audit) - `update_atomicmarket_sales_filters` `nx`/`nb` (non-transferable / non-burnable) diff --git a/definitions/migrations/2.0.0/atomicassets-deferred.sql b/definitions/migrations/2.0.0/atomicassets-deferred.sql deleted file mode 100644 index 1f802e32..00000000 --- a/definitions/migrations/2.0.0/atomicassets-deferred.sql +++ /dev/null @@ -1,11 +0,0 @@ --- Deferred (post-commit, outside transaction) DDL for ECA 2.0.0. --- Run by upgrade-db.ts via a dedicated pool with statement_timeout=1h. --- CREATE INDEX CONCURRENTLY cannot run inside a transaction and can take a long --- time on WAX mainnet's ~475M-row / ~211GB atomicassets_assets table. --- --- Runbook ordering for an existing (non-fresh) deployment: --- 1. 2.0.0/atomicassets.sql ALTERs (instant, in-txn) add the nullable `holder`. --- 2. Batched out-of-band `holder = owner` backfill (see 2.0.0/README.md). --- 3. This concurrent index build. --- IF NOT EXISTS keeps it idempotent across filler restarts / partial builds. -CREATE INDEX CONCURRENTLY IF NOT EXISTS atomicassets_assets_holder_btree ON atomicassets_assets USING btree (holder); diff --git a/definitions/migrations/2.0.0/atomicassets.sql b/definitions/migrations/2.0.0/atomicassets.sql index 101cb875..d5e48ae3 100644 --- a/definitions/migrations/2.0.0/atomicassets.sql +++ b/definitions/migrations/2.0.0/atomicassets.sql @@ -9,15 +9,6 @@ -- DROP ... CASCADE of dependent atomicmarket views. -- ============================================================================ --- Dual ownership (renting): `holder` = current possessor, `owner` = real owner. --- New rows are populated by the filler (logmint/logtransfer/logmove). Existing --- rows must be backfilled `holder = owner` ONCE, OUT-OF-BAND and BATCHED — a --- single `UPDATE atomicassets_assets SET holder = owner` here would rewrite the --- whole table inside the migration transaction and bust statement_timeout on --- WAX. See 2.0.0/README.md for the batched backfill. (Release blocker — tracked --- in the v2 audit.) -ALTER TABLE atomicassets_assets ADD COLUMN IF NOT EXISTS holder character varying(12); - -- Collection author swaps (pending acceptance): the proposed new author + the -- acceptance date, surfaced until accept/reject clears them. ALTER TABLE atomicassets_collections ADD COLUMN IF NOT EXISTS new_author_name character varying(12); @@ -31,34 +22,3 @@ ALTER TABLE atomicassets_schemas ADD COLUMN IF NOT EXISTS types jsonb[] NOT NULL ALTER TABLE atomicassets_templates ADD COLUMN IF NOT EXISTS mutable_data jsonb; ALTER TABLE atomicassets_templates ADD COLUMN IF NOT EXISTS deleted_at_block bigint; ALTER TABLE atomicassets_templates ADD COLUMN IF NOT EXISTS deleted_at_time bigint; - --- Asset moves (rental holder changes via the `move` / `logmove` action). -CREATE TABLE IF NOT EXISTS atomicassets_moves ( - move_id bigint NOT NULL, - contract character varying(12) NOT NULL, - "sender" character varying(12) NOT NULL, - "recipient" character varying(12) NOT NULL, - memo character varying(256) NOT NULL, - txid bytea NOT NULL, - created_at_block bigint NOT NULL, - created_at_time bigint NOT NULL, - CONSTRAINT atomicassets_moves_pkey PRIMARY KEY (contract, move_id) -); - -CREATE TABLE IF NOT EXISTS atomicassets_moves_assets ( - move_id bigint NOT NULL, - contract character varying(12) NOT NULL, - "index" integer NOT NULL, - asset_id bigint NOT NULL, - CONSTRAINT atomicassets_moves_assets_pkey PRIMARY KEY (move_id, contract, asset_id) -); - --- Indexes for the NEW (empty) moves tables only — these are instant. The --- holder index on the existing ~475M-row atomicassets_assets is built --- CONCURRENTLY in 2.0.0/atomicassets-deferred.sql (a non-CONCURRENTLY build --- here would lock + bust statement_timeout inside the migration txn and --- crash-loop the filler). -CREATE INDEX IF NOT EXISTS atomicassets_moves_sender ON atomicassets_moves USING btree (sender); -CREATE INDEX IF NOT EXISTS atomicassets_moves_recipient ON atomicassets_moves USING btree (recipient); -CREATE INDEX IF NOT EXISTS atomicassets_moves_created_at_time ON atomicassets_moves USING btree (created_at_time); -CREATE INDEX IF NOT EXISTS atomicassets_moves_assets_asset_id ON atomicassets_moves_assets USING btree (asset_id); diff --git a/definitions/tables/atomicassets_tables.sql b/definitions/tables/atomicassets_tables.sql index 928e4536..d522342c 100644 --- a/definitions/tables/atomicassets_tables.sql +++ b/definitions/tables/atomicassets_tables.sql @@ -6,7 +6,6 @@ CREATE TABLE IF NOT EXISTS atomicassets_assets ( schema_name character varying(12) NOT NULL, template_id bigint, owner character varying(12), - holder character varying(12), mutable_data jsonb, immutable_data jsonb, template_mint INT, @@ -154,27 +153,6 @@ CREATE TABLE IF NOT EXISTS atomicassets_transfers_assets ( CONSTRAINT atomicassets_transfers_assets_pkey PRIMARY KEY (transfer_id, contract, asset_id) ); --- v2: asset moves (rental holder changes via the `move`/`logmove` action) -- -CREATE TABLE IF NOT EXISTS atomicassets_moves ( - move_id bigint NOT NULL, - contract character varying(12) NOT NULL, - "sender" character varying(12) NOT NULL, - "recipient" character varying(12) NOT NULL, - memo character varying(256) NOT NULL, - txid bytea NOT NULL, - created_at_block bigint NOT NULL, - created_at_time bigint NOT NULL, - CONSTRAINT atomicassets_moves_pkey PRIMARY KEY (contract, move_id) -); - -CREATE TABLE IF NOT EXISTS atomicassets_moves_assets ( - move_id bigint NOT NULL, - contract character varying(12) NOT NULL, - "index" integer NOT NULL, - asset_id bigint NOT NULL, - CONSTRAINT atomicassets_moves_assets_pkey PRIMARY KEY (move_id, contract, asset_id) -); - -- FOREIGN KEYS -- DO $$ BEGIN @@ -270,7 +248,6 @@ CREATE INDEX IF NOT EXISTS atomicassets_assets_collection_name_btree ON atomicas CREATE INDEX IF NOT EXISTS atomicassets_assets_template_id_asset_id ON atomicassets_assets (template_id, asset_id); CREATE INDEX IF NOT EXISTS atomicassets_assets_schema_name ON atomicassets_assets USING btree (schema_name); CREATE INDEX IF NOT EXISTS atomicassets_assets_owner_btree ON atomicassets_assets USING btree (owner); -CREATE INDEX IF NOT EXISTS atomicassets_assets_holder_btree ON atomicassets_assets USING btree (holder); CREATE INDEX IF NOT EXISTS atomicassets_assets_mutable_data ON atomicassets_assets USING gin (mutable_data); CREATE INDEX IF NOT EXISTS atomicassets_assets_immutable_data ON atomicassets_assets USING gin (immutable_data); CREATE INDEX IF NOT EXISTS atomicassets_assets_template_mint ON atomicassets_assets USING btree (template_mint); @@ -328,9 +305,3 @@ CREATE INDEX IF NOT EXISTS atomicassets_transfers_recipient ON atomicassets_tran CREATE INDEX IF NOT EXISTS atomicassets_transfers_created_at_time ON atomicassets_transfers USING btree (created_at_time); CREATE INDEX IF NOT EXISTS atomicassets_transfers_assets_asset_id ON atomicassets_transfers_assets USING btree (asset_id); - -CREATE INDEX IF NOT EXISTS atomicassets_moves_sender ON atomicassets_moves USING btree (sender); -CREATE INDEX IF NOT EXISTS atomicassets_moves_recipient ON atomicassets_moves USING btree (recipient); -CREATE INDEX IF NOT EXISTS atomicassets_moves_created_at_time ON atomicassets_moves USING btree (created_at_time); - -CREATE INDEX IF NOT EXISTS atomicassets_moves_assets_asset_id ON atomicassets_moves_assets USING btree (asset_id); diff --git a/definitions/views/atomicassets_assets_master.sql b/definitions/views/atomicassets_assets_master.sql index f3b67824..b153d218 100644 --- a/definitions/views/atomicassets_assets_master.sql +++ b/definitions/views/atomicassets_assets_master.sql @@ -64,8 +64,7 @@ CREATE OR REPLACE VIEW atomicassets_assets_master AS asset.burned_by_account, asset.burned_at_block, asset.burned_at_time, asset.updated_at_block, asset.updated_at_time, asset.transferred_at_block, asset.transferred_at_time, - asset.minted_at_block, asset.minted_at_time, - asset.holder + asset.minted_at_block, asset.minted_at_time FROM atomicassets_assets asset LEFT JOIN atomicassets_templates "template" ON ( diff --git a/definitions/views/atomicassets_moves_master.sql b/definitions/views/atomicassets_moves_master.sql deleted file mode 100644 index 5248270b..00000000 --- a/definitions/views/atomicassets_moves_master.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE OR REPLACE VIEW atomicassets_moves_master AS - SELECT - t1.move_id, t1.contract, - t1.sender sender_name, t1.recipient recipient_name, t1.memo, - encode(t1.txid::bytea, 'hex') txid, - ARRAY( - SELECT asset_t.asset_id - FROM atomicassets_moves_assets asset_t - WHERE asset_t.move_id = t1.move_id AND asset_t.contract = t1.contract - ) assets, - t1.created_at_block, t1.created_at_time - FROM atomicassets_moves t1 diff --git a/src/api/namespaces/atomicassets/filler.ts b/src/api/namespaces/atomicassets/filler.ts index 768baf50..9eb985be 100644 --- a/src/api/namespaces/atomicassets/filler.ts +++ b/src/api/namespaces/atomicassets/filler.ts @@ -104,21 +104,3 @@ export async function fillTransfers( })); } -export async function fillMoves( - db: DB, contract: string, moves: any[], formatter: (_: any) => any, - view: string, hook?: FillerHook -): Promise { - const assetIDs: string[] = []; - - for (const move of moves) { - assetIDs.push(...move.assets); - } - - const filler = new AssetFiller(db, contract, assetIDs, formatter, view, hook); - - return await Promise.all(moves.map(async (move) => { - move.assets = await filler.fill(move.assets); - - return move; - })); -} diff --git a/src/api/namespaces/atomicassets/format.ts b/src/api/namespaces/atomicassets/format.ts index c56b0467..2b070bc3 100644 --- a/src/api/namespaces/atomicassets/format.ts +++ b/src/api/namespaces/atomicassets/format.ts @@ -113,7 +113,3 @@ export function formatOffer(row: any): any { export function formatTransfer(row: any): any { return {...row}; } - -export function formatMove(row: any): any { - return {...row}; -} diff --git a/src/api/namespaces/atomicassets/handlers/moves.ts b/src/api/namespaces/atomicassets/handlers/moves.ts deleted file mode 100644 index 07b14225..00000000 --- a/src/api/namespaces/atomicassets/handlers/moves.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { buildBoundaryFilter, RequestValues } from '../../utils'; -import { AtomicAssetsContext } from '../index'; -import QueryBuilder from '../../../builder'; -import { buildAssetFilter, hasAssetFilter } from '../utils'; -import { FilteredValues, filterQueryArgs } from '../../validation'; -import { ApiError } from '../../../error'; - -export async function getRawMovesAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { - const maxLimit = ctx.coreArgs.limits?.moves || 100; - const args = await filterQueryArgs(params, { - page: {type: 'int', min: 1, default: 1}, - limit: {type: 'int', min: 1, max: maxLimit, default: Math.min(maxLimit, 100)}, - sort: {type: 'string', allowedValues: ['created'], default: 'created'}, - order: {type: 'string', allowedValues: ['asc', 'desc'], default: 'desc'}, - - asset_id: {type: 'list[id]'}, - - collection_blacklist: {type: 'list[name]'}, - collection_whitelist: {type: 'list[name]'}, - - account: {type: 'list[name]'}, - sender: {type: 'list[name]'}, - recipient: {type: 'list[name]'}, - memo: {type: 'string', min: 1}, - match_memo: {type: 'string', min: 1}, - - hide_contracts: {type: 'bool'}, - - count: {type: 'bool'} - }); - - if (args.account.length && (args.sender.length || args.recipient.length)) { - throw new ApiError('Can not use account and sender or recipient filters at the same time', 400); - } - - const unionArgsList = getUnionArgsList(args); - const query = unionArgsList.length - ? await buildUnionQuery(unionArgsList, args, params, ctx) - : await buildMoveQuery(args, params, ctx); - - if (args.count) { - const countQuery = await ctx.db.query( - 'SELECT COUNT(*) counter FROM (' + query.buildString() + ') x', - query.buildValues() - ); - - return countQuery.rows[0].counter; - } - - const sortColumnMapping: { [key: string]: string } = { - created: 'move_id' - }; - - query.append('ORDER BY ' + sortColumnMapping[args.sort] + ' ' + args.order); - query.paginate(args.page, args.limit); - - return await ctx.db.query(query.buildString(), query.buildValues()); -} - -async function buildUnionQuery(unionArgsList: any[], args: Record, params: RequestValues, ctx: AtomicAssetsContext): Promise { - const query = new QueryBuilder(''); - - const unions = []; - for (const unionArgs of unionArgsList) { - const union = await buildMoveQuery(unionArgs, params, ctx, query.buildValues()); - union.append('ORDER BY move_id ' + args.order); - union.append(`LIMIT ${union.addVariable(args.page * args.limit)}`); - - unions.push(`(\n${union.buildString()}\n)`); - query.setVars(union.buildValues()); - } - - return new QueryBuilder(unions.join(' UNION '), query.buildValues()); -} - -function getUnionArgsList>(args: Record): T[] { - if (args.count || (args.sort !== 'created') || ((args.sender.length > 0) && (args.recipient.length > 0))) { - return []; // unable to use unions - } - - if (args.sender.length > 1) { - return args.sender.map((sender: string) => ({...args, sender: [sender]})); - } - - if (args.recipient.length > 1) { - return args.recipient.map((recipient: string) => ({...args, recipient: [recipient]})); - } - - const result = []; - for (const account of args.account) { - result.push({...args, account: [], sender: [account]} as any as T); - result.push({...args, account: [], recipient: [account]} as any as T); - } - return result; -} - -async function buildMoveQuery(args: Record, params: RequestValues, ctx: AtomicAssetsContext, queryValues: any[] = []): Promise { - const query = new QueryBuilder('SELECT DISTINCT move.* FROM atomicassets_moves_master move', queryValues); - query.equal('contract', ctx.coreArgs.atomicassets_account); - - if (args.account.length) { - const varName = query.addVariable(args.account); - query.addCondition(`(sender_name = ANY (${varName}) OR recipient_name = ANY (${varName}))`); - } - - if (args.sender.length) { - query.equalMany('sender_name', args.sender); - } - - if (args.recipient.length) { - query.equalMany('recipient_name', args.recipient); - } - - if (args.memo) { - query.equal('memo', args.memo); - } - - if (args.match_memo) { - query.addCondition( - 'memo ILIKE ' + query.addVariable('%' + query.escapeLikeVariable(args.match_memo) + '%') - ); - } - - if (hasAssetFilter(params, ['asset_id'])) { - const assetQuery = new QueryBuilder('SELECT move_asset.move_id move_join_id FROM atomicassets_moves_assets move_asset, atomicassets_assets asset', query.buildValues()); - - assetQuery.equal('asset.contract', ctx.coreArgs.atomicassets_account); - - assetQuery.join('asset', 'move_asset', ['contract', 'asset_id']); - - await buildAssetFilter(params, assetQuery, {assetTable: '"asset"', allowDataFilter: false}); - - query.appendToBase(` JOIN (${assetQuery.buildString()}) assets ON move.move_id = assets.move_join_id`); - - query.setVars(assetQuery.buildValues()); - } - - if (args.asset_id.length) { - query.addCondition( - 'EXISTS(' + - 'SELECT * FROM atomicassets_moves_assets asset ' + - 'WHERE move.contract = asset.contract AND move.move_id = asset.move_id AND ' + - 'asset_id = ANY (' + query.addVariable(args.asset_id) + ')' + - ') ' - ); - } - - /* - the collection_whitelist and collection_blacklist filters have + 0 on the move.move_id - to prevent postgres from rewriting the query in very inefficient way. - we want the outer query to lead using the recipient or sender index, and only validate those results - against the lists - */ - - if (args.collection_blacklist.length) { - query.addCondition( - 'NOT EXISTS(' + - 'SELECT * FROM atomicassets_moves_assets move_asset, atomicassets_assets asset ' + - 'WHERE move_asset.contract = move.contract AND move_asset.move_id = move.move_id + 0 AND ' + - 'move_asset.contract = asset.contract AND move_asset.asset_id = asset.asset_id AND ' + - 'asset.collection_name = ANY (' + query.addVariable(args.collection_blacklist) + ')' + - ') ' - ); - } - - if (args.collection_whitelist.length) { - query.addCondition( - 'NOT EXISTS(' + - 'SELECT * FROM atomicassets_moves_assets move_asset, atomicassets_assets asset ' + - 'WHERE move_asset.contract = move.contract AND move_asset.move_id = move.move_id + 0 AND ' + - 'move_asset.contract = asset.contract AND move_asset.asset_id = asset.asset_id AND ' + - 'NOT (asset.collection_name = ANY (' + query.addVariable(args.collection_whitelist) + '))' + - ')' - ); - } - - if (args.hide_contracts) { - query.addCondition( - 'NOT EXISTS(SELECT * FROM contract_codes ' + - 'WHERE (account = move.recipient_name OR account = move.sender_name) AND NOT (account = ANY(' + - query.addVariable([...args.account, ...args.sender, ...args.recipient]) + - ')))' - ); - } - - await buildBoundaryFilter(params, query, 'move_id', 'int', 'created_at_time'); - - return query; -} - - -export async function getMovesCountAction(params: RequestValues, ctx: AtomicAssetsContext): Promise { - return getRawMovesAction({...params, count: 'true'}, ctx); -} diff --git a/src/api/namespaces/atomicassets/index.ts b/src/api/namespaces/atomicassets/index.ts index d2eb8de5..30802310 100644 --- a/src/api/namespaces/atomicassets/index.ts +++ b/src/api/namespaces/atomicassets/index.ts @@ -8,9 +8,8 @@ import { configEndpoints } from './routes/config'; import { schemasEndpoints } from './routes/schemas'; import { templatesEndpoints } from './routes/templates'; import { atomicassetsComponents } from './openapi'; -import { formatAsset, formatMove, formatOffer, formatTransfer } from './format'; +import { formatAsset, formatOffer, formatTransfer } from './format'; import { TransferApi } from './routes/transfers'; -import { MoveApi } from './routes/moves'; import { OfferApi } from './routes/offers'; import { accountsEndpoints } from './routes/accounts'; import ApiNotificationReceiver from '../../notification'; @@ -69,11 +68,6 @@ export class AtomicAssetsNamespace extends ApiNamespace { 'atomicassets_transfers_master', formatTransfer, 'atomicassets_assets_master', formatAsset ); - const moveApi = new MoveApi( - this, server, 'Move', - 'atomicassets_moves_master', formatMove, - 'atomicassets_assets_master', formatAsset - ); const offerApi = new OfferApi( this, server, 'Offer', 'atomicassets_offers_master', formatOffer, @@ -88,7 +82,6 @@ export class AtomicAssetsNamespace extends ApiNamespace { endpointsDocs.push(offerApi.endpoints(router)); endpointsDocs.push(transferApi.endpoints(router)); - endpointsDocs.push(moveApi.endpoints(router)); endpointsDocs.push(accountsEndpoints(this, server, router)); endpointsDocs.push(burnEndpoints(this, server, router)); endpointsDocs.push(configEndpoints(this, server, router)); @@ -126,11 +119,6 @@ export class AtomicAssetsNamespace extends ApiNamespace { 'atomicassets_transfers_master', formatTransfer, 'atomicassets_assets_master', formatAsset ); - const moveApi = new MoveApi( - this, server, 'Move', - 'atomicassets_moves_master', formatMove, - 'atomicassets_assets_master', formatAsset - ); const offerApi = new OfferApi( this, server, 'Offer', 'atomicassets_offers_master', formatOffer, @@ -139,7 +127,6 @@ export class AtomicAssetsNamespace extends ApiNamespace { assetApi.sockets(notification); transferApi.sockets(notification); - moveApi.sockets(notification); offerApi.sockets(notification); } } diff --git a/src/api/namespaces/atomicassets/openapi.ts b/src/api/namespaces/atomicassets/openapi.ts index 1e1b4ae5..f7f15247 100644 --- a/src/api/namespaces/atomicassets/openapi.ts +++ b/src/api/namespaces/atomicassets/openapi.ts @@ -41,25 +41,6 @@ export function generateTransferSchema(assetSchema: string): any { }; } -export function generateMoveSchema(assetSchema: string): any { - return { - type: 'object', - properties: { - contract: {type: 'string'}, - move_id: {type: 'string'}, - sender_name: {type: 'string'}, - recipient_name: {type: 'string'}, - memo: {type: 'string'}, - txid: {type: 'string'}, - - assets: {type: 'array', items: {'$ref': '#/components/schemas/' + assetSchema}}, - - created_at_block: {type: 'string'}, - created_at_time: {type: 'string'} - } - }; -} - export const atomicassetsComponents = { 'Asset': { type: 'object', @@ -67,7 +48,6 @@ export const atomicassetsComponents = { contract: {type: 'string'}, asset_id: {type: 'string'}, owner: {type: 'string'}, - holder: {type: 'string'}, name: {type: 'string'}, is_transferable: {type: 'boolean'}, is_burnable: {type: 'boolean'}, @@ -270,8 +250,7 @@ export const atomicassetsComponents = { } }, 'Offer': generateOfferSchema('Asset'), - 'Transfer': generateTransferSchema('Asset'), - 'Move': generateMoveSchema('Asset') + 'Transfer': generateTransferSchema('Asset') }; export const greylistFilterParameters = [ @@ -330,13 +309,6 @@ export const extendedAssetFilterParameters = [ required: false, schema: {type: 'string'} }, - { - name: 'holder', - in: 'query', - description: 'Filter by holder', - required: false, - schema: {type: 'string'} - }, { name: 'match', in: 'query', diff --git a/src/api/namespaces/atomicassets/routes/moves.ts b/src/api/namespaces/atomicassets/routes/moves.ts deleted file mode 100644 index 03fa9916..00000000 --- a/src/api/namespaces/atomicassets/routes/moves.ts +++ /dev/null @@ -1,190 +0,0 @@ -import express from 'express'; - -import { AtomicAssetsContext, AtomicAssetsNamespace } from '../index'; -import { HTTPServer } from '../../../server'; -import { RequestValues } from '../../utils'; -import { FillerHook, fillMoves } from '../filler'; -import { - dateBoundaryParameters, - getOpenAPI3Responses, - getPrimaryBoundaryParams, - paginationParameters -} from '../../../docs'; -import { greylistFilterParameters } from '../openapi'; -import ApiNotificationReceiver from '../../../notification'; -import { createSocketApiNamespace } from '../../../utils'; -import { NotificationData } from '../../../../filler/notifier'; -import { getRawMovesAction, getMovesCountAction } from '../handlers/moves'; - -export class MoveApi { - constructor( - readonly core: AtomicAssetsNamespace, - readonly server: HTTPServer, - readonly schema: string, - readonly moveView: string, - readonly moveFormatter: (_: any) => any, - readonly assetView: string, - readonly assetFormatter: (_: any) => any, - readonly fillerHook?: FillerHook - ) { } - - getMovesAction = async (params: RequestValues, ctx: AtomicAssetsContext): Promise => { - const result = await getRawMovesAction(params, ctx); - - return await fillMoves( - this.server, this.core.args.atomicassets_account, - result.rows.map(this.moveFormatter), - this.assetFormatter, this.assetView, this.fillerHook - ); - }; - - endpoints(router: express.Router): any { - const {caching, returnAsJSON} = this.server.web; - - router.all('/v1/moves', caching(), returnAsJSON(this.getMovesAction, this.core)); - router.all('/v1/moves/_count', caching(), returnAsJSON(getMovesCountAction, this.core)); - - return { - tag: { - name: 'moves', - description: 'Moves' - }, - paths: { - '/v1/moves': { - get: { - tags: ['moves'], - summary: 'Fetch moves', - parameters: [ - { - name: 'account', - in: 'query', - description: 'Notified account (can be sender or recipient) - separate multiple with ","', - required: false, - schema: {type: 'string'} - }, - { - name: 'sender', - in: 'query', - description: 'Move sender - separate multiple with ","', - required: false, - schema: {type: 'string'} - }, - { - name: 'recipient', - in: 'query', - description: 'Move recipient - separate multiple with ","', - required: false, - schema: {type: 'string'} - }, - { - name: 'memo', - in: 'query', - description: 'Search for exact memo', - required: false, - schema: {type: 'string'} - }, - { - name: 'match_memo', - in: 'query', - description: 'Search for text in memo', - required: false, - schema: {type: 'string'} - }, - { - name: 'asset_id', - in: 'query', - description: 'only moves which contain this asset_id - separate multiple with ","', - required: false, - schema: {type: 'string'} - }, - { - name: 'template_id', - in: 'query', - description: 'only moves which contain assets of this template - separate multiple with ","', - required: false, - schema: {type: 'string'} - }, - { - name: 'schema_name', - in: 'query', - description: 'only moves which contain assets of this schema - separate multiple with ","', - required: false, - schema: {type: 'string'} - }, - { - name: 'collection_name', - in: 'query', - description: 'only moves which contain assets of this collection - separate multiple with ","', - required: false, - schema: {type: 'string'} - }, - { - name: 'hide_contracts', - in: 'query', - description: 'dont show moves from or to accounts that have code deployed', - required: false, - schema: {type: 'boolean'} - }, - ...getPrimaryBoundaryParams('move_id'), - ...dateBoundaryParameters, - ...greylistFilterParameters, - ...paginationParameters, - { - name: 'sort', - in: 'query', - description: 'Column to sort', - required: false, - schema: { - type: 'string', - enum: ['created'], - default: 'created' - } - } - ], - responses: getOpenAPI3Responses([200, 500], {type: 'array', items: {'$ref': '#/components/schemas/' + this.schema}}) - } - } - } - }; - } - - sockets(notification: ApiNotificationReceiver): void { - const namespace = createSocketApiNamespace(this.server, this.core.path + '/v1/moves'); - - notification.onData('moves', async (notifications: NotificationData[]) => { - const moveIDs = notifications.filter(row => row.type === 'trace').map(row => row.data.trace.global_sequence); - const query = await this.server.database.query( - 'SELECT * FROM ' + this.moveView + ' WHERE contract = $1 AND move_id = ANY($2)', - [this.core.args.atomicassets_account, moveIDs] - ); - - const moves = await fillMoves( - this.server, this.core.args.atomicassets_account, - query.rows.map((row) => this.moveFormatter(row)), - this.assetFormatter, this.assetView, this.fillerHook - ); - - for (const notification of notifications) { - if (notification.type === 'trace' && notification.data.trace) { - const trace = notification.data.trace; - - if (trace.act.account !== this.core.args.atomicassets_account) { - continue; - } - - if (trace.act.name === 'logmove') { - namespace.emit('new_move', { - transaction: notification.data.tx, - block: notification.data.block, - trace: trace, - move_id: trace.global_sequence, - move: moves.find(row => String(row.move_id) === String(trace.global_sequence)) - }); - } - } else if (notification.type === 'fork') { - namespace.emit('fork', {block_num: notification.data.block.block_num}); - } - } - }); - } -} diff --git a/src/api/namespaces/atomicassets/utils.ts b/src/api/namespaces/atomicassets/utils.ts index 15dccf4a..c3e7b7ee 100644 --- a/src/api/namespaces/atomicassets/utils.ts +++ b/src/api/namespaces/atomicassets/utils.ts @@ -176,7 +176,6 @@ export function buildDataConditions(values: FilterValues, query: QueryBuilder, o const assetFilters: FiltersDefinition = { asset_id: {type: 'list[id]'}, owner: {type: 'list[name]'}, - holder: {type: 'list[name]'}, burned: {type: 'bool'}, template_id: {type: 'list[id]'}, collection_name: {type: 'list[name]'}, @@ -208,10 +207,6 @@ export async function buildAssetFilter( query.equalMany(options.assetTable + '.owner', args.owner); } - if (args.holder.length) { - query.equalMany(options.assetTable + '.holder', args.holder); - } - if (args.template_id.length) { if ((args.template_id.length === 1) && (args.template_id[0] === 'null')) { query.isNull(options.assetTable + '.template_id'); diff --git a/src/filler/handlers/atomicassets/index.ts b/src/filler/handlers/atomicassets/index.ts index e967c906..849329c2 100644 --- a/src/filler/handlers/atomicassets/index.ts +++ b/src/filler/handlers/atomicassets/index.ts @@ -43,10 +43,6 @@ export enum AtomicAssetsUpdatePriority { TABLE_TEMPLATES = ATOMICASSETS_BASE_PRIORITY + 40, ACTION_MINT_ASSET = ATOMICASSETS_BASE_PRIORITY + 50, ACTION_UPDATE_ASSET = ATOMICASSETS_BASE_PRIORITY + 60, - // v2: the `holders` table delta is the AUTHORITATIVE holder source and must - // run AFTER the asset action handlers (mint/transfer/move/burn) so it - // reconciles `holder` for rented assets after they optimistically set it. - TABLE_HOLDERS = ATOMICASSETS_BASE_PRIORITY + 70, ACTION_CREATE_OFFER = ATOMICASSETS_BASE_PRIORITY + 80, ACTION_UPDATE_OFFER = ATOMICASSETS_BASE_PRIORITY + 90, LOGS = 0 @@ -75,7 +71,7 @@ export default class AtomicAssetsHandler extends ContractHandler { const views = [ 'atomicassets_asset_mints_master', 'atomicassets_templates_master', 'atomicassets_schemas_master', 'atomicassets_collections_master', 'atomicassets_offers_master', - 'atomicassets_transfers_master', 'atomicassets_moves_master' + 'atomicassets_transfers_master' ]; if (!existsQuery.rows[0].exists) { @@ -131,7 +127,6 @@ export default class AtomicAssetsHandler extends ContractHandler { await client.query(fs.readFileSync('./definitions/views/atomicassets_collections_master.sql', {encoding: 'utf8'})); await client.query(fs.readFileSync('./definitions/views/atomicassets_templates_master.sql', {encoding: 'utf8'})); await client.query(fs.readFileSync('./definitions/views/atomicassets_assets_master.sql', {encoding: 'utf8'})); - await client.query(fs.readFileSync('./definitions/views/atomicassets_moves_master.sql', {encoding: 'utf8'})); } } diff --git a/src/filler/handlers/atomicassets/processors/assets.integration.test.ts b/src/filler/handlers/atomicassets/processors/assets.integration.test.ts index 7b4cecef..51360d73 100644 --- a/src/filler/handlers/atomicassets/processors/assets.integration.test.ts +++ b/src/filler/handlers/atomicassets/processors/assets.integration.test.ts @@ -7,9 +7,7 @@ import { createBlock, createTx, createActionTrace, - createContractRow, processActionTrace, - processContractRow, createTestTransaction, } from '../../test-helper'; import { assetProcessor } from './assets'; @@ -21,9 +19,7 @@ import { LogSetDataActionData, LogTransferActionData, LogBackAssetActionData, - LogMoveActionData, } from '../types/actions'; -import { HoldersTableRow } from '../types/tables'; import { ModuleLoader } from '../../../modules'; import { eosioTimestampToDate } from '../../../../utils/eosio'; @@ -683,377 +679,4 @@ describe('assetProcessor', () => { expect(result.rows[0].amount).to.equal('80000'); }); }); - - // ----------------------------------------------------------------------- - // Shared helper: mint an asset with a known owner so move/holder handlers - // have a target row to update. Mirrors the chunking-block helper but is - // available to all the move/holder describe blocks below. - // ----------------------------------------------------------------------- - async function mintAsset(assetId: string, owner: string): Promise { - const mintTrace = createActionTrace(CONTRACT, 'logmint', { - asset_id: assetId, - authorized_minter: 'minter1', - collection_name: 'testcol11111', - schema_name: 'testschema11', - template_id: 1, - new_asset_owner: owner, - immutable_data: [], - mutable_data: [], - backed_tokens: [], - immutable_template_data: [], - } as LogMintAssetActionData); - await processActionTrace(processor, db, createBlock(), createTx(), mintTrace); - } - - /** - * Process a logmove action for the given asset_ids and return the move trace - * (move_id === trace.global_sequence). `block` lets callers pin the timestamp. - */ - async function moveAssets( - from: string, - to: string, - assetIds: string[], - memo: string, - block = createBlock() - ): Promise> { - const moveTrace = createActionTrace(CONTRACT, 'logmove', { - collection_name: 'testcol11111', - owner: from, - from, - to, - asset_ids: assetIds, - memo, - } as LogMoveActionData); - await processActionTrace(processor, db, block, createTx(), moveTrace); - return moveTrace; - } - - /** - * Process a `holders` table delta for an asset. `present` toggles the row's - * presence (rental start vs. lease end). `block` lets callers pin the timestamp. - */ - async function processHoldersDelta( - assetId: string, - holder: string, - owner: string, - present: boolean, - block = createBlock() - ): Promise { - const delta = createContractRow( - CONTRACT, - 'holders', - { asset_id: assetId, holder, owner } as HoldersTableRow, - present - ); - await processContractRow(processor, db, block, delta); - } - - describe('logmove', () => { - it('inserts a move row and move-asset rows and updates holder=to for each asset', async () => { - const assetIds = ['9100000000001', '9100000000002', '9100000000003']; - for (const id of assetIds) { - await mintAsset(id, 'owner1111111'); - } - - const moveBlock = createBlock({ timestamp: '2023-08-01T09:00:00.000' }); - const moveTrace = await moveAssets('owner1111111', 'renter111111', assetIds, 'lease out', moveBlock); - - // One move row, move_id === global_sequence. - const moveResult = await client.query( - 'SELECT * FROM atomicassets_moves WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveResult.rowCount).to.equal(1); - const move = moveResult.rows[0]; - expect(String(move.move_id)).to.equal(String(moveTrace.global_sequence)); - expect(move.sender).to.equal('owner1111111'); - expect(move.recipient).to.equal('renter111111'); - expect(move.memo).to.equal('lease out'); - expect(Number(move.created_at_block)).to.equal(moveBlock.block_num); - - // N move-asset rows with 1-based index. - const moveAssetsResult = await client.query( - 'SELECT asset_id, index FROM atomicassets_moves_assets WHERE contract = $1 AND move_id = $2 ORDER BY index', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveAssetsResult.rowCount).to.equal(3); - expect(moveAssetsResult.rows.map(r => String(r.asset_id))).to.deep.equal(assetIds); - expect(moveAssetsResult.rows.map(r => r.index)).to.deep.equal([1, 2, 3]); - - // Every asset's holder reflects the recipient; owner is unchanged. - const assetResult = await client.query( - 'SELECT asset_id, owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = ANY($2)', - [CONTRACT, assetIds] - ); - expect(assetResult.rowCount).to.equal(3); - for (const row of assetResult.rows) { - expect(row.holder).to.equal('renter111111'); - expect(row.owner).to.equal('owner1111111'); - } - }); - - it('truncates memo to 256 chars', async () => { - await mintAsset('9100000000010', 'owner1111111'); - - const longMemo = 'x'.repeat(300); - const moveTrace = await moveAssets('owner1111111', 'renter111111', ['9100000000010'], longMemo); - - const moveResult = await client.query( - 'SELECT memo FROM atomicassets_moves WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveResult.rows[0].memo).to.have.lengthOf(256); - expect(moveResult.rows[0].memo).to.equal('x'.repeat(256)); - }); - - it('does no DB work and does not throw on empty asset_ids', async () => { - const moveTrace = await moveAssets('owner1111111', 'renter111111', [], 'empty move'); - - const moveResult = await client.query( - 'SELECT COUNT(*)::int AS n FROM atomicassets_moves WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveResult.rows[0].n).to.equal(0); - - const moveAssetsResult = await client.query( - 'SELECT COUNT(*)::int AS n FROM atomicassets_moves_assets WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveAssetsResult.rows[0].n).to.equal(0); - }); - - it('updates EVERY asset holder when asset_ids exceed ASSET_CHUNK_SIZE (>100)', async () => { - // 250 ids -> 3 UPDATE chunks; assert all 250 holders updated. - const assetIds = Array.from({ length: 250 }, (_, i) => String(9_200_000_000 + i)); - for (const id of assetIds) { - await mintAsset(id, 'owner1111111'); - } - - const moveTrace = await moveAssets('owner1111111', 'renter111111', assetIds, 'big lease'); - - const holders = await client.query( - 'SELECT DISTINCT holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = ANY($2)', - [CONTRACT, assetIds] - ); - expect(holders.rowCount).to.equal(1); - expect(holders.rows[0].holder).to.equal('renter111111'); - - // every move-asset row persisted - const moveAssetCount = await client.query( - 'SELECT COUNT(*)::int AS n FROM atomicassets_moves_assets WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveAssetCount.rows[0].n).to.equal(250); - }); - - it('inserts EVERY move-asset row when count exceeds MOVE_INSERT_CHUNK_SIZE (>1000)', async () => { - // 1500 ids -> 2 INSERT chunks (1000 + 500); assert all 1500 rows present. - const assetIds = Array.from({ length: 1500 }, (_, i) => String(9_300_000_000 + i)); - for (const id of assetIds) { - await mintAsset(id, 'owner1111111'); - } - - const moveTrace = await moveAssets('owner1111111', 'renter111111', assetIds, 'huge lease'); - - const moveAssetCount = await client.query( - 'SELECT COUNT(*)::int AS n FROM atomicassets_moves_assets WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveAssetCount.rows[0].n).to.equal(1500); - - // index range is contiguous 1..1500 - const idxResult = await client.query( - 'SELECT MIN(index) AS lo, MAX(index) AS hi FROM atomicassets_moves_assets WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(idxResult.rows[0].lo).to.equal(1); - expect(idxResult.rows[0].hi).to.equal(1500); - }); - - it('updates holder but writes no moves rows when store_transfers is false', async () => { - // Re-create processor with store_transfers = false. - if (destroyProcessor) { - destroyProcessor(); - } - processor = new DataProcessor(ProcessingState.HEAD, createMockModuleLoader()); - db = createTestTransaction(client); - const core = createMockCore({ store_transfers: false }); - destroyProcessor = assetProcessor(core as any, processor, createMockNotifier()); - - await mintAsset('9100000000020', 'owner1111111'); - - const moveTrace = await moveAssets('owner1111111', 'renter111111', ['9100000000020'], 'silent lease'); - - // holder still updated - const assetResult = await client.query( - 'SELECT holder, owner FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, '9100000000020'] - ); - expect(assetResult.rows[0].holder).to.equal('renter111111'); - expect(assetResult.rows[0].owner).to.equal('owner1111111'); - - // but no moves bookkeeping rows - const moveResult = await client.query( - 'SELECT COUNT(*)::int AS n FROM atomicassets_moves WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveResult.rows[0].n).to.equal(0); - const moveAssetsResult = await client.query( - 'SELECT COUNT(*)::int AS n FROM atomicassets_moves_assets WHERE contract = $1 AND move_id = $2', - [CONTRACT, moveTrace.global_sequence] - ); - expect(moveAssetsResult.rows[0].n).to.equal(0); - }); - }); - - describe('holders table delta', () => { - it('present -> sets holder = delta.value.holder', async () => { - await mintAsset('9400000000001', 'owner1111111'); - - const block = createBlock({ timestamp: '2023-09-01T00:00:00.000' }); - await processHoldersDelta('9400000000001', 'renter111111', 'owner1111111', true, block); - - const result = await client.query( - 'SELECT holder, owner, updated_at_block FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, '9400000000001'] - ); - expect(result.rows[0].holder).to.equal('renter111111'); - // owner must NOT be touched by the holders handler - expect(result.rows[0].owner).to.equal('owner1111111'); - expect(Number(result.rows[0].updated_at_block)).to.equal(block.block_num); - }); - - it('!present -> holder reverts to the asset current owner (not the stale holders-row owner)', async () => { - await mintAsset('9400000000002', 'owner1111111'); - - // First rent it out: holder = renter. - await processHoldersDelta('9400000000002', 'renter111111', 'owner1111111', true); - - // sanity: holder is the renter now - const mid = await client.query( - 'SELECT holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, '9400000000002'] - ); - expect(mid.rows[0].holder).to.equal('renter111111'); - - // Lease ends: holders row removed. delta.value.owner is intentionally a - // STALE value to prove the handler reads the asset's current owner, not - // delta.value.owner. - await processHoldersDelta('9400000000002', 'renter111111', 'staleowner11', false); - - const result = await client.query( - 'SELECT holder, owner FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, '9400000000002'] - ); - // reverts to the real owner read from the asset row, NOT delta.value.owner - expect(result.rows[0].holder).to.equal('owner1111111'); - expect(result.rows[0].owner).to.equal('owner1111111'); - }); - - it('!present for an unknown asset is a no-op (no row, no throw)', async () => { - await processHoldersDelta('9499999999999', 'renter111111', 'owner1111111', false); - - const result = await client.query( - 'SELECT COUNT(*)::int AS n FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, '9499999999999'] - ); - expect(result.rows[0].n).to.equal(0); - }); - }); - - describe('rental lifecycle (lockstep holder reconciliation)', () => { - it('mint -> move -> holders present -> holders !present reconciles holder in lockstep', async () => { - const assetId = '9500000000001'; - - // 1. Mint: holder === owner. - await mintAsset(assetId, 'owner1111111'); - let row = (await client.query( - 'SELECT owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, assetId] - )).rows[0]; - expect(row.owner).to.equal('owner1111111'); - expect(row.holder).to.equal('owner1111111'); - - // 2. logmove: holder becomes the recipient, owner unchanged. - await moveAssets('owner1111111', 'renter111111', [assetId], 'lease'); - row = (await client.query( - 'SELECT owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, assetId] - )).rows[0]; - expect(row.owner).to.equal('owner1111111'); - expect(row.holder).to.equal('renter111111'); - - // 3. holders present delta confirms the rental (authoritative source). - await processHoldersDelta(assetId, 'renter111111', 'owner1111111', true); - row = (await client.query( - 'SELECT owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, assetId] - )).rows[0]; - expect(row.owner).to.equal('owner1111111'); - expect(row.holder).to.equal('renter111111'); - - // 4. logtransfer to a new owner while still rented (changes owner+holder), - // then holders !present (lease ends) -> holder reconciles to the new owner. - const transferTrace = createActionTrace(CONTRACT, 'logtransfer', { - collection_name: 'testcol11111', - from: 'owner1111111', - to: 'newowner1111', - asset_ids: [assetId], - memo: 'sale', - } as LogTransferActionData); - await processActionTrace(processor, db, createBlock(), createTx(), transferTrace); - row = (await client.query( - 'SELECT owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, assetId] - )).rows[0]; - // logtransfer optimistically sets both owner and holder to the recipient - expect(row.owner).to.equal('newowner1111'); - expect(row.holder).to.equal('newowner1111'); - - // 5. holders !present -> holder reverts to the CURRENT owner. - await processHoldersDelta(assetId, 'renter111111', 'owner1111111', false); - row = (await client.query( - 'SELECT owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, assetId] - )).rows[0]; - expect(row.owner).to.equal('newowner1111'); - expect(row.holder).to.equal('newowner1111'); - }); - - it('burn then holders !present clears holder to null in lockstep with owner', async () => { - const assetId = '9500000000002'; - await mintAsset(assetId, 'owner1111111'); - - // burn: owner and holder both cleared to null - const burnTrace = createActionTrace(CONTRACT, 'logburnasset', { - asset_owner: 'owner1111111', - asset_id: assetId, - collection_name: 'testcol11111', - schema_name: 'testschema11', - template_id: 1, - backed_tokens: [], - asset_ram_payer: 'minter1', - old_immutable_data: [], - old_mutable_data: [], - } as LogBurnAssetActionData); - await processActionTrace(processor, db, createBlock(), createTx(), burnTrace); - - let row = (await client.query( - 'SELECT owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, assetId] - )).rows[0]; - expect(row.owner).to.be.null; - expect(row.holder).to.be.null; - - // holders !present after burn -> reads current owner (null) -> holder stays null - await processHoldersDelta(assetId, 'renter111111', 'owner1111111', false); - - row = (await client.query( - 'SELECT owner, holder FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [CONTRACT, assetId] - )).rows[0]; - expect(row.owner).to.be.null; - expect(row.holder).to.be.null; - }); - }); }); diff --git a/src/filler/handlers/atomicassets/processors/assets.ts b/src/filler/handlers/atomicassets/processors/assets.ts index 7c1b69e5..6ecdd3dd 100644 --- a/src/filler/handlers/atomicassets/processors/assets.ts +++ b/src/filler/handlers/atomicassets/processors/assets.ts @@ -1,16 +1,14 @@ import AtomicAssetsHandler, { AtomicAssetsUpdatePriority } from '../index'; import DataProcessor from '../../../processor'; import { ContractDBTransaction } from '../../../database'; -import { EosioActionTrace, EosioContractRow, EosioTransaction } from '../../../../types/eosio'; +import { EosioActionTrace, EosioTransaction } from '../../../../types/eosio'; import { LogBackAssetActionData, LogBurnAssetActionData, LogMintAssetActionData, - LogMoveActionData, LogSetDataActionData, LogTransferActionData } from '../types/actions'; -import { HoldersTableRow } from '../types/tables'; import { ShipBlock } from '../../../../types/ship'; import { eosioTimestampToDate, splitEosioToken } from '../../../../utils/eosio'; import { convertAttributeMapToObject } from '../utils'; @@ -37,7 +35,6 @@ export function assetProcessor(core: AtomicAssetsHandler, processor: DataProcess schema_name: trace.act.data.schema_name, template_id: trace.act.data.template_id === -1 ? null : trace.act.data.template_id, owner: trace.act.data.new_asset_owner, - holder: trace.act.data.new_asset_owner, mutable_data: encodeDatabaseJson(convertAttributeMapToObject(trace.act.data.mutable_data)), immutable_data: encodeDatabaseJson(convertAttributeMapToObject(trace.act.data.immutable_data)), burned_by_account: null, @@ -126,7 +123,6 @@ export function assetProcessor(core: AtomicAssetsHandler, processor: DataProcess async (db: ContractDBTransaction, block: ShipBlock, tx: EosioTransaction, trace: EosioActionTrace): Promise => { await db.update('atomicassets_assets', { owner: null, - holder: null, burned_by_account: trace.act.data.asset_owner, burned_at_block: block.block_num, burned_at_time: eosioTimestampToDate(block.timestamp).getTime(), @@ -197,7 +193,6 @@ export function assetProcessor(core: AtomicAssetsHandler, processor: DataProcess const updateValues = { owner: trace.act.data.to, - holder: trace.act.data.to, transferred_at_block: block.block_num, transferred_at_time: blockTime, updated_at_block: block.block_num, @@ -239,116 +234,5 @@ export function assetProcessor(core: AtomicAssetsHandler, processor: DataProcess }, AtomicAssetsUpdatePriority.ACTION_UPDATE_ASSET.valueOf() )); - destructors.push(processor.onActionTrace( - contract, 'logmove', - async (db: ContractDBTransaction, block: ShipBlock, tx: EosioTransaction, trace: EosioActionTrace): Promise => { - const assetIds = trace.act.data.asset_ids; - - if (assetIds.length === 0) { - notifier.sendActionTrace('moves', block, tx, trace); - return; - } - - const blockTime = eosioTimestampToDate(block.timestamp).getTime(); - - // A move only changes the holder (rental), not the real owner. Bound - // per-statement work exactly like logtransfer above — a move action can - // in principle carry a large asset_ids array. - const ASSET_CHUNK_SIZE = 100; - const MOVE_INSERT_CHUNK_SIZE = 1000; - - await db.query("SET LOCAL statement_timeout = '300s'"); - - const updateValues = { - owner: trace.act.data.owner, - holder: trace.act.data.to, - transferred_at_block: block.block_num, - transferred_at_time: blockTime, - updated_at_block: block.block_num, - updated_at_time: blockTime, - }; - - for (const chunk of arrayChunk(assetIds, ASSET_CHUNK_SIZE)) { - await db.update('atomicassets_assets', updateValues, { - str: 'contract = $1 AND asset_id = ANY ($2)', - values: [contract, chunk] - }, ['contract', 'asset_id']); - } - - if (core.args.store_transfers) { - await db.insert('atomicassets_moves', { - contract: contract, - move_id: trace.global_sequence, - sender: trace.act.data.from, - recipient: trace.act.data.to, - memo: String(trace.act.data.memo).substr(0, 256), - txid: Buffer.from(tx.id, 'hex'), - created_at_block: block.block_num, - created_at_time: blockTime - }, ['contract', 'move_id'], true, true, 'update'); - - const moveAssetRows = assetIds.map((assetID, index) => ({ - move_id: trace.global_sequence, - contract: contract, - index: index + 1, - asset_id: assetID - })); - - for (const insertChunk of arrayChunk(moveAssetRows, MOVE_INSERT_CHUNK_SIZE)) { - await db.insert('atomicassets_moves_assets', insertChunk, ['contract', 'move_id', 'asset_id'], true, true, 'update'); - } - } - - notifier.sendActionTrace('moves', block, tx, trace); - }, AtomicAssetsUpdatePriority.ACTION_UPDATE_ASSET.valueOf() - )); - - // v2: the global `holders` table is the AUTHORITATIVE source for `holder`. A - // row exists only while holder != owner (asset rented out). This runs at - // TABLE_HOLDERS priority (after the asset action handlers), so it reconciles - // `holder` for rented assets after logmint/logtransfer/logmove optimistically - // set it (those keep `holder` correct for the common non-rented case, which - // produces no holders-table delta): - // - present -> holder := holders.holder (asset is rented out) - // - !present -> holder := owner (lease ended / asset returned, transferred - // to the holder, or burned -> owner is the new/cleared owner) - destructors.push(processor.onContractRow( - contract, 'holders', - async (db: ContractDBTransaction, block: ShipBlock, delta: EosioContractRow): Promise => { - if (delta.present) { - await db.update('atomicassets_assets', { - holder: delta.value.holder, - updated_at_block: block.block_num, - updated_at_time: eosioTimestampToDate(block.timestamp).getTime(), - }, { - str: 'contract = $1 AND asset_id = $2', - values: [contract, delta.value.asset_id] - }, ['contract', 'asset_id']); - } else { - // Holders row removed -> holder reverts to the legal owner. Read - // the asset's CURRENT owner (the burn/transfer action handlers run - // at lower priority, so owner is already updated and db.query - // flushes the write buffer before reading). delta.value.owner is - // the pre-deletion holders-row owner and is NOT reliable here (it - // is stale after a transfer-to-holder or a burn). - const assetRow = await db.query( - 'SELECT owner FROM atomicassets_assets WHERE contract = $1 AND asset_id = $2', - [contract, delta.value.asset_id] - ); - - if (assetRow.rowCount > 0) { - await db.update('atomicassets_assets', { - holder: assetRow.rows[0].owner, - updated_at_block: block.block_num, - updated_at_time: eosioTimestampToDate(block.timestamp).getTime(), - }, { - str: 'contract = $1 AND asset_id = $2', - values: [contract, delta.value.asset_id] - }, ['contract', 'asset_id']); - } - } - }, AtomicAssetsUpdatePriority.TABLE_HOLDERS.valueOf() - )); - return (): any => destructors.map(fn => fn()); } diff --git a/src/filler/handlers/atomicassets/processors/logs.ts b/src/filler/handlers/atomicassets/processors/logs.ts index 09701dce..3a165707 100644 --- a/src/filler/handlers/atomicassets/processors/logs.ts +++ b/src/filler/handlers/atomicassets/processors/logs.ts @@ -17,7 +17,6 @@ import { LogBackAssetActionData, LogBurnAssetActionData, LogMintAssetActionData, - LogMoveActionData, LogNewOfferActionData, LogNewTemplateActionData, LogSetDataActionData, @@ -110,13 +109,6 @@ export function logProcessor(core: AtomicAssetsHandler, processor: DataProcessor }, AtomicAssetsUpdatePriority.LOGS.valueOf() )); - destructors.push(processor.onActionTrace( - contract, 'logmove', - async (db: ContractDBTransaction, block: ShipBlock, tx: EosioTransaction, trace: EosioActionTrace): Promise => { - await db.logTrace(block, tx, trace, trace.act.data); - }, AtomicAssetsUpdatePriority.LOGS.valueOf() - )); - /* COLLECTIONS */ destructors.push(processor.onActionTrace( contract, 'createcol', diff --git a/src/filler/handlers/atomicassets/types/actions.ts b/src/filler/handlers/atomicassets/types/actions.ts index 837a2f54..c958512b 100644 --- a/src/filler/handlers/atomicassets/types/actions.ts +++ b/src/filler/handlers/atomicassets/types/actions.ts @@ -9,15 +9,6 @@ export type LogTransferActionData = { memo: string }; -export type LogMoveActionData = { - collection_name: string, - owner: string, - 'from': string, - to: string, - asset_ids: string[], - memo: string -}; - export type LogMintAssetActionData = { asset_id: string; authorized_minter: string, diff --git a/src/filler/handlers/atomicassets/types/tables.ts b/src/filler/handlers/atomicassets/types/tables.ts index 32843614..eb4a2123 100644 --- a/src/filler/handlers/atomicassets/types/tables.ts +++ b/src/filler/handlers/atomicassets/types/tables.ts @@ -31,14 +31,6 @@ export type AuthorSwapsTableRow = { acceptance_date: number, }; -// v2 renting: global `holders` table (scope = contract). A row exists only while -// holder != owner (asset is rented out). pk = asset_id. -export type HoldersTableRow = { - asset_id: string, - holder: string, - owner: string, -}; - export type ConfigTableRow = { asset_counter: number, offer_counter: number, diff --git a/src/types/config.ts b/src/types/config.ts index e10fc302..e67aaaff 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -69,7 +69,6 @@ interface IAssetsLimitsConfig { schemas?: number; templates?: number; transfers?: number; - moves?: number; logs?: number; }