@ocap/resolver 1.18.152 → 1.18.154
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 +24 -4
- package/lib/migration-chain.js +21 -13
- package/lib/token-flow.js +43 -16
- package/package.json +12 -12
package/lib/index.js
CHANGED
|
@@ -162,7 +162,7 @@ module.exports = class OCAPResolver {
|
|
|
162
162
|
* @param {object} params.filter bloom filter to do anti-replay check
|
|
163
163
|
* @param {bool} params.validateTokenConfig should we validate token supply and token-holder balance, should be disabled when starting an existing chain
|
|
164
164
|
*/
|
|
165
|
-
constructor({ statedb, indexdb, config, filter, validateTokenConfig = true }) {
|
|
165
|
+
constructor({ statedb, indexdb, config, filter, validateTokenConfig = true, logger }) {
|
|
166
166
|
if (!statedb) {
|
|
167
167
|
throw new Error('OCAP Resolver requires a valid statedb implementation to work');
|
|
168
168
|
}
|
|
@@ -170,6 +170,8 @@ module.exports = class OCAPResolver {
|
|
|
170
170
|
throw new Error('OCAP Resolver requires a valid indexdb implementation to work');
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
this.logger = logger || { info: debug, warn: debug, error: debug, debug };
|
|
174
|
+
|
|
173
175
|
this.statedb = statedb;
|
|
174
176
|
this.indexdb = indexdb;
|
|
175
177
|
this.filter = filter;
|
|
@@ -208,7 +210,10 @@ module.exports = class OCAPResolver {
|
|
|
208
210
|
}
|
|
209
211
|
|
|
210
212
|
async sendTx({ tx: txBase64 }, ctx = {}) {
|
|
211
|
-
|
|
213
|
+
this.logger.info('sendTx', {
|
|
214
|
+
txBase64,
|
|
215
|
+
request: ctx.request,
|
|
216
|
+
});
|
|
212
217
|
|
|
213
218
|
if (process.env.CHAIN_MODE === 'readonly') {
|
|
214
219
|
throw new CustomError('FORBIDDEN', 'This chain node is running in readonly mode');
|
|
@@ -682,7 +687,16 @@ module.exports = class OCAPResolver {
|
|
|
682
687
|
}
|
|
683
688
|
|
|
684
689
|
async verifyAccountRisk(args, ctx) {
|
|
685
|
-
return tokenFlow.verifyAccountRisk(
|
|
690
|
+
return tokenFlow.verifyAccountRisk(
|
|
691
|
+
{
|
|
692
|
+
...args,
|
|
693
|
+
accountLimit: process.env.VERIFY_RISK_ACCOUNT_LIMIT,
|
|
694
|
+
txLimit: process.env.VERIFY_RISK_TX_LIMIT,
|
|
695
|
+
tolerance: process.env.VERIFY_RISK_TOLERANCE,
|
|
696
|
+
},
|
|
697
|
+
this,
|
|
698
|
+
ctx
|
|
699
|
+
);
|
|
686
700
|
}
|
|
687
701
|
|
|
688
702
|
async listTokenFlows(args, ctx) {
|
|
@@ -1004,11 +1018,17 @@ module.exports = class OCAPResolver {
|
|
|
1004
1018
|
return attachPaidTxGas(tx);
|
|
1005
1019
|
}
|
|
1006
1020
|
|
|
1007
|
-
async _getAllResults(dataKey, fn) {
|
|
1021
|
+
async _getAllResults(dataKey, fn, limit) {
|
|
1008
1022
|
const results = [];
|
|
1009
1023
|
const pageSize = 100;
|
|
1010
1024
|
|
|
1011
1025
|
const { paging, [dataKey]: firstPage } = await fn({ size: pageSize });
|
|
1026
|
+
|
|
1027
|
+
// Skip if total exceeds limit cause we cannot query full data
|
|
1028
|
+
if (limit && paging.total > limit) {
|
|
1029
|
+
throw new CustomError('EXCEED_LIMIT', `Total exceeds limit ${limit}`);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1012
1032
|
if (paging.total < pageSize) {
|
|
1013
1033
|
return firstPage;
|
|
1014
1034
|
}
|
package/lib/migration-chain.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable max-classes-per-file */
|
|
2
|
+
const { isEthereumDid } = require('@arcblock/did');
|
|
2
3
|
|
|
3
4
|
class MigrationNode {
|
|
4
5
|
constructor(address, validFrom, validUntil = null, nextAddress = null) {
|
|
@@ -59,7 +60,7 @@ class MigrationChainManager {
|
|
|
59
60
|
*/
|
|
60
61
|
_processMigration(fromAddr, toAddr, timestamp) {
|
|
61
62
|
// Find or create the root address for this chain
|
|
62
|
-
const rootAddr = this.
|
|
63
|
+
const rootAddr = this.getRootAddress(fromAddr);
|
|
63
64
|
|
|
64
65
|
// Get or create the chain
|
|
65
66
|
if (!this.chains.has(rootAddr)) {
|
|
@@ -81,31 +82,29 @@ class MigrationChainManager {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
// Update root address mapping for the new address
|
|
84
|
-
this.rootAddresses.set(toAddr, rootAddr);
|
|
85
|
+
this.rootAddresses.set(this.formatAddress(toAddr), rootAddr);
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
/**
|
|
88
89
|
* Find the valid address at a specific timestamp
|
|
89
|
-
* @param {string}
|
|
90
|
+
* @param {string} address - Address to look up
|
|
90
91
|
* @param {Date} timestamp - Timestamp to check
|
|
91
92
|
* @returns {string} Valid address at the timestamp
|
|
92
93
|
*/
|
|
93
|
-
findAddressAtTime(
|
|
94
|
-
|
|
95
|
-
const rootAddr = this.rootAddresses.get(initialAddress) || initialAddress;
|
|
96
|
-
const chain = this.chains.get(rootAddr);
|
|
94
|
+
findAddressAtTime(address, timestamp) {
|
|
95
|
+
const chains = this.getMigrationHistory(address);
|
|
97
96
|
|
|
98
|
-
if (!
|
|
99
|
-
return
|
|
97
|
+
if (!chains?.length) {
|
|
98
|
+
return address;
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
// Binary search for the correct address
|
|
103
102
|
let left = 0;
|
|
104
|
-
let right =
|
|
103
|
+
let right = chains.length - 1;
|
|
105
104
|
|
|
106
105
|
while (left <= right) {
|
|
107
106
|
const mid = Math.floor((left + right) / 2);
|
|
108
|
-
const node =
|
|
107
|
+
const node = chains[mid];
|
|
109
108
|
|
|
110
109
|
if (node.validFrom <= timestamp && (!node.validUntil || timestamp < node.validUntil)) {
|
|
111
110
|
return node.address;
|
|
@@ -117,7 +116,16 @@ class MigrationChainManager {
|
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
return
|
|
119
|
+
return chains[chains.length - 1].address;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
formatAddress(address) {
|
|
123
|
+
return isEthereumDid(address) ? address.toLowerCase() : address;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getRootAddress(address) {
|
|
127
|
+
const formattedAddress = this.formatAddress(address);
|
|
128
|
+
return this.rootAddresses.get(formattedAddress) || formattedAddress;
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
/**
|
|
@@ -126,7 +134,7 @@ class MigrationChainManager {
|
|
|
126
134
|
* @returns {Array} List of migration nodes
|
|
127
135
|
*/
|
|
128
136
|
getMigrationHistory(address) {
|
|
129
|
-
const rootAddr = this.
|
|
137
|
+
const rootAddr = this.getRootAddress(address);
|
|
130
138
|
return this.chains.get(rootAddr) || [];
|
|
131
139
|
}
|
|
132
140
|
}
|
package/lib/token-flow.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable no-await-in-loop */
|
|
2
2
|
|
|
3
|
-
const { BN, fromTokenToUnit } = require('@ocap/util');
|
|
3
|
+
const { BN, fromTokenToUnit, isSameDid } = require('@ocap/util');
|
|
4
4
|
const { schemas, Joi } = require('@arcblock/validator');
|
|
5
5
|
const { CustomError } = require('@ocap/util/lib/error');
|
|
6
6
|
const uniq = require('lodash/uniq');
|
|
@@ -126,6 +126,11 @@ const getVaultAccounts = (config) => {
|
|
|
126
126
|
return Object.values(config.vaults).flat().concat(FORGE_TOKEN_HOLDER);
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
+
const getInitialBalance = (address, config) => {
|
|
130
|
+
const account = config?.accounts?.find((x) => isSameDid(x.address, address));
|
|
131
|
+
return account ? fromTokenToUnit(account.balance) : ZERO;
|
|
132
|
+
};
|
|
133
|
+
|
|
129
134
|
const fixMigrateReceipts = async ({ accountAddress, tx }, resolver) => {
|
|
130
135
|
const migrationChain = await resolver.getMigrationChain();
|
|
131
136
|
const address = migrationChain.findAddressAtTime(accountAddress, new Date(tx.time));
|
|
@@ -134,14 +139,18 @@ const fixMigrateReceipts = async ({ accountAddress, tx }, resolver) => {
|
|
|
134
139
|
// fix receipts address
|
|
135
140
|
if (address && migrations.length) {
|
|
136
141
|
tx.receipts.forEach((receipt) => {
|
|
137
|
-
if (migrations.some((x) => x.address
|
|
142
|
+
if (migrations.some((x) => isSameDid(x.address, receipt.address))) {
|
|
138
143
|
receipt.address = address;
|
|
139
144
|
}
|
|
140
145
|
});
|
|
141
146
|
}
|
|
142
147
|
};
|
|
143
148
|
|
|
144
|
-
const verifyAccountRisk = async (
|
|
149
|
+
const verifyAccountRisk = async (
|
|
150
|
+
{ accountAddress, tokenAddress, accountLimit = 400, txLimit = 10000, tolerance = '0.0000000001' },
|
|
151
|
+
resolver,
|
|
152
|
+
ctx = {}
|
|
153
|
+
) => {
|
|
145
154
|
// validate request params
|
|
146
155
|
const { error } = paramsSchema.validate({ accountAddress, tokenAddress, resolver });
|
|
147
156
|
if (error) {
|
|
@@ -151,12 +160,13 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
|
|
|
151
160
|
const checkedAccounts = new Map();
|
|
152
161
|
const checkedTx = new Map();
|
|
153
162
|
const accountQueue = [accountAddress];
|
|
154
|
-
const
|
|
163
|
+
const tokenState = await resolver.tokenCache.get(tokenAddress);
|
|
164
|
+
const toleranceUnit = fromTokenToUnit(tolerance, tokenState?.decimal);
|
|
155
165
|
const vaultAccounts = getVaultAccounts(resolver.config);
|
|
156
166
|
|
|
157
167
|
while (accountQueue.length) {
|
|
158
168
|
// limit
|
|
159
|
-
if (checkedAccounts.size >=
|
|
169
|
+
if (checkedAccounts.size >= accountLimit) {
|
|
160
170
|
return {
|
|
161
171
|
isRisky: false,
|
|
162
172
|
reason: 'MAX_ACCOUNT_SIZE_LIMIT',
|
|
@@ -172,21 +182,33 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
|
|
|
172
182
|
if (checkedAccounts.has(address)) continue;
|
|
173
183
|
// skip trusted accounts
|
|
174
184
|
if (await resolver.filter.isTrusted(address)) {
|
|
175
|
-
checkedAccounts.set(address,
|
|
185
|
+
checkedAccounts.set(address, true);
|
|
176
186
|
continue;
|
|
177
187
|
}
|
|
178
188
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
let transactions = [];
|
|
190
|
+
try {
|
|
191
|
+
transactions = await resolver._getAllResults(
|
|
192
|
+
'transactions',
|
|
193
|
+
(paging) => resolver.listTransactions({ paging, accountFilter: { accounts: [address] } }, ctx),
|
|
194
|
+
txLimit
|
|
195
|
+
);
|
|
196
|
+
} catch (e) {
|
|
197
|
+
// skip if tx limit exceeded
|
|
198
|
+
if (e.code === 'EXCEED_LIMIT') {
|
|
199
|
+
checkedAccounts.set(address, true);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
throw e;
|
|
203
|
+
}
|
|
183
204
|
|
|
205
|
+
const accountState = await resolver.getAccountState({ address, traceMigration: false }, ctx);
|
|
184
206
|
if (!accountState) {
|
|
185
207
|
throw new CustomError('INVALID_REQUEST', `Invalid address ${address}`);
|
|
186
208
|
}
|
|
187
|
-
const balance = accountState.tokens.find((item) => item.address
|
|
209
|
+
const balance = accountState.tokens.find((item) => isSameDid(item.address, tokenAddress))?.value || 0;
|
|
188
210
|
|
|
189
|
-
let transferIn =
|
|
211
|
+
let transferIn = getInitialBalance(address, resolver.config);
|
|
190
212
|
let transferOut = ZERO;
|
|
191
213
|
|
|
192
214
|
// Parse txs to get transfer amounts
|
|
@@ -205,20 +227,20 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
|
|
|
205
227
|
// Calculate the total amount of transfer for this address
|
|
206
228
|
transferIn = transferIn.add(
|
|
207
229
|
transferInList
|
|
208
|
-
.filter((item) => item.address
|
|
230
|
+
.filter((item) => isSameDid(item.address, address))
|
|
209
231
|
.map((item) => item.value)
|
|
210
232
|
.reduce((prev, cur) => prev.add(cur), ZERO)
|
|
211
233
|
);
|
|
212
234
|
|
|
213
235
|
transferOut = transferOut.add(
|
|
214
236
|
transferOutList
|
|
215
|
-
.filter((item) => item.address
|
|
237
|
+
.filter((item) => isSameDid(item.address, address))
|
|
216
238
|
.map((item) => item.value)
|
|
217
239
|
.reduce((prev, cur) => prev.add(cur), ZERO)
|
|
218
240
|
);
|
|
219
241
|
|
|
220
242
|
// push transferIn accounts to queue for next time check
|
|
221
|
-
if (transferInList.some((item) => item.address
|
|
243
|
+
if (transferInList.some((item) => isSameDid(item.address, address))) {
|
|
222
244
|
const accountsToQueue = transferOutList
|
|
223
245
|
.filter((item) => {
|
|
224
246
|
if (accountQueue.includes(item.address)) return false;
|
|
@@ -240,7 +262,12 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
|
|
|
240
262
|
checkedAccounts.set(address, true);
|
|
241
263
|
|
|
242
264
|
// Check if the balance not matches the transfer records
|
|
243
|
-
if (
|
|
265
|
+
if (
|
|
266
|
+
transferIn
|
|
267
|
+
.sub(transferOut.add(new BN(balance)))
|
|
268
|
+
.abs()
|
|
269
|
+
.gt(toleranceUnit)
|
|
270
|
+
) {
|
|
244
271
|
debug('Account balance does not match transfer records', {
|
|
245
272
|
address,
|
|
246
273
|
transferIn: transferIn.toString(),
|
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.154",
|
|
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.154",
|
|
26
|
+
"@arcblock/did-util": "1.18.154",
|
|
27
|
+
"@arcblock/validator": "1.18.154",
|
|
28
|
+
"@ocap/config": "1.18.154",
|
|
29
|
+
"@ocap/indexdb": "1.18.154",
|
|
30
|
+
"@ocap/mcrypto": "1.18.154",
|
|
31
|
+
"@ocap/message": "1.18.154",
|
|
32
|
+
"@ocap/state": "1.18.154",
|
|
33
|
+
"@ocap/tx-protocols": "1.18.154",
|
|
34
|
+
"@ocap/util": "1.18.154",
|
|
35
35
|
"debug": "^4.3.6",
|
|
36
36
|
"lodash": "^4.17.21"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "414d4207886905fca9529912a12a3b6b8c811886"
|
|
39
39
|
}
|