@ocap/resolver 1.18.161 → 1.18.163
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 +4 -6
- package/lib/token-flow.js +103 -69
- package/package.json +12 -12
package/lib/index.js
CHANGED
|
@@ -688,6 +688,8 @@ module.exports = class OCAPResolver {
|
|
|
688
688
|
return tokenFlow.verifyAccountRisk(
|
|
689
689
|
{
|
|
690
690
|
...args,
|
|
691
|
+
tokenAddress: toAddress(args.tokenAddress || this.config.token.address),
|
|
692
|
+
accountAddress: toAddress(args.accountAddress || ''),
|
|
691
693
|
accountLimit: process.env.VERIFY_RISK_ACCOUNT_LIMIT,
|
|
692
694
|
txLimit: process.env.VERIFY_RISK_TX_LIMIT,
|
|
693
695
|
tolerance: process.env.VERIFY_RISK_TOLERANCE,
|
|
@@ -999,11 +1001,7 @@ module.exports = class OCAPResolver {
|
|
|
999
1001
|
}
|
|
1000
1002
|
|
|
1001
1003
|
if (typeUrl === 'fg:t:account_migrate') {
|
|
1002
|
-
const fromState = await this.
|
|
1003
|
-
address: tx.tx.from,
|
|
1004
|
-
traceMigration: false,
|
|
1005
|
-
expandContext: false,
|
|
1006
|
-
});
|
|
1004
|
+
const fromState = await this.statedb.account.get(tx.tx.from, { traceMigration: false });
|
|
1007
1005
|
if (fromState) {
|
|
1008
1006
|
tx.receipts = getTxReceipts(tx, { config: this.config, fromState });
|
|
1009
1007
|
}
|
|
@@ -1189,7 +1187,7 @@ module.exports = class OCAPResolver {
|
|
|
1189
1187
|
this.listTransactions({
|
|
1190
1188
|
paging,
|
|
1191
1189
|
typeFilter: { types: ['account_migrate'] },
|
|
1192
|
-
validityFilter: { validity:
|
|
1190
|
+
validityFilter: { validity: 'VALID' },
|
|
1193
1191
|
})
|
|
1194
1192
|
);
|
|
1195
1193
|
const migrationChain = new MigrationChainManager();
|
package/lib/token-flow.js
CHANGED
|
@@ -6,7 +6,6 @@ const { schemas, Joi } = require('@arcblock/validator');
|
|
|
6
6
|
const { CustomError } = require('@ocap/util/lib/error');
|
|
7
7
|
const uniq = require('lodash/uniq');
|
|
8
8
|
const { FORGE_TOKEN_HOLDER } = require('@ocap/state/lib/states/tx');
|
|
9
|
-
const debug = require('debug')(require('../package.json').name);
|
|
10
9
|
|
|
11
10
|
const ZERO = new BN(0);
|
|
12
11
|
const paramsSchema = Joi.object({
|
|
@@ -161,22 +160,31 @@ const verifyAccountRisk = async (
|
|
|
161
160
|
throw new CustomError('INVALID_PARAMS', validation.error.message);
|
|
162
161
|
}
|
|
163
162
|
|
|
163
|
+
const { logger } = resolver;
|
|
164
164
|
const checkedAccounts = new Map();
|
|
165
165
|
const checkedTx = new Map();
|
|
166
|
-
const accountQueue = [accountAddress];
|
|
167
166
|
const tokenState = await resolver.tokenCache.get(tokenAddress);
|
|
168
167
|
const toleranceUnit = fromTokenToUnit(tolerance, tokenState?.decimal);
|
|
169
168
|
const vaultAccounts = getVaultAccounts(resolver.config);
|
|
170
169
|
|
|
171
|
-
|
|
170
|
+
const accountQueue = [[{ address: accountAddress, chain: [] }]];
|
|
172
171
|
|
|
173
|
-
const execute = async (txn) => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
while (accountQueue.length) {
|
|
172
|
+
const execute = async (depth, txn) => {
|
|
173
|
+
const queue = accountQueue[depth];
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < queue.length; i++) {
|
|
178
176
|
// limit
|
|
179
177
|
if (checkedAccounts.size >= accountLimit) {
|
|
178
|
+
logger.warn('Account risk check reached max account size limit', {
|
|
179
|
+
address: accountAddress,
|
|
180
|
+
tokenAddress,
|
|
181
|
+
accountCount: checkedAccounts.size,
|
|
182
|
+
txCount: checkedTx.size,
|
|
183
|
+
depth,
|
|
184
|
+
accountLimit,
|
|
185
|
+
txLimit,
|
|
186
|
+
tolerance,
|
|
187
|
+
});
|
|
180
188
|
return {
|
|
181
189
|
isRisky: false,
|
|
182
190
|
reason: 'MAX_ACCOUNT_SIZE_LIMIT',
|
|
@@ -187,7 +195,8 @@ const verifyAccountRisk = async (
|
|
|
187
195
|
};
|
|
188
196
|
}
|
|
189
197
|
|
|
190
|
-
const address =
|
|
198
|
+
const { address, chain } = queue[i];
|
|
199
|
+
chain.push(address);
|
|
191
200
|
|
|
192
201
|
// Avoid circular query
|
|
193
202
|
if (checkedAccounts.has(address)) continue;
|
|
@@ -199,18 +208,22 @@ const verifyAccountRisk = async (
|
|
|
199
208
|
continue;
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
|
|
203
|
-
|
|
211
|
+
let balance = 0;
|
|
204
212
|
let transactions = [];
|
|
213
|
+
|
|
205
214
|
try {
|
|
206
|
-
transactions = await
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
215
|
+
[balance, transactions] = await Promise.all([
|
|
216
|
+
getBalance(address, tokenAddress, { resolver, txn }),
|
|
217
|
+
resolver._getAllResults(
|
|
218
|
+
'transactions',
|
|
219
|
+
(paging) => resolver.listTransactions({ paging, accountFilter: { accounts: [address] } }, ctx),
|
|
220
|
+
txLimit
|
|
221
|
+
),
|
|
222
|
+
]);
|
|
211
223
|
} catch (e) {
|
|
212
224
|
// skip if tx limit exceeded
|
|
213
|
-
if (e
|
|
225
|
+
if (e?.code === 'EXCEED_LIMIT') {
|
|
226
|
+
logger.warn('Skip checking account cause tx count exceeding limit', { address, txLimit });
|
|
214
227
|
checkedAccounts.set(address, true);
|
|
215
228
|
continue;
|
|
216
229
|
}
|
|
@@ -230,90 +243,113 @@ const verifyAccountRisk = async (
|
|
|
230
243
|
const { transferInList, transferOutList } = checkedTx.get(tx.hash);
|
|
231
244
|
|
|
232
245
|
// Calculate the total amount of transfer for this address
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.reduce((prev, cur) => prev.add(cur), ZERO)
|
|
238
|
-
);
|
|
246
|
+
const transferInAmount = transferInList
|
|
247
|
+
.filter((item) => isSameDid(item.address, address))
|
|
248
|
+
.map((item) => item.value)
|
|
249
|
+
.reduce((prev, cur) => prev.add(cur), ZERO);
|
|
239
250
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
);
|
|
251
|
+
const transferOutAmount = transferOutList
|
|
252
|
+
.filter((item) => isSameDid(item.address, address))
|
|
253
|
+
.map((item) => item.value)
|
|
254
|
+
.reduce((prev, cur) => prev.add(cur), ZERO);
|
|
255
|
+
|
|
256
|
+
transferIn = transferIn.add(transferInAmount);
|
|
257
|
+
transferOut = transferOut.add(transferOutAmount);
|
|
246
258
|
|
|
247
259
|
// push transferIn accounts to queue for next time check
|
|
248
|
-
if (
|
|
260
|
+
if (transferInAmount.gt(ZERO)) {
|
|
261
|
+
if (!accountQueue[depth + 1]) {
|
|
262
|
+
accountQueue[depth + 1] = [];
|
|
263
|
+
}
|
|
249
264
|
const accountsToQueue = transferOutList
|
|
250
265
|
.filter((item) => {
|
|
251
|
-
if (
|
|
266
|
+
if (checkedAccounts.has(item.address)) return false;
|
|
267
|
+
// Skip not token holders
|
|
268
|
+
if (schemas.tokenHolder.validate(item.address).error) return false;
|
|
252
269
|
// Skip vault accounts
|
|
253
270
|
if (vaultAccounts.includes(item.address)) return false;
|
|
254
271
|
// skip gas、fee
|
|
255
272
|
if (['gas', 'fee'].includes(item.action)) return false;
|
|
256
|
-
// Skip not token holders
|
|
257
|
-
if (schemas.tokenHolder.validate(item.address).error) return false;
|
|
258
273
|
|
|
259
274
|
return true;
|
|
260
275
|
})
|
|
261
276
|
.map((item) => item.address);
|
|
262
277
|
|
|
263
|
-
accountQueue.push(...uniq(accountsToQueue));
|
|
278
|
+
accountQueue[depth + 1].push(...uniq(accountsToQueue).map((x) => ({ address: x, chain: chain.concat() })));
|
|
264
279
|
}
|
|
265
280
|
}
|
|
266
281
|
|
|
267
282
|
checkedAccounts.set(address, true);
|
|
268
283
|
|
|
269
284
|
// Check if the balance not matches the transfer records
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
285
|
+
const diff = transferIn
|
|
286
|
+
.sub(transferOut)
|
|
287
|
+
.sub(new BN(balance))
|
|
288
|
+
.add(fromTokenToUnit(trustedConfig?.tolerance || 0));
|
|
289
|
+
|
|
290
|
+
if (diff.abs().gt(toleranceUnit)) {
|
|
291
|
+
const data = {
|
|
292
|
+
address: chain.join('->'),
|
|
293
|
+
balance,
|
|
279
294
|
transferIn: transferIn.toString(),
|
|
280
295
|
transferOut: transferOut.toString(),
|
|
281
|
-
|
|
296
|
+
accountCount: checkedAccounts.size,
|
|
297
|
+
txCount: checkedTx.size,
|
|
298
|
+
};
|
|
299
|
+
logger.warn('Account balance does not match transfer records', {
|
|
300
|
+
...data,
|
|
282
301
|
sourceAccount: accountAddress,
|
|
302
|
+
tokenAddress,
|
|
303
|
+
diff: diff.toString(),
|
|
304
|
+
depth,
|
|
305
|
+
accountLimit,
|
|
306
|
+
txLimit,
|
|
307
|
+
tolerance,
|
|
283
308
|
});
|
|
284
309
|
return {
|
|
285
310
|
isRisky: true,
|
|
286
311
|
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
|
-
},
|
|
312
|
+
data,
|
|
295
313
|
};
|
|
296
314
|
}
|
|
297
315
|
}
|
|
298
316
|
};
|
|
299
317
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
result || {
|
|
310
|
-
isRisky: false,
|
|
311
|
-
data: {
|
|
312
|
-
accountCount: checkedAccounts.size,
|
|
313
|
-
txCount: checkedTx.size,
|
|
318
|
+
for (let depth = 0; depth < accountQueue.length; depth++) {
|
|
319
|
+
let isExecuted = false;
|
|
320
|
+
const result = await resolver.runAsLambda(
|
|
321
|
+
(txn) => {
|
|
322
|
+
if (isExecuted) {
|
|
323
|
+
throw new CustomError('INVALID_REQUEST', 'verifyAccountRisk should not retry');
|
|
324
|
+
}
|
|
325
|
+
isExecuted = true;
|
|
326
|
+
return execute(depth, txn);
|
|
314
327
|
},
|
|
328
|
+
{ retryLimit: 0 }
|
|
329
|
+
);
|
|
330
|
+
if (result) {
|
|
331
|
+
return result;
|
|
315
332
|
}
|
|
316
|
-
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
logger.info('Account risk check completed', {
|
|
336
|
+
address: accountAddress,
|
|
337
|
+
tokenAddress,
|
|
338
|
+
accountCount: checkedAccounts.size,
|
|
339
|
+
txCount: checkedTx.size,
|
|
340
|
+
depth: accountQueue.length,
|
|
341
|
+
accountLimit,
|
|
342
|
+
txLimit,
|
|
343
|
+
tolerance,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
isRisky: false,
|
|
348
|
+
data: {
|
|
349
|
+
accountCount: checkedAccounts.size,
|
|
350
|
+
txCount: checkedTx.size,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
317
353
|
};
|
|
318
354
|
|
|
319
355
|
const listTokenFlows = async (
|
|
@@ -349,8 +385,6 @@ const listTokenFlows = async (
|
|
|
349
385
|
if (checkedAccounts.has(address)) continue;
|
|
350
386
|
// Skip not token holders
|
|
351
387
|
if (schemas.tokenHolder.validate(address).error) continue;
|
|
352
|
-
// Skip vault accounts
|
|
353
|
-
if (vaultAccounts.includes(address)) continue;
|
|
354
388
|
|
|
355
389
|
const transactions = await resolver._getAllResults('transactions', (page) =>
|
|
356
390
|
resolver.listTransactions(
|
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.163",
|
|
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.163",
|
|
26
|
+
"@arcblock/did-util": "1.18.163",
|
|
27
|
+
"@arcblock/validator": "1.18.163",
|
|
28
|
+
"@ocap/config": "1.18.163",
|
|
29
|
+
"@ocap/indexdb": "1.18.163",
|
|
30
|
+
"@ocap/mcrypto": "1.18.163",
|
|
31
|
+
"@ocap/message": "1.18.163",
|
|
32
|
+
"@ocap/state": "1.18.163",
|
|
33
|
+
"@ocap/tx-protocols": "1.18.163",
|
|
34
|
+
"@ocap/util": "1.18.163",
|
|
35
35
|
"debug": "^4.3.6",
|
|
36
36
|
"lodash": "^4.17.21"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "c4bb7fae24dbac7a56ffbb84d19f64b90ea770b7"
|
|
39
39
|
}
|