@ocap/resolver 1.28.8 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/api.d.mts +24 -0
- package/esm/api.mjs +53 -0
- package/esm/hooks.d.mts +153 -0
- package/esm/hooks.mjs +267 -0
- package/esm/index.d.mts +201 -0
- package/esm/index.mjs +1327 -0
- package/esm/migration-chain.d.mts +52 -0
- package/esm/migration-chain.mjs +97 -0
- package/esm/package.mjs +5 -0
- package/esm/token-cache.d.mts +20 -0
- package/esm/token-cache.mjs +26 -0
- package/esm/token-distribution.d.mts +166 -0
- package/esm/token-distribution.mjs +241 -0
- package/esm/token-flow.d.mts +139 -0
- package/esm/token-flow.mjs +330 -0
- package/esm/types.d.mts +115 -0
- package/esm/types.mjs +1 -0
- package/lib/_virtual/rolldown_runtime.cjs +29 -0
- package/lib/api.cjs +54 -0
- package/lib/api.d.cts +24 -0
- package/lib/hooks.cjs +274 -0
- package/lib/hooks.d.cts +153 -0
- package/lib/index.cjs +1343 -0
- package/lib/index.d.cts +201 -0
- package/lib/migration-chain.cjs +99 -0
- package/lib/migration-chain.d.cts +52 -0
- package/lib/package.cjs +11 -0
- package/lib/token-cache.cjs +27 -0
- package/lib/token-cache.d.cts +20 -0
- package/lib/token-distribution.cjs +243 -0
- package/lib/token-distribution.d.cts +166 -0
- package/lib/token-flow.cjs +336 -0
- package/lib/token-flow.d.cts +139 -0
- package/lib/types.cjs +0 -0
- package/lib/types.d.cts +115 -0
- package/package.json +49 -21
- package/lib/api.js +0 -71
- package/lib/hooks.js +0 -339
- package/lib/index.js +0 -1486
- package/lib/migration-chain.js +0 -144
- package/lib/token-cache.js +0 -40
- package/lib/token-distribution.js +0 -358
- package/lib/token-flow.js +0 -445
package/lib/index.js
DELETED
|
@@ -1,1486 +0,0 @@
|
|
|
1
|
-
const get = require('lodash/get');
|
|
2
|
-
const set = require('lodash/set');
|
|
3
|
-
const uniq = require('lodash/uniq');
|
|
4
|
-
const pick = require('lodash/pick');
|
|
5
|
-
const isEmpty = require('lodash/isEmpty');
|
|
6
|
-
const isEqual = require('lodash/isEqual');
|
|
7
|
-
const uniqBy = require('lodash/unionBy');
|
|
8
|
-
const omit = require('lodash/omit');
|
|
9
|
-
const Config = require('@ocap/config');
|
|
10
|
-
const states = require('@ocap/state');
|
|
11
|
-
const { patterns } = require('@arcblock/validator');
|
|
12
|
-
const { md5 } = require('@ocap/util/lib/md5');
|
|
13
|
-
const { CustomError } = require('@ocap/util/lib/error');
|
|
14
|
-
const { types } = require('@ocap/mcrypto');
|
|
15
|
-
const { fromTypeUrl } = require('@ocap/message');
|
|
16
|
-
const { toStakeAddress } = require('@arcblock/did-util');
|
|
17
|
-
const { fromPublicKey, toTypeInfo, isValid: isValidDid } = require('@arcblock/did');
|
|
18
|
-
const { BN, toBN, fromTokenToUnit, toAddress } = require('@ocap/util');
|
|
19
|
-
const { DEFAULT_TOKEN_DECIMAL } = require('@ocap/util/lib/constant');
|
|
20
|
-
const { createExecutor } = require('@ocap/tx-protocols');
|
|
21
|
-
const { decodeAnySafe } = require('@ocap/tx-protocols/lib/util');
|
|
22
|
-
const { Joi } = require('@arcblock/validator');
|
|
23
|
-
const Queue = require('queue');
|
|
24
|
-
|
|
25
|
-
const {
|
|
26
|
-
createIndexedAccount,
|
|
27
|
-
createIndexedAsset,
|
|
28
|
-
createIndexedFactory,
|
|
29
|
-
createIndexedDelegation,
|
|
30
|
-
createIndexedToken,
|
|
31
|
-
createIndexedTransaction,
|
|
32
|
-
createIndexedStake,
|
|
33
|
-
createIndexedRollup,
|
|
34
|
-
createIndexedRollupBlock,
|
|
35
|
-
isDefaultTokenChanged,
|
|
36
|
-
createIndexedTokenFactory,
|
|
37
|
-
} = require('@ocap/indexdb/lib/util');
|
|
38
|
-
const { formatTxType } = require('@ocap/util');
|
|
39
|
-
|
|
40
|
-
const {
|
|
41
|
-
getTxReceipts,
|
|
42
|
-
mergeTxReceipts,
|
|
43
|
-
getTxSender,
|
|
44
|
-
getTxReceiver,
|
|
45
|
-
attachPaidTxGas,
|
|
46
|
-
} = require('@ocap/state/lib/states/tx');
|
|
47
|
-
|
|
48
|
-
const debug = require('debug')(require('../package.json').name);
|
|
49
|
-
const hooks = require('./hooks');
|
|
50
|
-
const tokenFlow = require('./token-flow');
|
|
51
|
-
const { getInstance: getTokenCacheInstance } = require('./token-cache');
|
|
52
|
-
const { MigrationChainManager } = require('./migration-chain');
|
|
53
|
-
const { TokenDistributionManager } = require('./token-distribution');
|
|
54
|
-
|
|
55
|
-
const noop = (x) => x;
|
|
56
|
-
const CHAIN_ADDR = md5('OCAP_CHAIN_ADDR');
|
|
57
|
-
|
|
58
|
-
const maxGasOps = {
|
|
59
|
-
'fg:t:account_migrate': { create: 2, update: 1 },
|
|
60
|
-
'fg:t:delegate': { create: 3, update: 1 },
|
|
61
|
-
'fg:t:revoke_delegate': { create: 1, update: 2 },
|
|
62
|
-
|
|
63
|
-
'fg:t:create_asset': { create: 2, update: 2 },
|
|
64
|
-
'fg:t:update_asset': { create: 1, update: 2 },
|
|
65
|
-
'fg:t:consume_asset': { create: 1, update: 2 },
|
|
66
|
-
|
|
67
|
-
'fg:t:create_factory': { create: 2, update: 2 },
|
|
68
|
-
'fg:t:acquire_asset_v2': { create: 2, update: 12 },
|
|
69
|
-
'fg:t:acquire_asset_v3': { create: 3, update: 12 },
|
|
70
|
-
'fg:t:mint_asset': { create: 3, update: 10 },
|
|
71
|
-
|
|
72
|
-
'fg:t:create_token': { create: 2, update: 3 },
|
|
73
|
-
'fg:t:deposit_token_v2': { create: 3, update: 3 },
|
|
74
|
-
'fg:t:withdraw_token_v2': { create: 2, update: 3 },
|
|
75
|
-
|
|
76
|
-
'fg:t:stake': { create: 2, update: 18 },
|
|
77
|
-
'fg:t:revoke_stake': { create: 1, update: 2 },
|
|
78
|
-
'fg:t:claim_stake': { create: 2, update: 18 },
|
|
79
|
-
'fg:t:slash_stake': { update: 18 },
|
|
80
|
-
'fg:t:return_stake': { update: 8 },
|
|
81
|
-
|
|
82
|
-
'fg:t:transfer': { create: 2, update: 20 },
|
|
83
|
-
'fg:t:transfer_v2': { create: 2, update: 20 },
|
|
84
|
-
'fg:t:transfer_v3': { create: 9, update: 16 },
|
|
85
|
-
'fg:t:exchange_v2': { create: 1, update: 18 },
|
|
86
|
-
|
|
87
|
-
'fg:t:create_token_factory': { create: 2, update: 3 },
|
|
88
|
-
'fg:t:update_token_factory': { create: 0, update: 3 },
|
|
89
|
-
'fg:t:mint_token': { create: 2, update: 13 },
|
|
90
|
-
'fg:t:burn_token': { create: 2, update: 13 },
|
|
91
|
-
|
|
92
|
-
'fg:t:create_rollup': { create: 2, update: 2 },
|
|
93
|
-
'fg:t:update_rollup': { create: 1, update: 2 },
|
|
94
|
-
'fg:t:pause_rollup': { create: 1, update: 2 },
|
|
95
|
-
'fg:t:close_rollup': { create: 1, update: 26 },
|
|
96
|
-
'fg:t:resume_rollup': { create: 1, update: 2 },
|
|
97
|
-
'fg:t:join_rollup': { create: 2, update: 3 },
|
|
98
|
-
'fg:t:leave_rollup': { create: 2, update: 3 },
|
|
99
|
-
'fg:t:migrate_rollup': { create: 1, update: 2 },
|
|
100
|
-
'fg:t:create_rollup_block': { create: 2, update: 38 },
|
|
101
|
-
'fg:t:claim_block_reward': { create: 2, update: 38 },
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const txRequestConfig = {
|
|
105
|
-
maxRequest: Number(process.env.TX_MAX_REQUEST) || 200,
|
|
106
|
-
concurrent: Number(process.env.TX_CONCURRENCY) || 10,
|
|
107
|
-
timeout: Number(process.env.TX_TIMEOUT) || 1000 * 30,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const formatData = (data) => {
|
|
111
|
-
if (!data) {
|
|
112
|
-
return data;
|
|
113
|
-
}
|
|
114
|
-
if (data.type_url) {
|
|
115
|
-
return data;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// For legacy compatibility
|
|
119
|
-
// If we can successfully parse the value, it must be valid JSON
|
|
120
|
-
if (data.typeUrl && ['json', 'vc'].includes(data.typeUrl)) {
|
|
121
|
-
try {
|
|
122
|
-
JSON.parse(data.value);
|
|
123
|
-
data.type_url = data.typeUrl;
|
|
124
|
-
return data;
|
|
125
|
-
} catch (_err) {
|
|
126
|
-
// Some legacy data contains escaped back slashes
|
|
127
|
-
try {
|
|
128
|
-
JSON.parse(data.value.replace(/\\"/g, '"'));
|
|
129
|
-
data.type_url = data.typeUrl;
|
|
130
|
-
data.value = data.value.replace(/\\"/g, '"');
|
|
131
|
-
return data;
|
|
132
|
-
} catch (_e) {
|
|
133
|
-
// do nothing
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const decoded = data.typeUrl ? decodeAnySafe(data) : data;
|
|
139
|
-
if (['json', 'vc', 'AssetFactory'].includes(decoded.type)) {
|
|
140
|
-
return { typeUrl: decoded.type, type_url: decoded.type, value: JSON.stringify(decoded.value) };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return { typeUrl: decoded.type, type_url: decoded.type, value: decoded.value };
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const formatDelegationOps = (state) => {
|
|
147
|
-
if (state?.ops && typeof state.ops === 'object') {
|
|
148
|
-
state.ops = Object.keys(state.ops).map((x) => ({ key: x, value: state.ops[x] }));
|
|
149
|
-
}
|
|
150
|
-
return state;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const extractTokenMeta = (address, tokenStates) => {
|
|
154
|
-
const tokenState = tokenStates.filter(Boolean).find((t) => t.address === address);
|
|
155
|
-
if (!tokenState) {
|
|
156
|
-
return {};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
address,
|
|
161
|
-
decimal: typeof tokenState.decimal === 'undefined' ? DEFAULT_TOKEN_DECIMAL : tokenState.decimal,
|
|
162
|
-
unit: tokenState.unit,
|
|
163
|
-
symbol: tokenState.symbol,
|
|
164
|
-
foreignToken: tokenState.foreignToken,
|
|
165
|
-
};
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
module.exports = class OCAPResolver {
|
|
169
|
-
/**
|
|
170
|
-
* Creates an instance of OCAPResolver.
|
|
171
|
-
* @param {object} params Parameters to bootstrap the resolver
|
|
172
|
-
* @param {object} params.statedb @ocap/statedb adapter
|
|
173
|
-
* @param {object} params.indexdb @ocap/indexdb adapter
|
|
174
|
-
* @param {object} params.config @ocap/config object
|
|
175
|
-
* @param {object} params.filter bloom filter to do anti-replay check
|
|
176
|
-
* @param {bool} params.validateTokenConfig should we validate token supply and token-holder balance, should be disabled when starting an existing chain
|
|
177
|
-
*/
|
|
178
|
-
constructor({ statedb, indexdb, config, filter, validateTokenConfig = true, logger }) {
|
|
179
|
-
if (!statedb) {
|
|
180
|
-
throw new Error('OCAP Resolver requires a valid statedb implementation to work');
|
|
181
|
-
}
|
|
182
|
-
if (!indexdb) {
|
|
183
|
-
throw new Error('OCAP Resolver requires a valid indexdb implementation to work');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
this.logger = logger || console;
|
|
187
|
-
|
|
188
|
-
this.statedb = statedb;
|
|
189
|
-
this.indexdb = indexdb;
|
|
190
|
-
this.filter = filter;
|
|
191
|
-
|
|
192
|
-
this.config = Object.freeze(Config.validate(config));
|
|
193
|
-
this.chainAddr = md5(config.chainId);
|
|
194
|
-
this.tokenItx = Config.genTokenItx(this.config);
|
|
195
|
-
this.consensus = `${statedb.name} v${statedb.version}`;
|
|
196
|
-
this.queue = new Queue({
|
|
197
|
-
autostart: true,
|
|
198
|
-
concurrency: txRequestConfig.concurrent,
|
|
199
|
-
timeout: txRequestConfig.timeout,
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
if (indexdb) {
|
|
203
|
-
this.tokenCache = getTokenCacheInstance(indexdb.token);
|
|
204
|
-
this.tokenDistribution = new TokenDistributionManager(this);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
if (this.tokenItx) {
|
|
208
|
-
this.config.token.address = this.tokenItx.address;
|
|
209
|
-
if (this.tokenCache) {
|
|
210
|
-
this.tokenCache.set(this.tokenItx.address, this.tokenItx);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
this.executor = createExecutor({
|
|
215
|
-
filter,
|
|
216
|
-
runAsLambda: typeof statedb.runAsLambda === 'function' ? statedb.runAsLambda.bind(statedb) : null,
|
|
217
|
-
});
|
|
218
|
-
this.formatTx = this._formatTx.bind(this);
|
|
219
|
-
|
|
220
|
-
if (validateTokenConfig) {
|
|
221
|
-
this.validateTokenConfig();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
this.connectIndexDB();
|
|
225
|
-
this.initializeStateDB().catch((err) => {
|
|
226
|
-
this.logger.error('Failed to initialize statedb:', err.message);
|
|
227
|
-
process.exit(1);
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
sendTx({ tx: txBase64, extra }, ctx = {}) {
|
|
232
|
-
debug('sendTx', { txBase64, request: ctx.request });
|
|
233
|
-
|
|
234
|
-
if (process.env.CHAIN_MODE === 'readonly') {
|
|
235
|
-
throw new CustomError('FORBIDDEN', 'This chain node is running in readonly mode');
|
|
236
|
-
}
|
|
237
|
-
if (this.queue.length >= txRequestConfig.maxRequest) {
|
|
238
|
-
throw new CustomError('FORBIDDEN', 'Chain is busy');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// 0. create new context
|
|
242
|
-
// NOTE: if you add more fields here, please remember to change core/tx-protocols/lib/execute.js
|
|
243
|
-
const context = {
|
|
244
|
-
txBase64,
|
|
245
|
-
statedb: this.statedb,
|
|
246
|
-
indexdb: this.indexdb,
|
|
247
|
-
config: this.config,
|
|
248
|
-
filter: this.filter,
|
|
249
|
-
logger: this.logger,
|
|
250
|
-
extra: {
|
|
251
|
-
...ctx,
|
|
252
|
-
txExtra: extra,
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
return new Promise((resolve, reject) => {
|
|
257
|
-
const task = async () => {
|
|
258
|
-
try {
|
|
259
|
-
const result = await this.executor.execute(context);
|
|
260
|
-
resolve(result.txHash);
|
|
261
|
-
} catch (err) {
|
|
262
|
-
reject(err);
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
this.queue.push(task);
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
getTx({ hash }, ctx) {
|
|
270
|
-
if (patterns.txHash.test(hash)) {
|
|
271
|
-
return this._getState({
|
|
272
|
-
table: 'tx',
|
|
273
|
-
id: hash.toUpperCase(),
|
|
274
|
-
dataKey: 'tx.itxJson.data',
|
|
275
|
-
onRead: (tx) => this.formatTx(tx, ctx),
|
|
276
|
-
expandContext: false,
|
|
277
|
-
ctx,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
getBlock() {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
getBlocks() {
|
|
289
|
-
return [];
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
getUnconfirmedTxs() {
|
|
293
|
-
return [];
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async getChainInfo() {
|
|
297
|
-
return Promise.resolve({
|
|
298
|
-
id: this.chainAddr,
|
|
299
|
-
totalTxs: await this.indexdb.tx.count(),
|
|
300
|
-
blockHash: md5(''),
|
|
301
|
-
blockHeight: 0,
|
|
302
|
-
blockTime: '',
|
|
303
|
-
version: this.config.version,
|
|
304
|
-
address: this.chainAddr,
|
|
305
|
-
appHash: md5(new Date().toISOString()),
|
|
306
|
-
consensusVersion: this.consensus,
|
|
307
|
-
forgeAppsVersion: [],
|
|
308
|
-
moniker: this.config.chainId,
|
|
309
|
-
network: this.config.chainId,
|
|
310
|
-
supportedTxs: this.config.transaction.supportedTxs,
|
|
311
|
-
synced: true,
|
|
312
|
-
votingPower: '10',
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
getNodeInfo() {
|
|
317
|
-
return Promise.resolve({
|
|
318
|
-
address: this.chainAddr,
|
|
319
|
-
appHash: md5(new Date().toISOString()),
|
|
320
|
-
consensusVersion: this.consensus,
|
|
321
|
-
forgeAppsVersion: [],
|
|
322
|
-
geoInfo: {
|
|
323
|
-
city: 'Unknown',
|
|
324
|
-
country: 'Unknown',
|
|
325
|
-
latitude: 0,
|
|
326
|
-
longitude: 0,
|
|
327
|
-
},
|
|
328
|
-
id: this.chainAddr,
|
|
329
|
-
ip: '',
|
|
330
|
-
moniker: this.config.chainId,
|
|
331
|
-
network: this.config.chainId,
|
|
332
|
-
p2pAddress: '',
|
|
333
|
-
supportedTxs: this.config.transaction.supportedTxs,
|
|
334
|
-
synced: true,
|
|
335
|
-
votingPower: '10',
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
getNetInfo() {
|
|
340
|
-
return Promise.resolve({
|
|
341
|
-
listeners: [],
|
|
342
|
-
listening: true,
|
|
343
|
-
nPeers: 0,
|
|
344
|
-
peers: [],
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
getValidatorsInfo() {
|
|
349
|
-
return Promise.resolve({
|
|
350
|
-
blockHeight: 0,
|
|
351
|
-
validators: [
|
|
352
|
-
{
|
|
353
|
-
address: fromPublicKey(this.chainAddr),
|
|
354
|
-
name: '',
|
|
355
|
-
proposerPriority: '0',
|
|
356
|
-
votingPower: '10',
|
|
357
|
-
},
|
|
358
|
-
],
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
getConfig() {
|
|
363
|
-
return JSON.stringify(this.config);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
getAccountState({ address, traceMigration = true, expandContext = true }, ctx) {
|
|
367
|
-
return this._getState({
|
|
368
|
-
table: 'account',
|
|
369
|
-
id: address,
|
|
370
|
-
dataKey: 'data',
|
|
371
|
-
extraParams: { traceMigration },
|
|
372
|
-
expandContext,
|
|
373
|
-
onRead: async (state) => {
|
|
374
|
-
if (state) {
|
|
375
|
-
if (state.tokens) {
|
|
376
|
-
state.tokens = await this.formatTokenMap(state.tokens);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return state;
|
|
381
|
-
},
|
|
382
|
-
ctx,
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
getStakeState({ address }, ctx) {
|
|
387
|
-
return this._getState({
|
|
388
|
-
table: 'stake',
|
|
389
|
-
id: address,
|
|
390
|
-
dataKey: 'data',
|
|
391
|
-
onRead: async (state) => {
|
|
392
|
-
if (state) {
|
|
393
|
-
if (state.tokens) {
|
|
394
|
-
state.tokens = await this.formatTokenMap(state.tokens);
|
|
395
|
-
}
|
|
396
|
-
if (state.revokedTokens) {
|
|
397
|
-
state.revokedTokens = await this.formatTokenMap(state.revokedTokens);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return state;
|
|
402
|
-
},
|
|
403
|
-
ctx,
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
async getRollupState({ address }, ctx) {
|
|
408
|
-
const rollup = await this._getState({
|
|
409
|
-
table: 'rollup',
|
|
410
|
-
id: address,
|
|
411
|
-
dataKey: 'data',
|
|
412
|
-
onRead: async (state) => {
|
|
413
|
-
if (state) {
|
|
414
|
-
const tokens = await this.formatTokenMap({ [state.tokenAddress]: '0' });
|
|
415
|
-
state.tokenInfo = tokens.find((x) => x.address === state.tokenAddress);
|
|
416
|
-
state.foreignToken = state.tokenInfo.foreignToken;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
return state;
|
|
420
|
-
},
|
|
421
|
-
ctx,
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
if (!rollup) {
|
|
425
|
-
return null;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const indexed = await this.indexdb.rollup.get(address);
|
|
429
|
-
if (indexed) {
|
|
430
|
-
rollup.totalDepositAmount = indexed.totalDepositAmount || '0';
|
|
431
|
-
rollup.totalWithdrawAmount = indexed.totalWithdrawAmount || '0';
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return rollup;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async getRollupBlock({ hash, height, rollupAddress }, ctx) {
|
|
438
|
-
if (hash) {
|
|
439
|
-
return this._getState({ table: 'rollupBlock', id: hash, dataKey: 'data', ctx });
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const blockHeight = Number(height || 0);
|
|
443
|
-
if (blockHeight > 0 && rollupAddress) {
|
|
444
|
-
const { blocks } = await this._doPaginatedSearch(
|
|
445
|
-
'listRollupBlocks',
|
|
446
|
-
{ height: blockHeight, rollupAddress },
|
|
447
|
-
'blocks',
|
|
448
|
-
'data',
|
|
449
|
-
ctx
|
|
450
|
-
);
|
|
451
|
-
|
|
452
|
-
if (blocks.length) {
|
|
453
|
-
return this._getState({ table: 'rollupBlock', id: blocks[0].hash, dataKey: 'data', ctx });
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return null;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
throw new Error('Can not get rollup block without hash or height + rollup');
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
getAssetState({ address }, ctx) {
|
|
463
|
-
return this._getState({
|
|
464
|
-
table: 'asset',
|
|
465
|
-
id: address,
|
|
466
|
-
dataKey: 'data',
|
|
467
|
-
onRead: (state) => {
|
|
468
|
-
if (state && Array.isArray(state.tagsList)) {
|
|
469
|
-
state.tags = state.tagsList;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
return state;
|
|
473
|
-
},
|
|
474
|
-
ctx,
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
getEvidenceState({ hash }, ctx) {
|
|
479
|
-
return this._getState({ table: 'evidence', id: hash, dataKey: null, ctx });
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
getFactoryState({ address }, ctx) {
|
|
483
|
-
return this._getState({
|
|
484
|
-
table: 'factory',
|
|
485
|
-
id: address,
|
|
486
|
-
dataKey: 'data',
|
|
487
|
-
onRead: async (state) => {
|
|
488
|
-
if (state) {
|
|
489
|
-
state.tokens = await this.formatTokenMap(state.tokens);
|
|
490
|
-
if (Array.isArray(state.input.tokens) && state.input.tokens.length > 0) {
|
|
491
|
-
state.input.tokens = await this.formatTokenArray(state.input.tokens || []);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
state.output.data = formatData(state.output.data);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return state;
|
|
498
|
-
},
|
|
499
|
-
ctx,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
async getTokenState({ address }, ctx) {
|
|
504
|
-
const state = await this._getState({ table: 'token', id: address, dataKey: 'data', ctx });
|
|
505
|
-
if (state) {
|
|
506
|
-
if (typeof state.decimal === 'undefined') {
|
|
507
|
-
state.decimal = DEFAULT_TOKEN_DECIMAL;
|
|
508
|
-
}
|
|
509
|
-
if (state.metadata) {
|
|
510
|
-
state.metadata = formatData(state.metadata);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
return state;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
getTokenFactoryState(args, ctx) {
|
|
518
|
-
return this._getState({
|
|
519
|
-
table: 'tokenFactory',
|
|
520
|
-
id: args.address,
|
|
521
|
-
dataKey: 'data',
|
|
522
|
-
ctx,
|
|
523
|
-
onRead: async (state) => {
|
|
524
|
-
if (state) {
|
|
525
|
-
const [token, reserveToken] = await Promise.all([
|
|
526
|
-
this.getTokenState({ address: state.tokenAddress }),
|
|
527
|
-
this.getTokenState({ address: state.reserveAddress }),
|
|
528
|
-
]);
|
|
529
|
-
state.token = { ...token, metadata: formatData(token.metadata) };
|
|
530
|
-
state.reserveToken = { ...reserveToken, metadata: formatData(reserveToken.metadata) };
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return state;
|
|
534
|
-
},
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
getDelegateState({ address }, ctx) {
|
|
539
|
-
return this._getState({
|
|
540
|
-
table: 'delegation',
|
|
541
|
-
id: address,
|
|
542
|
-
dataKey: 'data',
|
|
543
|
-
onRead: async (state) => {
|
|
544
|
-
if (state) {
|
|
545
|
-
formatDelegationOps(state);
|
|
546
|
-
await this.formatDelegateState(state);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return state;
|
|
550
|
-
},
|
|
551
|
-
ctx,
|
|
552
|
-
});
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
async getAccountTokens({ address, token }, ctx) {
|
|
556
|
-
if (!address) {
|
|
557
|
-
return [];
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
let state = null;
|
|
561
|
-
|
|
562
|
-
const type = toTypeInfo(address);
|
|
563
|
-
if (type.role === types.RoleType.ROLE_ASSET) {
|
|
564
|
-
state = await this._getState({ table: 'factory', id: address, dataKey: 'data', expandContext: false, ctx });
|
|
565
|
-
} else {
|
|
566
|
-
state = await this._getState({ table: 'account', id: address, dataKey: 'data', expandContext: false, ctx });
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (!state || !state.tokens) {
|
|
570
|
-
return [];
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const allTokens = Object.keys(state.tokens);
|
|
574
|
-
const wantedTokens = token ? allTokens.filter((x) => x === token) : allTokens;
|
|
575
|
-
|
|
576
|
-
const tokens = await Promise.all(
|
|
577
|
-
wantedTokens.map((x) => this._getState({ table: 'token', id: x, dataKey: 'data', expandContext: false, ctx }))
|
|
578
|
-
);
|
|
579
|
-
const tokensInfo = tokens.reduce((acc, x) => {
|
|
580
|
-
acc[x.address] = { symbol: x.symbol, decimal: x.decimal || DEFAULT_TOKEN_DECIMAL, unit: x.util }; // 兼容没有 decimal 的 token
|
|
581
|
-
return acc;
|
|
582
|
-
}, {});
|
|
583
|
-
|
|
584
|
-
return wantedTokens.map((x) => ({
|
|
585
|
-
address: x,
|
|
586
|
-
...tokensInfo[x],
|
|
587
|
-
balance: state.tokens[x],
|
|
588
|
-
}));
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
getForgeState() {
|
|
592
|
-
const { accounts, token, transaction, vaults } = this.config;
|
|
593
|
-
return Promise.resolve({
|
|
594
|
-
accountConfig: accounts,
|
|
595
|
-
address: 'forge_state',
|
|
596
|
-
consensus: {
|
|
597
|
-
maxBytes: '150000',
|
|
598
|
-
maxCandidates: 256,
|
|
599
|
-
maxGas: '-1',
|
|
600
|
-
maxValidators: 64,
|
|
601
|
-
paramChanged: false,
|
|
602
|
-
validatorChanged: false,
|
|
603
|
-
},
|
|
604
|
-
data: null,
|
|
605
|
-
tasks: [],
|
|
606
|
-
token,
|
|
607
|
-
vaults,
|
|
608
|
-
txConfig: {
|
|
609
|
-
...transaction,
|
|
610
|
-
txGas: {
|
|
611
|
-
...transaction.txGas,
|
|
612
|
-
minStake: fromTokenToUnit(transaction.txGas.minStake, token.decimal).toString(10),
|
|
613
|
-
maxStake: fromTokenToUnit(transaction.txGas.maxStake, token.decimal).toString(10),
|
|
614
|
-
},
|
|
615
|
-
txStake: {
|
|
616
|
-
...transaction.txStake,
|
|
617
|
-
createToken: fromTokenToUnit(transaction.txStake.createToken, token.decimal).toString(10),
|
|
618
|
-
createCreditToken: fromTokenToUnit(transaction.txStake.createCreditToken, token.decimal).toString(10),
|
|
619
|
-
},
|
|
620
|
-
txFee: Object.keys(transaction.txFee)
|
|
621
|
-
.filter((x) => x !== 'default')
|
|
622
|
-
.map((x) => ({ typeUrl: x, fee: fromTokenToUnit(transaction.txFee[x], token.decimal).toString(10) })),
|
|
623
|
-
},
|
|
624
|
-
upgradeInfo: null,
|
|
625
|
-
reservedSymbols: this.config.reservedSymbols,
|
|
626
|
-
});
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
async getForgeStats() {
|
|
630
|
-
const [accountCount, assetCount, txCount] = await Promise.all([
|
|
631
|
-
this.indexdb.account.count(),
|
|
632
|
-
this.indexdb.asset.count(),
|
|
633
|
-
this.indexdb.tx.count(),
|
|
634
|
-
]);
|
|
635
|
-
|
|
636
|
-
return {
|
|
637
|
-
avgBlockTime: 1,
|
|
638
|
-
avgTps: 0,
|
|
639
|
-
maxTps: 0,
|
|
640
|
-
numAccountMigrateTxs: ['0'],
|
|
641
|
-
numBlocks: [txCount],
|
|
642
|
-
numConsensusUpgradeTxs: [0],
|
|
643
|
-
numConsumeAssetTxs: ['0'],
|
|
644
|
-
numCreateAssetTxs: [assetCount],
|
|
645
|
-
numDeclareFileTxs: ['0'],
|
|
646
|
-
numDeclareTxs: [accountCount],
|
|
647
|
-
numExchangeTxs: ['0'],
|
|
648
|
-
numPokeTxs: ['0'],
|
|
649
|
-
numStakeTxs: ['0'],
|
|
650
|
-
numStakes: ['0'],
|
|
651
|
-
numSysUpgradeTxs: [0],
|
|
652
|
-
numTransferTxs: ['0'],
|
|
653
|
-
numTxs: [txCount],
|
|
654
|
-
numUpdateAssetTxs: ['0'],
|
|
655
|
-
numValidators: [1],
|
|
656
|
-
tps: [0],
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
async listTransactions(args, ctx) {
|
|
661
|
-
const data = await this._doPaginatedSearch('listTransactions', args, 'transactions', 'tx.itxJson.data', ctx);
|
|
662
|
-
return data;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
listAssets(args, ctx) {
|
|
666
|
-
return this._doPaginatedSearch('listAssets', args, 'assets', 'data', ctx);
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
listAssetTransactions(args = {}, ctx = {}) {
|
|
670
|
-
if (!args.address) {
|
|
671
|
-
return { transactions: [] };
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
return this._doPaginatedSearch(
|
|
675
|
-
'listTransactions',
|
|
676
|
-
{ ...args, assetFilter: { assets: [args.address] } },
|
|
677
|
-
'transactions',
|
|
678
|
-
'tx.itxJson.data',
|
|
679
|
-
ctx
|
|
680
|
-
);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
async listFactories(args, ctx) {
|
|
684
|
-
const result = await this._doPaginatedSearch('listFactories', args, 'factories', 'data', ctx);
|
|
685
|
-
result.factories = result.factories.map((x) => {
|
|
686
|
-
x.output.data = formatData(x.output.data);
|
|
687
|
-
return x;
|
|
688
|
-
});
|
|
689
|
-
|
|
690
|
-
return result;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
listTopAccounts(args) {
|
|
694
|
-
if (!args.tokenAddress) {
|
|
695
|
-
args.tokenAddress = this.tokenItx.address;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
return this._doPaginatedSearch('listTopAccounts', args, 'accounts');
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
listTokens(args, ctx) {
|
|
702
|
-
return this._doPaginatedSearch('listTokens', args, 'tokens', ['data', 'metadata'], ctx);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
listTokenFactories(args, ctx) {
|
|
706
|
-
return this._doPaginatedSearch('listTokenFactories', args, 'tokenFactories', 'data', ctx);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
listStakes(args, ctx) {
|
|
710
|
-
return this._doPaginatedSearch('listStakes', args, 'stakes', 'data', ctx);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
listRollups(args, ctx) {
|
|
714
|
-
return this._doPaginatedSearch('listRollups', args, 'rollups', 'data', ctx);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
listRollupBlocks(args, ctx) {
|
|
718
|
-
return this._doPaginatedSearch('listRollupBlocks', args, 'blocks', 'data', ctx);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
async listRollupValidators(args, ctx) {
|
|
722
|
-
if (!args.rollupAddress) {
|
|
723
|
-
return { validators: [] };
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const rollup = await this.statedb.rollup.get(args.rollupAddress);
|
|
727
|
-
if (!rollup) {
|
|
728
|
-
return { validators: [] };
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
const result = await this._doPaginatedSearch('listRollupValidators', args, 'validators', null, ctx);
|
|
732
|
-
const stakes = (
|
|
733
|
-
await Promise.all(
|
|
734
|
-
result.validators.map((x) => {
|
|
735
|
-
const stakeAddress = toStakeAddress(x.address, args.rollupAddress);
|
|
736
|
-
return this.statedb.stake.get(stakeAddress);
|
|
737
|
-
})
|
|
738
|
-
)
|
|
739
|
-
).filter(Boolean);
|
|
740
|
-
result.validators.forEach((x) => {
|
|
741
|
-
const stakeAddress = toStakeAddress(x.address, args.rollupAddress);
|
|
742
|
-
const stake = stakes.find((s) => s.address === stakeAddress);
|
|
743
|
-
x.availableStake = stake ? stake.tokens[rollup.tokenAddress] : '0';
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
return result;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
listDelegations(args, ctx) {
|
|
750
|
-
return this._doPaginatedSearch('listDelegations', args, 'delegations', 'data', ctx);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
verifyAccountRisk(args, ctx) {
|
|
754
|
-
if (process.env.TOKEN_FLOW_ENABLED === 'true') {
|
|
755
|
-
return tokenFlow.verifyAccountRisk(
|
|
756
|
-
{
|
|
757
|
-
...args,
|
|
758
|
-
tokenAddress: toAddress(args.tokenAddress || this.config.token.address),
|
|
759
|
-
accountAddress: toAddress(args.accountAddress || ''),
|
|
760
|
-
accountLimit: process.env.VERIFY_RISK_ACCOUNT_LIMIT,
|
|
761
|
-
txLimit: process.env.VERIFY_RISK_TX_LIMIT,
|
|
762
|
-
tolerance: process.env.VERIFY_RISK_TOLERANCE,
|
|
763
|
-
},
|
|
764
|
-
this,
|
|
765
|
-
ctx
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
listTokenFlows(args, ctx) {
|
|
772
|
-
if (process.env.TOKEN_FLOW_ENABLED === 'true') {
|
|
773
|
-
return tokenFlow.listTokenFlows(args, this, ctx);
|
|
774
|
-
}
|
|
775
|
-
return [];
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
async search(args) {
|
|
779
|
-
if (!args.keyword) {
|
|
780
|
-
return { results: [] };
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
const doSearch = async (type, keyword) => {
|
|
784
|
-
const result = await this.indexdb[type].get(keyword);
|
|
785
|
-
return result ? { type, id: keyword } : null;
|
|
786
|
-
};
|
|
787
|
-
|
|
788
|
-
if (patterns.txHash.test(args.keyword)) {
|
|
789
|
-
const results = await Promise.all([
|
|
790
|
-
doSearch('tx', args.keyword.toUpperCase()),
|
|
791
|
-
doSearch('rollupBlock', args.keyword),
|
|
792
|
-
]);
|
|
793
|
-
return { results: results.filter(Boolean) };
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
const entitiesByDid = ['account', 'asset', 'delegation', 'factory', 'token', 'stake', 'rollup', 'tokenFactory'];
|
|
797
|
-
if (isValidDid(args.keyword)) {
|
|
798
|
-
const keyword = toAddress(args.keyword);
|
|
799
|
-
const results = await Promise.all(entitiesByDid.map((type) => doSearch(type, keyword)));
|
|
800
|
-
return { results: results.filter(Boolean) };
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
return { results: [] };
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
estimateGas(args = {}) {
|
|
807
|
-
const { typeUrl = '' } = args;
|
|
808
|
-
if (!typeUrl || !maxGasOps[typeUrl]) {
|
|
809
|
-
throw new CustomError('INVALID_REQUEST', 'Unknown tx typeUrl');
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
const { txGas, maxTxSize } = this.config.transaction;
|
|
813
|
-
const estimate = maxGasOps[args.typeUrl];
|
|
814
|
-
let totalGas = new BN(txGas.price).mul(new BN(txGas.dataStorage)).mul(new BN(maxTxSize[args.typeUrl]));
|
|
815
|
-
totalGas = totalGas.add(new BN(txGas.price).mul(new BN(txGas.createState)).mul(new BN(estimate.create + 1))); // 1 = tx insert
|
|
816
|
-
totalGas = totalGas.add(new BN(txGas.price).mul(new BN(txGas.updateState)).mul(new BN(estimate.update)));
|
|
817
|
-
return { estimate: { max: totalGas.toString(10) } };
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
listBlocks() {
|
|
821
|
-
return { blocks: [] };
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
validateTokenConfig() {
|
|
825
|
-
const { accounts, token } = this.config;
|
|
826
|
-
const sum = accounts.reduce((acc, x) => acc.add(toBN(x.balance)), toBN(0));
|
|
827
|
-
const initialSupply = toBN(token.initialSupply);
|
|
828
|
-
if (!sum.eq(initialSupply)) {
|
|
829
|
-
throw new Error('Invalid config, account balance sum does not equal to token supply');
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
runAsLambda(fn, args) {
|
|
834
|
-
if (typeof this.statedb.runAsLambda === 'function') {
|
|
835
|
-
return this.statedb.runAsLambda((txn) => fn(txn), args);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
return fn();
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
getChain() {
|
|
842
|
-
return this.statedb.chain.get(CHAIN_ADDR);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
getToken(id) {
|
|
846
|
-
return this.statedb.token.get(id);
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
async initializeStateDB() {
|
|
850
|
-
const { accounts, token } = this.config;
|
|
851
|
-
const { account: accountDB, chain: chainDB, token: tokenDB } = this.statedb;
|
|
852
|
-
const { account: accountState, chain: chainState, token: tokenState } = states;
|
|
853
|
-
|
|
854
|
-
const context = { txTime: new Date().toISOString(), txHash: '' };
|
|
855
|
-
|
|
856
|
-
// Auto persist config to chain state
|
|
857
|
-
// Will throw error if immutable chain config are updated
|
|
858
|
-
await this.runAsLambda(
|
|
859
|
-
async (txn) => {
|
|
860
|
-
const info = await this.statedb.chain.get(CHAIN_ADDR, { txn });
|
|
861
|
-
const config = omit(this.config, ['reservedSymbols']);
|
|
862
|
-
|
|
863
|
-
if (!info) {
|
|
864
|
-
const state = chainState.create({ ...config, address: CHAIN_ADDR }, context);
|
|
865
|
-
const result = await chainDB.create(CHAIN_ADDR, state, { txn });
|
|
866
|
-
this.logger.debug('create chain state', { result });
|
|
867
|
-
} else if (isEqual(pick(info, Object.keys(config)), config) === false) {
|
|
868
|
-
const state = chainState.update(info, config, context);
|
|
869
|
-
const result = await chainDB.update(CHAIN_ADDR, state, { txn });
|
|
870
|
-
this.logger.debug('update chain state', { result });
|
|
871
|
-
}
|
|
872
|
-
},
|
|
873
|
-
{ commitMessage: 'initialize chain' }
|
|
874
|
-
);
|
|
875
|
-
|
|
876
|
-
// Auto persist token state, just once
|
|
877
|
-
// Since the token info should not be changed after restart
|
|
878
|
-
if (this.tokenItx) {
|
|
879
|
-
await this.runAsLambda(
|
|
880
|
-
async (txn) => {
|
|
881
|
-
const existToken = await this.statedb.token.get(this.tokenItx.address);
|
|
882
|
-
|
|
883
|
-
if (!existToken) {
|
|
884
|
-
const state = tokenState.create(this.tokenItx, context);
|
|
885
|
-
const result = await tokenDB.create(this.tokenItx.address, state, { txn });
|
|
886
|
-
tokenDB.emit('create', result, { txn });
|
|
887
|
-
this.logger.debug('create token state', { address: result?.address });
|
|
888
|
-
} else {
|
|
889
|
-
// update token state if metadata are changed
|
|
890
|
-
const changedKeys = ['icon', 'website', 'metadata'];
|
|
891
|
-
const pickStateToken = pick(existToken, changedKeys);
|
|
892
|
-
const pickConfigToken = pick(this.tokenItx, changedKeys);
|
|
893
|
-
|
|
894
|
-
if (!isEqual(pickStateToken, pickConfigToken)) {
|
|
895
|
-
const state = tokenState.update(existToken, pickConfigToken, context);
|
|
896
|
-
const result = await tokenDB.update(this.tokenItx.address, state, { txn });
|
|
897
|
-
tokenDB.emit('update', result, { txn });
|
|
898
|
-
this.logger.info('update token state', { result });
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
},
|
|
902
|
-
{ commitMessage: 'initialize token' }
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
// Auto populate token holder accounts if not exist
|
|
906
|
-
await this.runAsLambda(
|
|
907
|
-
async (txn) => {
|
|
908
|
-
for (let i = 0; i < accounts.length; i++) {
|
|
909
|
-
const { address, balance, moniker, pk } = accounts[i];
|
|
910
|
-
try {
|
|
911
|
-
const existAccount = await accountDB.get(address, { txn });
|
|
912
|
-
if (!existAccount) {
|
|
913
|
-
const balanceStr = fromTokenToUnit(balance, token.decimal).toString(10);
|
|
914
|
-
const state = accountState.create(
|
|
915
|
-
{ address, tokens: { [this.tokenItx.address]: balanceStr }, moniker: moniker || 'token-holder', pk },
|
|
916
|
-
context
|
|
917
|
-
);
|
|
918
|
-
const result = await accountDB.create(address, state, { txn });
|
|
919
|
-
accountDB.emit('create', result, { txn });
|
|
920
|
-
this.logger.info('create account done', { address });
|
|
921
|
-
}
|
|
922
|
-
} catch (err) {
|
|
923
|
-
this.logger.error('Failed to initialize initial token holders', err);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
},
|
|
927
|
-
{ commitMessage: 'initialize accounts' }
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
connectIndexDB() {
|
|
933
|
-
const getToken = this.getToken.bind(this);
|
|
934
|
-
|
|
935
|
-
this.statedb.tx.on('create', async (x, ctx) => {
|
|
936
|
-
try {
|
|
937
|
-
const tx = await createIndexedTransaction(x, ctx, this.indexdb);
|
|
938
|
-
await this.indexdb.tx.insert(tx);
|
|
939
|
-
if (typeof hooks.onCreateTx === 'function') {
|
|
940
|
-
await hooks.onCreateTx(tx, ctx, this);
|
|
941
|
-
}
|
|
942
|
-
} catch (error) {
|
|
943
|
-
this.logger.error('create tx index failed', { account: x, error });
|
|
944
|
-
}
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
this.statedb.account.on('create', async (x) => {
|
|
948
|
-
try {
|
|
949
|
-
await this.indexdb.account.insert(await createIndexedAccount(x, getToken));
|
|
950
|
-
} catch (error) {
|
|
951
|
-
this.logger.error('create account index failed', { account: x, error });
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
|
-
this.statedb.account.on('update', async (x) => {
|
|
955
|
-
try {
|
|
956
|
-
await this.indexdb.account.update(x.address, await createIndexedAccount(x, getToken));
|
|
957
|
-
} catch (error) {
|
|
958
|
-
this.logger.error('update account index failed', { account: x, error });
|
|
959
|
-
}
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
const mapping = {
|
|
963
|
-
asset: [createIndexedAsset, 'address'],
|
|
964
|
-
delegation: [createIndexedDelegation, 'address'],
|
|
965
|
-
token: [createIndexedToken, 'address', null, hooks.onUpdateToken],
|
|
966
|
-
factory: [createIndexedFactory, 'address'],
|
|
967
|
-
stake: [createIndexedStake, 'address'],
|
|
968
|
-
rollup: [createIndexedRollup, 'address', hooks.onCreateRollup],
|
|
969
|
-
rollupBlock: [createIndexedRollupBlock, 'hash', hooks.onCreateRollupBlock],
|
|
970
|
-
tokenFactory: [createIndexedTokenFactory, 'address'],
|
|
971
|
-
};
|
|
972
|
-
Object.keys(mapping).forEach((table) => {
|
|
973
|
-
const [fn, key, onCreate, onUpdate] = mapping[table];
|
|
974
|
-
|
|
975
|
-
this.statedb[table].on('create', async (x, _ctx) => {
|
|
976
|
-
try {
|
|
977
|
-
const ctx = this.enrichIndexContext(_ctx);
|
|
978
|
-
const doc = await fn(x, ctx, this.indexdb);
|
|
979
|
-
await this.indexdb[table].insert(doc);
|
|
980
|
-
if (typeof onCreate === 'function') {
|
|
981
|
-
await onCreate(doc, ctx, this.indexdb, this);
|
|
982
|
-
}
|
|
983
|
-
} catch (error) {
|
|
984
|
-
this.logger.error(`create ${table} index failed`, { [table]: x, error });
|
|
985
|
-
}
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
this.statedb[table].on('update', async (x, _ctx) => {
|
|
989
|
-
try {
|
|
990
|
-
const ctx = this.enrichIndexContext(_ctx);
|
|
991
|
-
const doc = await fn(x, ctx, this.indexdb);
|
|
992
|
-
await this.indexdb[table].update(doc[key], doc);
|
|
993
|
-
if (typeof onUpdate === 'function') {
|
|
994
|
-
await onUpdate(doc, ctx, this.indexdb, this);
|
|
995
|
-
}
|
|
996
|
-
} catch (error) {
|
|
997
|
-
this.logger.error(`update ${table} index failed`, { [table]: x, error });
|
|
998
|
-
}
|
|
999
|
-
});
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
async _getState({ table, id, dataKey, extraParams = {}, onRead = noop, expandContext = true, ctx }) {
|
|
1004
|
-
if (!id) {
|
|
1005
|
-
throw new CustomError('INVALID_REQUEST', `Missing required parameter to read ${table} state`);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
const unified = isValidDid(id) ? toAddress(id) : id;
|
|
1009
|
-
const state = await this.statedb[table].get(unified, { ...extraParams });
|
|
1010
|
-
if (state) {
|
|
1011
|
-
if (dataKey) {
|
|
1012
|
-
set(state, dataKey, formatData(get(state, dataKey)));
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// expand context transactions
|
|
1016
|
-
if (expandContext && state.context) {
|
|
1017
|
-
const { genesisTx, renaissanceTx } = state.context;
|
|
1018
|
-
const txs = (
|
|
1019
|
-
await Promise.all(
|
|
1020
|
-
uniq([genesisTx, renaissanceTx])
|
|
1021
|
-
.filter(Boolean)
|
|
1022
|
-
.map((x) => this.getTx({ hash: x }, ctx))
|
|
1023
|
-
)
|
|
1024
|
-
).filter(Boolean);
|
|
1025
|
-
state.context.genesisTx = txs.find((x) => x.hash === genesisTx);
|
|
1026
|
-
state.context.renaissanceTx = txs.find((x) => x.hash === renaissanceTx);
|
|
1027
|
-
|
|
1028
|
-
if (!state.context.genesisTx) {
|
|
1029
|
-
state.context.genesisTx = { hash: genesisTx };
|
|
1030
|
-
}
|
|
1031
|
-
if (!state.context.renaissanceTx) {
|
|
1032
|
-
state.context.renaissanceTx = { hash: renaissanceTx };
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
return onRead(state);
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
async _doPaginatedSearch(fn, args, listKey, dataKey, ctx) {
|
|
1041
|
-
const result = await this.indexdb[fn](args);
|
|
1042
|
-
const { [listKey]: items = [], ...rest } = result;
|
|
1043
|
-
let data = items;
|
|
1044
|
-
|
|
1045
|
-
if (dataKey) {
|
|
1046
|
-
data = await Promise.all(
|
|
1047
|
-
items.map(async (x) => {
|
|
1048
|
-
let tx = x;
|
|
1049
|
-
// dataKey can be an array or string
|
|
1050
|
-
[].concat(dataKey).forEach((key) => {
|
|
1051
|
-
tx = set(tx, key, formatData(get(tx, key)));
|
|
1052
|
-
});
|
|
1053
|
-
if (listKey === 'transactions') {
|
|
1054
|
-
tx = await this.formatTx(tx, ctx);
|
|
1055
|
-
}
|
|
1056
|
-
if (listKey === 'delegations') {
|
|
1057
|
-
tx = await this.formatDelegateState(tx);
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
return tx;
|
|
1061
|
-
})
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
const final = { [listKey]: data, ...rest };
|
|
1066
|
-
if (!final.paging) {
|
|
1067
|
-
final.paging = { cursor: '0', next: false, total: 0 };
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
return final;
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
async _formatTx(tx, ctx) {
|
|
1074
|
-
if (!tx) {
|
|
1075
|
-
return tx;
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
if (!tx.sender && !tx.receiver) {
|
|
1079
|
-
const typeUrl = fromTypeUrl(tx.tx.itx.typeUrl);
|
|
1080
|
-
tx.sender = getTxSender({ tx: tx.tx, itx: tx.tx.itxJson, typeUrl });
|
|
1081
|
-
tx.receiver = getTxReceiver({ tx: tx.tx, itx: tx.tx.itxJson, typeUrl });
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
if (!tx.receipts) {
|
|
1085
|
-
tx.receipts = getTxReceipts(tx, { config: this.config });
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
const typeUrl = get(tx, 'tx.itxJson.type_url');
|
|
1089
|
-
|
|
1090
|
-
if (typeUrl === 'fg:t:revoke_withdraw' || typeUrl === 'fg:t:approve_withdraw') {
|
|
1091
|
-
const withdrawHash = get(tx, 'tx.itxJson.withdraw_tx_hash');
|
|
1092
|
-
// Avoid infinite loop by invalid data
|
|
1093
|
-
if (withdrawHash && withdrawHash !== tx.hash) {
|
|
1094
|
-
const withdrawTx = await this.getTx({ hash: withdrawHash }, ctx);
|
|
1095
|
-
tx.receipts = getTxReceipts(tx, { config: this.config, withdrawTx });
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
if (
|
|
1100
|
-
[
|
|
1101
|
-
// https://github.com/blocklet/abt-wallet/issues/681
|
|
1102
|
-
'fg:t:deposit_token',
|
|
1103
|
-
'fg:t:withdraw_token',
|
|
1104
|
-
'fg:t:revoke_swap',
|
|
1105
|
-
].includes(typeUrl)
|
|
1106
|
-
) {
|
|
1107
|
-
tx.receipts = getTxReceipts(tx, { config: this.config });
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
if (typeUrl === 'fg:t:account_migrate') {
|
|
1111
|
-
const fromState = await this.statedb.account.get(tx.tx.from, { traceMigration: false });
|
|
1112
|
-
if (fromState) {
|
|
1113
|
-
tx.receipts = getTxReceipts(tx, { config: this.config, fromState });
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
if (typeUrl === 'fg:t:declare') {
|
|
1118
|
-
tx.receipts = getTxReceipts(tx, { config: this.config, time: tx.time });
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
tx.receipts = mergeTxReceipts(tx.receipts);
|
|
1122
|
-
tx.tokenSymbols = await this.getTxTokenSymbols(tx);
|
|
1123
|
-
|
|
1124
|
-
this.fixReceiptTokens(tx, ctx);
|
|
1125
|
-
this.fixTokenSymbols(tx, ctx);
|
|
1126
|
-
|
|
1127
|
-
return attachPaidTxGas(tx);
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
async _getAllResults(dataKey, fn, limit) {
|
|
1131
|
-
const pageSize = 100;
|
|
1132
|
-
const { paging, [dataKey]: firstPage } = await fn({ size: pageSize });
|
|
1133
|
-
|
|
1134
|
-
// Skip if total exceeds limit cause we cannot query full data
|
|
1135
|
-
if (limit && paging.total > limit) {
|
|
1136
|
-
throw new CustomError('EXCEED_LIMIT', `Total exceeds limit ${limit}`);
|
|
1137
|
-
}
|
|
1138
|
-
if (paging.total <= pageSize) {
|
|
1139
|
-
return firstPage;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
const totalPage = Math.ceil(paging.total / pageSize) - 1;
|
|
1143
|
-
const cursors = new Array(totalPage).fill(true).map((_, i) => (i + 1) * pageSize);
|
|
1144
|
-
const step = process.env.RESOLVER_BATCH_CONCURRENCY || 3;
|
|
1145
|
-
let results = firstPage;
|
|
1146
|
-
|
|
1147
|
-
for (let i = 0; i < cursors.length; i += step) {
|
|
1148
|
-
const batchResults = await Promise.all(
|
|
1149
|
-
cursors.slice(i, i + step).map(async (cursor) => {
|
|
1150
|
-
const { [dataKey]: list } = await fn({ size: pageSize, cursor });
|
|
1151
|
-
return list;
|
|
1152
|
-
})
|
|
1153
|
-
);
|
|
1154
|
-
results = results.concat(batchResults.flat());
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
return results;
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
async formatTokenArray(tokens) {
|
|
1161
|
-
const uniqTokens = uniqBy(tokens, 'address');
|
|
1162
|
-
const tokenStates = await Promise.all(uniqTokens.map((token) => this.tokenCache.get(token.address)));
|
|
1163
|
-
|
|
1164
|
-
return uniqTokens.map((token) => ({ ...token, ...extractTokenMeta(token.address, tokenStates) }));
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
async formatTokenMap(tokens) {
|
|
1168
|
-
if (isEmpty(tokens)) {
|
|
1169
|
-
return [];
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
const tasks = Object.keys(tokens).map((address) => this.tokenCache.get(address));
|
|
1173
|
-
const tokenStates = await Promise.all(tasks);
|
|
1174
|
-
|
|
1175
|
-
return Object.keys(tokens).map((address) => ({
|
|
1176
|
-
value: tokens[address],
|
|
1177
|
-
...extractTokenMeta(address, tokenStates),
|
|
1178
|
-
}));
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
async getTxTokenSymbols(tx) {
|
|
1182
|
-
if (!tx) {
|
|
1183
|
-
return [];
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
const rollupTxs = [
|
|
1187
|
-
'DepositTokenV2Tx',
|
|
1188
|
-
'WithdrawTokenV2Tx',
|
|
1189
|
-
'JoinRollupTx',
|
|
1190
|
-
'LeaveRollupTx',
|
|
1191
|
-
'CreateRollupBlockTx',
|
|
1192
|
-
'ClaimBlockRewardTx',
|
|
1193
|
-
];
|
|
1194
|
-
const typeUrl = formatTxType(tx.tx.itxJson._type);
|
|
1195
|
-
const tokens = [];
|
|
1196
|
-
|
|
1197
|
-
if (isDefaultTokenChanged(tx, this.config.token)) {
|
|
1198
|
-
tokens.push(this.tokenItx);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
if (typeUrl === 'AcquireAssetV2Tx') {
|
|
1202
|
-
const factory = await this.indexdb.factory.get(tx.tx.itxJson.factory);
|
|
1203
|
-
tokens.push(...factory.input.tokens);
|
|
1204
|
-
} else if (typeUrl === 'ExchangeV2Tx') {
|
|
1205
|
-
tokens.push(...tx.tx.itxJson.sender.tokens);
|
|
1206
|
-
tokens.push(...tx.tx.itxJson.receiver.tokens);
|
|
1207
|
-
} else if (typeUrl === 'TransferV2Tx') {
|
|
1208
|
-
tokens.push(...tx.tx.itxJson.tokens);
|
|
1209
|
-
} else if (['TransferV3Tx', 'AcquireAssetV3Tx', 'StakeTx'].includes(typeUrl)) {
|
|
1210
|
-
tokens.push(
|
|
1211
|
-
...uniqBy(
|
|
1212
|
-
tx.tx.itxJson.inputs.reduce((acc, x) => acc.concat(x.tokens), []),
|
|
1213
|
-
'address'
|
|
1214
|
-
).filter((x) => x.address)
|
|
1215
|
-
);
|
|
1216
|
-
} else if (['RevokeStakeTx', 'SlashStakeTx', 'ReturnStakeTx'].includes(typeUrl)) {
|
|
1217
|
-
tokens.push(
|
|
1218
|
-
...uniqBy(
|
|
1219
|
-
tx.tx.itxJson.outputs.reduce((acc, x) => acc.concat(x.tokens), []),
|
|
1220
|
-
'address'
|
|
1221
|
-
).filter((x) => x.address)
|
|
1222
|
-
);
|
|
1223
|
-
} else if (typeUrl === 'ClaimStakeTx') {
|
|
1224
|
-
const revokeTx = await this.indexdb.tx.get(tx.tx.itxJson.evidence.hash);
|
|
1225
|
-
if (revokeTx) {
|
|
1226
|
-
tokens.push(
|
|
1227
|
-
...uniqBy(
|
|
1228
|
-
revokeTx.tx.itxJson.outputs.reduce((acc, x) => acc.concat(x.tokens), []),
|
|
1229
|
-
'address'
|
|
1230
|
-
).filter((x) => x.address)
|
|
1231
|
-
);
|
|
1232
|
-
} else {
|
|
1233
|
-
this.logger.warn('Revoke stake tx not found', { tx });
|
|
1234
|
-
}
|
|
1235
|
-
} else if (rollupTxs.includes(typeUrl)) {
|
|
1236
|
-
const rollup = await this.indexdb.rollup.get(tx.tx.itxJson.rollup);
|
|
1237
|
-
tokens.push({ address: rollup.tokenAddress, value: '0' });
|
|
1238
|
-
} else if (typeUrl === 'CreateTokenTx') {
|
|
1239
|
-
return [extractTokenMeta(tx.tx.itxJson.address, [tx.tx.itxJson])];
|
|
1240
|
-
} else if (typeUrl === 'MintTokenTx' || typeUrl === 'BurnTokenTx') {
|
|
1241
|
-
const token = await this.indexdb.tokenFactory.get(tx.tx.itxJson.tokenFactory);
|
|
1242
|
-
tokens.push({ address: token.tokenAddress });
|
|
1243
|
-
} else if (typeUrl === 'CreateTokenFactoryTx') {
|
|
1244
|
-
tokens.push({ address: tx.tx.itxJson.token.address });
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
if (tokens.length > 0) {
|
|
1248
|
-
return this.formatTokenArray(tokens);
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
return [];
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
fixReceiptTokens(tx) {
|
|
1255
|
-
// Auto populate initial token address in tx receipts
|
|
1256
|
-
tx.receipts.forEach((receipt) =>
|
|
1257
|
-
receipt.changes.forEach((x) => {
|
|
1258
|
-
if (x.target === '') {
|
|
1259
|
-
x.target = this.tokenItx.address;
|
|
1260
|
-
}
|
|
1261
|
-
})
|
|
1262
|
-
);
|
|
1263
|
-
|
|
1264
|
-
return tx;
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
fixTokenSymbols(tx) {
|
|
1268
|
-
const index = tx.tokenSymbols.findIndex((x) => x.address === this.tokenItx.address);
|
|
1269
|
-
if (index === -1) {
|
|
1270
|
-
tx.tokenSymbols.push(extractTokenMeta(this.tokenItx.address, [this.tokenItx]));
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
return tx;
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
enrichIndexContext(ctx) {
|
|
1277
|
-
if (Array.isArray(ctx.tokenStates)) {
|
|
1278
|
-
return { ...ctx, tokenStates: [...ctx.tokenStates, this.tokenItx] };
|
|
1279
|
-
}
|
|
1280
|
-
if (ctx.tokenState) {
|
|
1281
|
-
return { ...ctx, tokenStates: [ctx.tokenState] };
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
return ctx;
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
async formatDelegateState(state) {
|
|
1288
|
-
if (Array.isArray(state.ops) && state.ops.length > 0) {
|
|
1289
|
-
await Promise.all(
|
|
1290
|
-
state.ops.map(async (op) => {
|
|
1291
|
-
if (op.value.limit && Array.isArray(op.value.limit.tokens)) {
|
|
1292
|
-
op.value.limit.tokens = await this.formatTokenArray(op.value.limit.tokens || []);
|
|
1293
|
-
}
|
|
1294
|
-
})
|
|
1295
|
-
);
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
return state;
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
async buildMigrationChain() {
|
|
1302
|
-
const migrationTxs = await this._getAllResults('transactions', (paging) =>
|
|
1303
|
-
this.listTransactions({
|
|
1304
|
-
paging,
|
|
1305
|
-
typeFilter: { types: ['account_migrate'] },
|
|
1306
|
-
validityFilter: { validity: 'VALID' },
|
|
1307
|
-
})
|
|
1308
|
-
);
|
|
1309
|
-
const migrationChain = new MigrationChainManager();
|
|
1310
|
-
migrationChain.buildChains(migrationTxs);
|
|
1311
|
-
this.migrationChain = migrationChain;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
async getMigrationChain() {
|
|
1315
|
-
if (!this.migrationChain) {
|
|
1316
|
-
await this.buildMigrationChain();
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
return this.migrationChain;
|
|
1320
|
-
}
|
|
1321
|
-
|
|
1322
|
-
/** fix accounts field of legacy approve_withdraw transactions in indexdb */
|
|
1323
|
-
async fixApproveWithdrawAccounts() {
|
|
1324
|
-
const approveWithdrawTxs = await this._getAllResults('transactions', (paging) =>
|
|
1325
|
-
this.listTransactions({
|
|
1326
|
-
paging,
|
|
1327
|
-
typeFilter: { types: ['approve_withdraw'] },
|
|
1328
|
-
validityFilter: { validity: ['VALID'] },
|
|
1329
|
-
})
|
|
1330
|
-
);
|
|
1331
|
-
|
|
1332
|
-
const updatedTxs = await Promise.all(
|
|
1333
|
-
approveWithdrawTxs.map(async (tx) => {
|
|
1334
|
-
const withdrawHash = tx.tx?.itxJson?.withdraw_tx_hash;
|
|
1335
|
-
|
|
1336
|
-
if (!withdrawHash) return;
|
|
1337
|
-
|
|
1338
|
-
const indexdbTx = await this.indexdb.tx.get(tx.hash);
|
|
1339
|
-
const withdrawTx = await this.getTx({ hash: withdrawHash });
|
|
1340
|
-
const from = withdrawTx?.tx?.from;
|
|
1341
|
-
|
|
1342
|
-
if (!from || !indexdbTx) return;
|
|
1343
|
-
|
|
1344
|
-
const accounts = uniq((indexdbTx.accounts || []).concat([from]).filter(Boolean));
|
|
1345
|
-
debug('fixApproveWithdrawAccounts', { tx, from, accounts });
|
|
1346
|
-
|
|
1347
|
-
// update indexdb
|
|
1348
|
-
const updatedTx = await this.indexdb.tx.update(tx.hash, { accounts });
|
|
1349
|
-
return updatedTx;
|
|
1350
|
-
})
|
|
1351
|
-
);
|
|
1352
|
-
|
|
1353
|
-
return updatedTxs.filter(Boolean);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
async updateTokenDistribution({ tokenAddress = this.config.token.address, force = false } = {}) {
|
|
1357
|
-
const paramsSchema = Joi.object({
|
|
1358
|
-
tokenAddress: Joi.DID().prefix().role('ROLE_TOKEN').required(),
|
|
1359
|
-
force: Joi.boolean().required(),
|
|
1360
|
-
});
|
|
1361
|
-
const validation = paramsSchema.validate({ tokenAddress, force });
|
|
1362
|
-
if (validation.error) {
|
|
1363
|
-
throw new CustomError('INVALID_PARAMS', validation.error.message);
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
const data = await this.tokenDistribution.updateByToken(tokenAddress, force);
|
|
1367
|
-
return data;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
async getTokenDistribution({ tokenAddress = this.config.token.address }) {
|
|
1371
|
-
const data = await this.tokenDistribution.getDistribution(tokenAddress);
|
|
1372
|
-
return data;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
/**
|
|
1376
|
-
* Fetch data in chunks to handle large datasets
|
|
1377
|
-
* @param {Function} fn Function to fetch data
|
|
1378
|
-
* @param {Object} param
|
|
1379
|
-
* @param {number} [param.concurrency=3] Number of concurrent requests
|
|
1380
|
-
* @param {number} [param.chunkSize=2000] Maximum number of items to return in each next() call
|
|
1381
|
-
* @param {number} [param.pageSize=100] Number of items per page
|
|
1382
|
-
* @param {string} param.timeKey Key to access time in response
|
|
1383
|
-
* @param {string} param.dataKey Key to access data in response
|
|
1384
|
-
* @returns {Promise<Array>} Array of fetched items
|
|
1385
|
-
*/
|
|
1386
|
-
listChunks(fn, { total, concurrency = 3, chunkSize = 2100, pageSize = 100, timeKey, dataKey }) {
|
|
1387
|
-
let done = false;
|
|
1388
|
-
let time;
|
|
1389
|
-
const totalPage = Math.ceil(total / pageSize);
|
|
1390
|
-
let curPage = 0;
|
|
1391
|
-
|
|
1392
|
-
const next = async () => {
|
|
1393
|
-
if (done) {
|
|
1394
|
-
return [];
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
let results = [];
|
|
1398
|
-
|
|
1399
|
-
// next pages
|
|
1400
|
-
for (; curPage < totalPage; curPage += concurrency) {
|
|
1401
|
-
const batchResults = await Promise.all(
|
|
1402
|
-
new Array(Math.min(concurrency, totalPage - curPage)).fill(true).map(async (_, i) => {
|
|
1403
|
-
const { [dataKey]: list } = await fn({
|
|
1404
|
-
size: pageSize,
|
|
1405
|
-
cursor: i * pageSize,
|
|
1406
|
-
time,
|
|
1407
|
-
});
|
|
1408
|
-
return list;
|
|
1409
|
-
})
|
|
1410
|
-
);
|
|
1411
|
-
const flatResults = batchResults.flat();
|
|
1412
|
-
|
|
1413
|
-
// finish
|
|
1414
|
-
if (!flatResults.length) {
|
|
1415
|
-
done = true;
|
|
1416
|
-
return results;
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
results = results.concat(flatResults);
|
|
1420
|
-
time = results.length ? results[results.length - 1][timeKey] : null;
|
|
1421
|
-
|
|
1422
|
-
// limit
|
|
1423
|
-
if (results.length >= chunkSize) {
|
|
1424
|
-
return results;
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
done = true;
|
|
1429
|
-
return results;
|
|
1430
|
-
};
|
|
1431
|
-
|
|
1432
|
-
return {
|
|
1433
|
-
next,
|
|
1434
|
-
done,
|
|
1435
|
-
};
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
async listTransactionsChunks(args = {}, { chunkSize = 2100, pageSize = 100 } = {}) {
|
|
1439
|
-
return this.listChunks(
|
|
1440
|
-
({ size, time, cursor }) =>
|
|
1441
|
-
this.listTransactions({
|
|
1442
|
-
...args,
|
|
1443
|
-
paging: {
|
|
1444
|
-
order: { field: 'time', type: 'asc' },
|
|
1445
|
-
...(args.paging || {}),
|
|
1446
|
-
size,
|
|
1447
|
-
cursor,
|
|
1448
|
-
},
|
|
1449
|
-
timeFilter: Object.assign(args.timeFilter || {}, time ? { startDateTime: time } : {}),
|
|
1450
|
-
}),
|
|
1451
|
-
{
|
|
1452
|
-
dataKey: 'transactions',
|
|
1453
|
-
timeKey: 'time',
|
|
1454
|
-
total: await this.indexdb.tx.count(),
|
|
1455
|
-
chunkSize,
|
|
1456
|
-
pageSize,
|
|
1457
|
-
}
|
|
1458
|
-
);
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
async listStakeChunks(args = {}, { chunkSize = 2100, pageSize = 100 } = {}) {
|
|
1462
|
-
return this.listChunks(
|
|
1463
|
-
({ size, time, cursor }) =>
|
|
1464
|
-
this.listStakes({
|
|
1465
|
-
...args,
|
|
1466
|
-
paging: {
|
|
1467
|
-
order: { field: 'time', type: 'asc' },
|
|
1468
|
-
...(args.paging || {}),
|
|
1469
|
-
size,
|
|
1470
|
-
cursor,
|
|
1471
|
-
},
|
|
1472
|
-
timeFilter: Object.assign(args.timeFilter || {}, time ? { startDateTime: time } : {}),
|
|
1473
|
-
}),
|
|
1474
|
-
{
|
|
1475
|
-
dataKey: 'stakes',
|
|
1476
|
-
timeKey: 'renaissanceTime',
|
|
1477
|
-
total: await this.indexdb.stake.count(),
|
|
1478
|
-
chunkSize,
|
|
1479
|
-
pageSize,
|
|
1480
|
-
}
|
|
1481
|
-
);
|
|
1482
|
-
}
|
|
1483
|
-
};
|
|
1484
|
-
|
|
1485
|
-
module.exports.formatData = formatData;
|
|
1486
|
-
module.exports.formatDelegationOps = formatDelegationOps;
|