@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 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
- debug('sendTx', txBase64);
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(args, this, ctx);
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
  }
@@ -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.rootAddresses.get(fromAddr) || fromAddr;
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} initialAddress - Address to look up
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(initialAddress, timestamp) {
94
- // Get the root address
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 (!chain) {
99
- return initialAddress;
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 = chain.length - 1;
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 = chain[mid];
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 chain[chain.length - 1].address;
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.rootAddresses.get(address) || address;
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 === receipt.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 ({ accountAddress, tokenAddress }, resolver, ctx = {}) => {
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 maxAccountSize = 400;
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 >= maxAccountSize) {
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
- const transactions = await resolver._getAllResults('transactions', (paging) =>
180
- resolver.listTransactions({ paging, accountFilter: { accounts: [address] } }, ctx)
181
- );
182
- const accountState = await resolver.getAccountState({ address, traceMigration: false }, ctx);
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 === tokenAddress)?.value || 0;
209
+ const balance = accountState.tokens.find((item) => isSameDid(item.address, tokenAddress))?.value || 0;
188
210
 
189
- let transferIn = ZERO;
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 === 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 === 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 === 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 (!transferIn.eq(transferOut.add(new BN(balance)))) {
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.152",
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.152",
26
- "@arcblock/did-util": "1.18.152",
27
- "@arcblock/validator": "1.18.152",
28
- "@ocap/config": "1.18.152",
29
- "@ocap/indexdb": "1.18.152",
30
- "@ocap/mcrypto": "1.18.152",
31
- "@ocap/message": "1.18.152",
32
- "@ocap/state": "1.18.152",
33
- "@ocap/tx-protocols": "1.18.152",
34
- "@ocap/util": "1.18.152",
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": "4f31d06aa2a15b57a7d00fdec6241376f10ee873"
38
+ "gitHead": "414d4207886905fca9529912a12a3b6b8c811886"
39
39
  }