@ocap/indexdb-memory
Version:
OCAP indexdb adapter that uses memory as backend, just for test purpose
574 lines (473 loc) • 18 kB
JavaScript
/* 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;