UNPKG

@ocap/indexdb-memory

Version:

OCAP indexdb adapter that uses memory as backend, just for test purpose

574 lines (473 loc) 18 kB
/* eslint-disable require-await */ /* eslint-disable newline-per-chained-call */ const { BN } = require('@ocap/util'); const { BaseIndexDB } = require('@ocap/indexdb'); const { formatPagination, formatNextPagination, formatDelegationAfterRead } = require('@ocap/indexdb/lib/util'); const { parseDateTime } = require('@ocap/indexdb/lib/util'); const { DEFAULT_TOKEN_DECIMAL } = require('@ocap/util/lib/constant'); const { toChecksumAddress } = require('@arcblock/did/lib/type'); const debug = require('debug')(require('../../package.json').name); const MAX_REQUEST_FACTORY_ADDRESS_SIZE = 100; class LocalBaseIndexDB extends BaseIndexDB { listTransactions({ addressFilter = {}, paging = {}, timeFilter = {}, typeFilter = {}, assetFilter = {}, factoryFilter = {}, tokenFilter = {}, accountFilter = {}, validityFilter = {}, txFilter = {}, rollupFilter = {}, stakeFilter = {}, delegationFilter = {}, } = {}) { const query = this.tx.collection.chain(); const { sender, receiver, direction = 'UNION' } = addressFilter; const { types = [] } = typeFilter; const { factories = [] } = factoryFilter; const { assets = [] } = assetFilter; const { tokens = [] } = tokenFilter; const { accounts = [] } = accountFilter; const { txs = [] } = txFilter; const { rollups = [] } = rollupFilter; const { stakes = [] } = stakeFilter; const { delegations = [] } = delegationFilter; let { startDateTime, endDateTime } = timeFilter; startDateTime = parseDateTime(startDateTime); endDateTime = parseDateTime(endDateTime); const pagination = formatPagination({ paging, defaultSortField: 'time' }); debug('listTransactions', { sender, receiver, pagination, types, factories, assets, tokens, accounts, rollups, stakes, delegations, }); if (sender && receiver) { if (direction === 'MUTUAL') { // 两人之间的所有往来 query.where((x) => { if (x.sender === sender && x.receiver === receiver) { return true; } if (x.sender === receiver && x.receiver === sender) { return true; } return false; }); } else if (direction === 'ONE_WAY') { // 两人之间的单向交易 query.where((x) => x.sender === sender && x.receiver === receiver); } else if (direction === 'UNION') { // 查询某个人收发的交易 query.where((x) => x.sender === sender || x.receiver === receiver); } } else if (sender) { // 某个人发的交易 query.where((x) => x.sender === sender); } else if (receiver) { // 某个人收的交易 query.where((x) => x.receiver === receiver); } if (types.length) { query.where((x) => types.includes(x.type)); } if (factories.length) { query.where((x) => x.factories.some((f) => factories.includes(f))); } if (assets.length) { query.where((x) => x.assets.some((f) => assets.includes(f))); } if (tokens.length) { query.where((x) => x.tokens.some((f) => tokens.includes(f))); } if (accounts.length) { query.where((x) => x.accounts.some((f) => accounts.includes(f))); } if (startDateTime && endDateTime) { query.where((x) => x.time > startDateTime && x.time <= endDateTime); } else if (startDateTime) { query.where((x) => x.time > startDateTime); } else if (endDateTime) { query.where((x) => x.time <= endDateTime); } if (validityFilter.validity && validityFilter.validity === 'VALID') { query.where((x) => x.valid === true); } if (validityFilter.validity && validityFilter.validity === 'INVALID') { query.where((x) => x.valid === false); } if (txs.length) { query.where((x) => txs.includes(x.hash)); } if (rollups.length) { query.where((x) => x.rollups.some((f) => rollups.includes(f))); } if (stakes.length) { query.where((x) => x.stakes.some((f) => stakes.includes(f))); } if (delegations.length) { query.where((x) => x.delegations.some((f) => delegations.includes(f))); } const transactions = query .simplesort(pagination.order.field, paging.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = query.count(); const nextPaging = { cursor: pagination.cursor + transactions.length, next: transactions.length >= pagination.size, total, }; return { transactions, paging: nextPaging }; } async getRelatedAddresses(address) { let account = await this.account.get(address); if (!account) { return []; } const related = [address]; while (account && account.migratedFrom && related.length <= 8) { related.push(account.migratedFrom); // eslint-disable-next-line no-await-in-loop account = await this.account.get(account.migratedFrom); } return related.filter(Boolean); } async listAssets({ ownerAddress, factoryAddress, paging, timeFilter = {} } = {}) { if (!ownerAddress && !factoryAddress) { return { assets: [], account: null }; } const pagination = formatPagination({ paging, defaultSortField: 'renaissanceTime' }); // eslint-disable-next-line prefer-const let { startDateTime, endDateTime, field = 'renaissanceTime' } = timeFilter; startDateTime = parseDateTime(startDateTime); endDateTime = parseDateTime(endDateTime); if (['genesisTime', 'renaissanceTime', 'consumedTime'].includes(field) === false) { throw new Error('invalid field specified in timeFilter'); } const query = this.asset.collection.chain(); if (ownerAddress) { const possibleOwners = await this.getRelatedAddresses(ownerAddress); if (possibleOwners.length) { query.where((x) => possibleOwners.includes(x.owner)); } else { // If we dit not find any owner state, just return empty return { assets: [], account: null }; } } if (factoryAddress) { query.where((x) => x.parent === factoryAddress); } if (startDateTime && endDateTime) { query.where((x) => x[field] > startDateTime && x[field] <= endDateTime); } else if (startDateTime) { query.where((x) => x[field] > startDateTime); } else if (endDateTime) { query.where((x) => x[field] <= endDateTime); } debug('listAssets', { ownerAddress, factoryAddress, timeFilter, paging, pagination }); const assets = await query .simplesort(pagination.order.field, pagination.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = query.count(); const account = ownerAddress ? await this.account.get(ownerAddress) : null; return { assets, account, paging: formatNextPagination(total, pagination) }; } async listTopAccounts({ paging, tokenAddress } = {}) { const query = this.account.collection.chain(); const pagination = formatPagination({ paging, defaultSortField: 'balance', supportedSortFields: ['genesisTime', 'renaissanceTime', 'balance', 'moniker'], }); debug('listTopAccounts', { tokenAddress, paging, pagination }); query.where((x) => { const owned = x.tokens.find((t) => t.address === tokenAddress); return owned ? owned.balance > '0' : false; }); if (pagination.order.field === 'balance') { const descending = pagination.order.type === 'desc'; query.sort((a, b) => { const tokenA = a.tokens.find((x) => x.address === tokenAddress); const tokenB = b.tokens.find((x) => x.address === tokenAddress); const balanceA = new BN(tokenA ? tokenA.balance : '0'); const balanceB = new BN(tokenB ? tokenB.balance : '0'); if (balanceB.gt(balanceA)) { return descending ? 1 : -1; } if (balanceB.eq(balanceA)) { return 0; } return descending ? -1 : 1; }); } else { query.simplesort(pagination.order.field, pagination.order.type); } const accounts = query.offset(pagination.cursor).limit(pagination.size).data(); const total = query.count(); accounts.forEach((account) => { if (Array.isArray(account.tokens) && account.tokens.length) { account.tokens = account.tokens.map((token) => { token.decimal = typeof token.decimal === 'undefined' ? DEFAULT_TOKEN_DECIMAL : token.decimal; return token; }); } }); return { accounts, paging: formatNextPagination(total, pagination) }; } async listTokens({ issuerAddress, paging } = {}) { const conditions = {}; if (issuerAddress) { conditions.issuer = issuerAddress; } const pagination = formatPagination({ paging, defaultSortField: 'renaissanceTime', supportedSortFields: ['genesisTime', 'renaissanceTime', 'issuer', 'symbol', 'totalSupply'], }); debug('listTokens', { issuerAddress, paging, conditions, pagination }); let tokens = this.token.collection .chain() .find(conditions) .simplesort(pagination.order.field, pagination.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); tokens = tokens.map((token) => { token.decimal = typeof token.decimal === 'undefined' ? DEFAULT_TOKEN_DECIMAL : token.decimal; return token; }); const total = this.token.count(Object.keys(conditions).length ? conditions : undefined); return { tokens, paging: formatNextPagination(total, pagination) }; } async listFactories({ ownerAddress, addressList, paging } = {}) { const conditions = {}; if (ownerAddress) { conditions.owner = ownerAddress; } if (Array.isArray(addressList) && addressList.length > 0) { if (addressList.length > MAX_REQUEST_FACTORY_ADDRESS_SIZE) { throw new Error(`The length of 'addressList' cannot exceed the length of ${MAX_REQUEST_FACTORY_ADDRESS_SIZE}`); } conditions.address = { $in: addressList }; } const pagination = formatPagination({ paging, defaultSortField: 'renaissanceTime', supportedSortFields: ['genesisTime', 'renaissanceTime', 'owner', 'numMinted', 'limit', 'name'], }); debug('listFactories', { ownerAddress, paging, conditions, pagination }); const factories = await this.factory.collection .chain() .find(conditions) .simplesort(pagination.order.field, pagination.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = this.factory.count(Object.keys(conditions).length ? conditions : undefined); return { factories, paging: formatNextPagination(total, pagination) }; } listStakes({ addressFilter = {}, paging = {}, timeFilter = {}, assetFilter = {} } = {}) { const query = this.stake.collection.chain(); const { sender, receiver } = addressFilter; const { assets = [] } = assetFilter; let { startDateTime, endDateTime, field } = timeFilter; startDateTime = parseDateTime(startDateTime); endDateTime = parseDateTime(endDateTime); field = ['genesisTime', 'renaissanceTime'].includes(field) ? field : 'renaissanceTime'; const pagination = formatPagination({ paging, defaultSortField: 'renaissanceTime', supportedSortFields: ['genesisTime', 'renaissanceTime'], }); if (sender && receiver) { query.where((x) => x.sender === sender && x.receiver === receiver); } else if (sender) { query.where((x) => x.sender === sender); } else if (receiver) { query.where((x) => x.receiver === receiver); } if (assets.length) { query.where((x) => x.assets.some((f) => assets.includes(f))); } if (startDateTime && endDateTime) { query.where((x) => x[field] > startDateTime && x[field] <= endDateTime); } else if (startDateTime) { query.where((x) => x[field] > startDateTime); } else if (endDateTime) { query.where((x) => x[field] <= endDateTime); } const stakes = query .simplesort(pagination.order.field, paging.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = query.count(); const nextPaging = { cursor: pagination.cursor + stakes.length, next: stakes.length >= pagination.size, total, }; return { stakes, paging: nextPaging }; } async listRollups({ paging, tokenAddress = '', erc20TokenAddress = '', foreignTokenAddress = '' } = {}) { const pagination = formatPagination({ paging, defaultSortField: 'renaissanceTime', supportedSortFields: ['genesisTime', 'renaissanceTime'], }); const query = this.rollup.collection.chain(); if (tokenAddress) { query.where((x) => x.tokenAddress === tokenAddress); } const foreignTokenAddr = foreignTokenAddress || erc20TokenAddress; if (erc20TokenAddress) { query.where((x) => x.foreignToken.contractAddress === toChecksumAddress(foreignTokenAddr)); } debug('listRollups', { paging, pagination }); const rollups = query .simplesort(pagination.order.field, pagination.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = query.count(); return { rollups, paging: formatNextPagination(total, pagination) }; } listRollupBlocks({ paging = {}, rollupAddress = '', tokenAddress = '', proposer = '', height = '', timeFilter = {}, txFilter = {}, validatorFilter = {}, } = {}) { const query = this.rollupBlock.collection.chain(); const { txs = [] } = txFilter; const { validators = [] } = validatorFilter; let { startDateTime, endDateTime, field = 'genesisTime' } = timeFilter; // eslint-disable-line startDateTime = parseDateTime(startDateTime); endDateTime = parseDateTime(endDateTime); const pagination = formatPagination({ paging, defaultSortField: 'genesisTime', supportedSortFields: ['genesisTime', 'renaissanceTime'], }); if (rollupAddress) { query.where((x) => x.rollup === rollupAddress); } if (Number(height) > 0) { query.where((x) => Number(x.height) === Number(height)); } if (proposer) { query.where((x) => x.proposer === proposer); } if (tokenAddress) { query.where((x) => x.tokenInfo.address === tokenAddress); } if (txs.length) { query.where((x) => x.txs.some((f) => txs.includes(f))); } if (validators.length) { query.where((x) => x.validators.some((f) => validators.includes(f))); } if (startDateTime && endDateTime) { query.where((x) => x[field] > startDateTime && x[field] <= endDateTime); } else if (startDateTime) { query.where((x) => x[field] > startDateTime); } else if (endDateTime) { query.where((x) => x[field] <= endDateTime); } const blocks = query .simplesort(pagination.order.field, paging.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = query.count(); const nextPaging = { cursor: pagination.cursor + blocks.length, next: blocks.length >= pagination.size, total, }; return { blocks, paging: nextPaging }; } listRollupValidators({ paging = {}, rollupAddress = '' } = {}) { const query = this.rollupValidator.collection.chain(); const pagination = formatPagination({ paging, defaultSortField: 'genesisTime', supportedSortFields: ['joinTime', 'leaveTime', 'genesisTime', 'renaissanceTime'], }); if (rollupAddress) { query.where((x) => x.rollup === rollupAddress); } const validators = query .simplesort(pagination.order.field, paging.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = query.count(); const nextPaging = { cursor: pagination.cursor + validators.length, next: validators.length >= pagination.size, total, }; return { validators, paging: nextPaging }; } listDelegations({ from, to, paging = {}, timeFilter = {} } = {}) { if (!from && !to) { return { delegations: [], paging: { cursor: 0, next: false, total: 0 } }; } const query = this.delegation.collection.chain(); let { startDateTime, endDateTime, field } = timeFilter; startDateTime = parseDateTime(startDateTime); endDateTime = parseDateTime(endDateTime); field = ['genesisTime', 'renaissanceTime'].includes(field) ? field : 'renaissanceTime'; const pagination = formatPagination({ paging, defaultSortField: 'renaissanceTime', supportedSortFields: ['genesisTime', 'renaissanceTime'], }); if (from && to) { query.where((x) => x.from === from && x.to === to); } else if (from) { query.where((x) => x.from === from); } else if (to) { query.where((x) => x.to === to); } if (startDateTime && endDateTime) { query.where((x) => x[field] > startDateTime && x[field] <= endDateTime); } else if (startDateTime) { query.where((x) => x[field] > startDateTime); } else if (endDateTime) { query.where((x) => x[field] <= endDateTime); } const delegations = query .simplesort(pagination.order.field, paging.order.type === 'desc') .offset(pagination.cursor) .limit(pagination.size) .data(); const total = query.count(); const nextPaging = { cursor: pagination.cursor + delegations.length, next: delegations.length >= pagination.size, total, }; return { delegations: delegations.map(formatDelegationAfterRead), paging: nextPaging }; } } module.exports = LocalBaseIndexDB;