@ocap/resolver 1.18.159 → 1.18.161
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/lib/index.js +3 -3
- package/lib/token-flow.js +138 -115
- package/package.json +12 -12
package/lib/index.js
CHANGED
|
@@ -756,9 +756,9 @@ module.exports = class OCAPResolver {
|
|
|
756
756
|
}
|
|
757
757
|
}
|
|
758
758
|
|
|
759
|
-
runAsLambda(fn) {
|
|
759
|
+
runAsLambda(fn, args) {
|
|
760
760
|
if (typeof this.statedb.runAsLambda === 'function') {
|
|
761
|
-
return this.statedb.runAsLambda((txn) => fn(txn));
|
|
761
|
+
return this.statedb.runAsLambda((txn) => fn(txn), args);
|
|
762
762
|
}
|
|
763
763
|
|
|
764
764
|
return fn();
|
|
@@ -1036,7 +1036,7 @@ module.exports = class OCAPResolver {
|
|
|
1036
1036
|
|
|
1037
1037
|
const totalPage = Math.floor(paging.total / pageSize);
|
|
1038
1038
|
const cursors = new Array(totalPage).fill(true).map((_, i) => (i + 1) * pageSize);
|
|
1039
|
-
const step = 3;
|
|
1039
|
+
const step = process.env.RESOLVER_BATCH_CONCURRENCY || 3;
|
|
1040
1040
|
let results = firstPage;
|
|
1041
1041
|
|
|
1042
1042
|
for (let i = 0; i < cursors.length; i += step) {
|
package/lib/token-flow.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable consistent-return */
|
|
1
2
|
/* eslint-disable no-await-in-loop */
|
|
2
3
|
|
|
3
4
|
const { BN, fromTokenToUnit, isSameDid } = require('@ocap/util');
|
|
@@ -131,6 +132,15 @@ const getInitialBalance = (address, config) => {
|
|
|
131
132
|
return account ? fromTokenToUnit(account.balance) : ZERO;
|
|
132
133
|
};
|
|
133
134
|
|
|
135
|
+
const getBalance = async (address, tokenAddress, { resolver, txn }) => {
|
|
136
|
+
const state = await resolver.statedb.account.get(address, { txn, traceMigration: false });
|
|
137
|
+
if (!state) {
|
|
138
|
+
throw new CustomError('INVALID_REQUEST', `Invalid address ${address}`);
|
|
139
|
+
}
|
|
140
|
+
const balance = state.tokens[tokenAddress] || 0;
|
|
141
|
+
return balance;
|
|
142
|
+
};
|
|
143
|
+
|
|
134
144
|
const fixMigrateReceipts = async (tx, resolver) => {
|
|
135
145
|
const migrationChain = await resolver.getMigrationChain();
|
|
136
146
|
const txTime = new Date(tx.time);
|
|
@@ -146,9 +156,9 @@ const verifyAccountRisk = async (
|
|
|
146
156
|
ctx = {}
|
|
147
157
|
) => {
|
|
148
158
|
// validate request params
|
|
149
|
-
const
|
|
150
|
-
if (error) {
|
|
151
|
-
throw new CustomError('INVALID_PARAMS', error.message);
|
|
159
|
+
const validation = paramsSchema.validate({ accountAddress, tokenAddress, resolver });
|
|
160
|
+
if (validation.error) {
|
|
161
|
+
throw new CustomError('INVALID_PARAMS', validation.error.message);
|
|
152
162
|
}
|
|
153
163
|
|
|
154
164
|
const checkedAccounts = new Map();
|
|
@@ -158,138 +168,152 @@ const verifyAccountRisk = async (
|
|
|
158
168
|
const toleranceUnit = fromTokenToUnit(tolerance, tokenState?.decimal);
|
|
159
169
|
const vaultAccounts = getVaultAccounts(resolver.config);
|
|
160
170
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
reason: 'MAX_ACCOUNT_SIZE_LIMIT',
|
|
167
|
-
data: {
|
|
168
|
-
accountCount: checkedAccounts.size,
|
|
169
|
-
txCount: checkedTx.size,
|
|
170
|
-
},
|
|
171
|
-
};
|
|
171
|
+
let executeCount = 0;
|
|
172
|
+
|
|
173
|
+
const execute = async (txn) => {
|
|
174
|
+
if (executeCount > 1) {
|
|
175
|
+
throw new CustomError('INVALID_REQUEST', 'verifyAccountRisk should not retry');
|
|
172
176
|
}
|
|
177
|
+
while (accountQueue.length) {
|
|
178
|
+
// limit
|
|
179
|
+
if (checkedAccounts.size >= accountLimit) {
|
|
180
|
+
return {
|
|
181
|
+
isRisky: false,
|
|
182
|
+
reason: 'MAX_ACCOUNT_SIZE_LIMIT',
|
|
183
|
+
data: {
|
|
184
|
+
accountCount: checkedAccounts.size,
|
|
185
|
+
txCount: checkedTx.size,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
173
189
|
|
|
174
|
-
|
|
175
|
-
// Avoid circular query
|
|
176
|
-
if (checkedAccounts.has(address)) continue;
|
|
190
|
+
const address = accountQueue.pop();
|
|
177
191
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (trustedConfig && !trustedConfig.tolerance) {
|
|
181
|
-
checkedAccounts.set(address, true);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
192
|
+
// Avoid circular query
|
|
193
|
+
if (checkedAccounts.has(address)) continue;
|
|
184
194
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
'transactions',
|
|
189
|
-
(paging) => resolver.listTransactions({ paging, accountFilter: { accounts: [address] } }, ctx),
|
|
190
|
-
txLimit
|
|
191
|
-
);
|
|
192
|
-
} catch (e) {
|
|
193
|
-
// skip if tx limit exceeded
|
|
194
|
-
if (e.code === 'EXCEED_LIMIT') {
|
|
195
|
+
const trustedConfig = await resolver.filter?.getTrustedAccountConfig(address);
|
|
196
|
+
// Skip trusted accounts that do not have tolerance configured
|
|
197
|
+
if (trustedConfig && !trustedConfig.tolerance) {
|
|
195
198
|
checkedAccounts.set(address, true);
|
|
196
199
|
continue;
|
|
197
200
|
}
|
|
198
|
-
throw e;
|
|
199
|
-
}
|
|
200
201
|
|
|
201
|
-
|
|
202
|
-
if (!accountState) {
|
|
203
|
-
throw new CustomError('INVALID_REQUEST', `Invalid address ${address}`);
|
|
204
|
-
}
|
|
205
|
-
const balance = accountState.tokens.find((item) => isSameDid(item.address, tokenAddress))?.value || 0;
|
|
206
|
-
|
|
207
|
-
let transferIn = getInitialBalance(address, resolver.config);
|
|
208
|
-
let transferOut = ZERO;
|
|
202
|
+
const balance = await getBalance(address, tokenAddress, { resolver, txn });
|
|
209
203
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
204
|
+
let transactions = [];
|
|
205
|
+
try {
|
|
206
|
+
transactions = await resolver._getAllResults(
|
|
207
|
+
'transactions',
|
|
208
|
+
(paging) => resolver.listTransactions({ paging, accountFilter: { accounts: [address] } }, ctx),
|
|
209
|
+
txLimit
|
|
210
|
+
);
|
|
211
|
+
} catch (e) {
|
|
212
|
+
// skip if tx limit exceeded
|
|
213
|
+
if (e.code === 'EXCEED_LIMIT') {
|
|
214
|
+
checkedAccounts.set(address, true);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
throw e;
|
|
216
218
|
}
|
|
217
|
-
const { transferInList, transferOutList } = checkedTx.get(tx.hash);
|
|
218
|
-
|
|
219
|
-
// Calculate the total amount of transfer for this address
|
|
220
|
-
transferIn = transferIn.add(
|
|
221
|
-
transferInList
|
|
222
|
-
.filter((item) => isSameDid(item.address, address))
|
|
223
|
-
.map((item) => item.value)
|
|
224
|
-
.reduce((prev, cur) => prev.add(cur), ZERO)
|
|
225
|
-
);
|
|
226
219
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
.filter((item) => isSameDid(item.address, address))
|
|
230
|
-
.map((item) => item.value)
|
|
231
|
-
.reduce((prev, cur) => prev.add(cur), ZERO)
|
|
232
|
-
);
|
|
220
|
+
let transferIn = getInitialBalance(address, resolver.config);
|
|
221
|
+
let transferOut = ZERO;
|
|
233
222
|
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
223
|
+
// Parse txs to get transfer amounts
|
|
224
|
+
for (const tx of transactions) {
|
|
225
|
+
// cache tx
|
|
226
|
+
if (!checkedTx.has(tx.hash)) {
|
|
227
|
+
await fixMigrateReceipts(tx, resolver);
|
|
228
|
+
checkedTx.set(tx.hash, getTransferList(tx, tokenAddress));
|
|
229
|
+
}
|
|
230
|
+
const { transferInList, transferOutList } = checkedTx.get(tx.hash);
|
|
231
|
+
|
|
232
|
+
// Calculate the total amount of transfer for this address
|
|
233
|
+
transferIn = transferIn.add(
|
|
234
|
+
transferInList
|
|
235
|
+
.filter((item) => isSameDid(item.address, address))
|
|
236
|
+
.map((item) => item.value)
|
|
237
|
+
.reduce((prev, cur) => prev.add(cur), ZERO)
|
|
238
|
+
);
|
|
245
239
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
240
|
+
transferOut = transferOut.add(
|
|
241
|
+
transferOutList
|
|
242
|
+
.filter((item) => isSameDid(item.address, address))
|
|
243
|
+
.map((item) => item.value)
|
|
244
|
+
.reduce((prev, cur) => prev.add(cur), ZERO)
|
|
245
|
+
);
|
|
249
246
|
|
|
250
|
-
|
|
247
|
+
// push transferIn accounts to queue for next time check
|
|
248
|
+
if (transferInList.some((item) => isSameDid(item.address, address))) {
|
|
249
|
+
const accountsToQueue = transferOutList
|
|
250
|
+
.filter((item) => {
|
|
251
|
+
if (accountQueue.includes(item.address)) return false;
|
|
252
|
+
// Skip vault accounts
|
|
253
|
+
if (vaultAccounts.includes(item.address)) return false;
|
|
254
|
+
// skip gas、fee
|
|
255
|
+
if (['gas', 'fee'].includes(item.action)) return false;
|
|
256
|
+
// Skip not token holders
|
|
257
|
+
if (schemas.tokenHolder.validate(item.address).error) return false;
|
|
258
|
+
|
|
259
|
+
return true;
|
|
260
|
+
})
|
|
261
|
+
.map((item) => item.address);
|
|
262
|
+
|
|
263
|
+
accountQueue.push(...uniq(accountsToQueue));
|
|
264
|
+
}
|
|
251
265
|
}
|
|
252
|
-
}
|
|
253
266
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
address,
|
|
266
|
-
transferIn: transferIn.toString(),
|
|
267
|
-
transferOut: transferOut.toString(),
|
|
268
|
-
balance,
|
|
269
|
-
sourceAccount: accountAddress,
|
|
270
|
-
});
|
|
271
|
-
return {
|
|
272
|
-
isRisky: true,
|
|
273
|
-
reason: 'INVALID_BALANCE',
|
|
274
|
-
data: {
|
|
267
|
+
checkedAccounts.set(address, true);
|
|
268
|
+
|
|
269
|
+
// Check if the balance not matches the transfer records
|
|
270
|
+
if (
|
|
271
|
+
transferIn
|
|
272
|
+
.sub(transferOut.add(new BN(balance)))
|
|
273
|
+
.add(fromTokenToUnit(trustedConfig?.tolerance || 0))
|
|
274
|
+
.abs()
|
|
275
|
+
.gt(toleranceUnit)
|
|
276
|
+
) {
|
|
277
|
+
debug('Account balance does not match transfer records', {
|
|
275
278
|
address,
|
|
276
|
-
balance,
|
|
277
279
|
transferIn: transferIn.toString(),
|
|
278
280
|
transferOut: transferOut.toString(),
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
281
|
+
balance,
|
|
282
|
+
sourceAccount: accountAddress,
|
|
283
|
+
});
|
|
284
|
+
return {
|
|
285
|
+
isRisky: true,
|
|
286
|
+
reason: 'INVALID_BALANCE',
|
|
287
|
+
data: {
|
|
288
|
+
address,
|
|
289
|
+
balance,
|
|
290
|
+
transferIn: transferIn.toString(),
|
|
291
|
+
transferOut: transferOut.toString(),
|
|
292
|
+
accountCount: checkedAccounts.size,
|
|
293
|
+
txCount: checkedTx.size,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
283
297
|
}
|
|
284
|
-
}
|
|
298
|
+
};
|
|
285
299
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
txCount: checkedTx.size,
|
|
300
|
+
const result = await resolver.runAsLambda(
|
|
301
|
+
(txn) => {
|
|
302
|
+
executeCount++;
|
|
303
|
+
return execute(txn);
|
|
291
304
|
},
|
|
292
|
-
|
|
305
|
+
{ retryLimit: 0 }
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
result || {
|
|
310
|
+
isRisky: false,
|
|
311
|
+
data: {
|
|
312
|
+
accountCount: checkedAccounts.size,
|
|
313
|
+
txCount: checkedTx.size,
|
|
314
|
+
},
|
|
315
|
+
}
|
|
316
|
+
);
|
|
293
317
|
};
|
|
294
318
|
|
|
295
319
|
const listTokenFlows = async (
|
|
@@ -337,10 +361,9 @@ const listTokenFlows = async (
|
|
|
337
361
|
let accountsToQueue = [];
|
|
338
362
|
|
|
339
363
|
for (const tx of transactions) {
|
|
340
|
-
// fix migrate receipts
|
|
341
|
-
await fixMigrateReceipts(tx, resolver, ctx);
|
|
342
364
|
// cache tx
|
|
343
365
|
if (!checkedTx.has(tx.hash)) {
|
|
366
|
+
await fixMigrateReceipts(tx, resolver);
|
|
344
367
|
checkedTx.set(tx.hash, await getTransferFlow(tx, tokenAddress));
|
|
345
368
|
}
|
|
346
369
|
const txTransfers = checkedTx.get(tx.hash).filter((item) => {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "1.18.
|
|
6
|
+
"version": "1.18.161",
|
|
7
7
|
"description": "GraphQL resolver built upon ocap statedb and GQL layer",
|
|
8
8
|
"main": "lib/index.js",
|
|
9
9
|
"files": [
|
|
@@ -22,18 +22,18 @@
|
|
|
22
22
|
"jest": "^29.7.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@arcblock/did": "1.18.
|
|
26
|
-
"@arcblock/did-util": "1.18.
|
|
27
|
-
"@arcblock/validator": "1.18.
|
|
28
|
-
"@ocap/config": "1.18.
|
|
29
|
-
"@ocap/indexdb": "1.18.
|
|
30
|
-
"@ocap/mcrypto": "1.18.
|
|
31
|
-
"@ocap/message": "1.18.
|
|
32
|
-
"@ocap/state": "1.18.
|
|
33
|
-
"@ocap/tx-protocols": "1.18.
|
|
34
|
-
"@ocap/util": "1.18.
|
|
25
|
+
"@arcblock/did": "1.18.161",
|
|
26
|
+
"@arcblock/did-util": "1.18.161",
|
|
27
|
+
"@arcblock/validator": "1.18.161",
|
|
28
|
+
"@ocap/config": "1.18.161",
|
|
29
|
+
"@ocap/indexdb": "1.18.161",
|
|
30
|
+
"@ocap/mcrypto": "1.18.161",
|
|
31
|
+
"@ocap/message": "1.18.161",
|
|
32
|
+
"@ocap/state": "1.18.161",
|
|
33
|
+
"@ocap/tx-protocols": "1.18.161",
|
|
34
|
+
"@ocap/util": "1.18.161",
|
|
35
35
|
"debug": "^4.3.6",
|
|
36
36
|
"lodash": "^4.17.21"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "5d8436fdf5218fcbf2218b77ae370d50617ba3d0"
|
|
39
39
|
}
|