@ocap/resolver 1.18.146 → 1.18.148

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/hooks.js CHANGED
@@ -285,11 +285,21 @@ const onClaimStake = async (tx, ctx, indexdb) => {
285
285
  }
286
286
  };
287
287
 
288
- const onCreateTx = async (tx, ctx, indexdb) => {
288
+ const onAccountMigrate = async (tx, resolver) => {
289
+ try {
290
+ await resolver.buildMigrationChain();
291
+ } catch (e) {
292
+ console.error('build migration chain error', e);
293
+ }
294
+ };
295
+
296
+ const onCreateTx = async (tx, ctx, resolver) => {
289
297
  if (tx.code !== 'OK') {
290
298
  return;
291
299
  }
292
300
 
301
+ const { indexdb } = resolver;
302
+
293
303
  switch (tx.tx.itxJson.type_url) {
294
304
  case 'fg:t:stake':
295
305
  return onStake(tx, ctx, indexdb);
@@ -306,6 +316,8 @@ const onCreateTx = async (tx, ctx, indexdb) => {
306
316
  case 'fg:t:withdraw_token_v2':
307
317
  // totalWithdrawAmount does not include txFee
308
318
  return updateRollupStats(ctx.rollupState.address, tx.tx.itxJson.token.value, 'totalWithdrawAmount', indexdb);
319
+ case 'fg:t:account_migrate':
320
+ return onAccountMigrate(tx, resolver);
309
321
  default:
310
322
  }
311
323
  };
package/lib/index.js CHANGED
@@ -47,6 +47,7 @@ const debug = require('debug')(require('../package.json').name);
47
47
  const hooks = require('./hooks');
48
48
  const tokenFlow = require('./token-flow');
49
49
  const { getInstance: getTokenCacheInstance } = require('./token-cache');
50
+ const { MigrationChainManager } = require('./migration-chain');
50
51
 
51
52
  const noop = (x) => x;
52
53
  const CHAIN_ADDR = md5('OCAP_CHAIN_ADDR');
@@ -189,6 +190,8 @@ module.exports = class OCAPResolver {
189
190
  }
190
191
  }
191
192
 
193
+ this.buildMigrationChain();
194
+
192
195
  this.executor = createExecutor({
193
196
  filter,
194
197
  runAsLambda: typeof statedb.runAsLambda === 'function' ? statedb.runAsLambda.bind(statedb) : null,
@@ -819,7 +822,7 @@ module.exports = class OCAPResolver {
819
822
  const tx = await createIndexedTransaction(x, ctx, this.indexdb);
820
823
  await this.indexdb.tx.insert(tx);
821
824
  if (typeof hooks.onCreateTx === 'function') {
822
- await hooks.onCreateTx(tx, ctx, this.indexdb);
825
+ await hooks.onCreateTx(tx, ctx, this);
823
826
  }
824
827
  } catch (error) {
825
828
  console.error('create tx index failed', { account: x, error });
@@ -959,15 +962,15 @@ module.exports = class OCAPResolver {
959
962
  }
960
963
 
961
964
  if (!tx.receipts) {
962
- if (get(tx, 'tx.itxJson.type_url') === 'fg:t:revoke_withdraw') {
963
- const withdrawHash = get(tx, 'tx.itxJson.withdraw_tx_hash');
964
- // Avoid infinite loop by invalid data
965
- if (withdrawHash && withdrawHash !== tx.hash) {
966
- const withdrawTx = await this.getTx({ hash: withdrawHash }, ctx);
967
- tx.receipts = getTxReceipts(tx, { config: this.config, withdrawTx });
968
- }
969
- } else {
970
- tx.receipts = getTxReceipts(tx, { config: this.config });
965
+ tx.receipts = getTxReceipts(tx, { config: this.config });
966
+ }
967
+
968
+ if (get(tx, 'tx.itxJson.type_url') === 'fg:t:revoke_withdraw') {
969
+ const withdrawHash = get(tx, 'tx.itxJson.withdraw_tx_hash');
970
+ // Avoid infinite loop by invalid data
971
+ if (withdrawHash && withdrawHash !== tx.hash) {
972
+ const withdrawTx = await this.getTx({ hash: withdrawHash }, ctx);
973
+ tx.receipts = getTxReceipts(tx, { config: this.config, withdrawTx });
971
974
  }
972
975
  }
973
976
 
@@ -1137,6 +1140,19 @@ module.exports = class OCAPResolver {
1137
1140
 
1138
1141
  return state;
1139
1142
  }
1143
+
1144
+ async buildMigrationChain() {
1145
+ const migrationTxs = await this._getAllResults('transactions', (paging) =>
1146
+ this.listTransactions({
1147
+ paging,
1148
+ typeFilter: { types: ['account_migrate'] },
1149
+ validityFilter: { validity: ['VALID'] },
1150
+ })
1151
+ );
1152
+ const migrationChain = new MigrationChainManager();
1153
+ migrationChain.buildChains(migrationTxs);
1154
+ this.migrationChain = migrationChain;
1155
+ }
1140
1156
  };
1141
1157
 
1142
1158
  module.exports.formatData = formatData;
@@ -0,0 +1,137 @@
1
+ /* eslint-disable max-classes-per-file */
2
+
3
+ class MigrationNode {
4
+ constructor(address, validFrom, validUntil = null, nextAddress = null) {
5
+ this.address = address;
6
+ this.validFrom = validFrom;
7
+ this.validUntil = validUntil;
8
+ this.nextAddress = nextAddress;
9
+ }
10
+ }
11
+
12
+ class MigrationChainManager {
13
+ constructor() {
14
+ // Main storage for migration chains
15
+ this.chains = new Map();
16
+ // Index for quick root lookup
17
+ this.rootAddresses = new Map();
18
+ }
19
+
20
+ validateMigration(migration) {
21
+ if (!migration?.tx?.from || !migration?.tx?.itxJson?.address || !migration?.time) {
22
+ throw new Error('Invalid migration format');
23
+ }
24
+ if (new Date(migration.time).toString() === 'Invalid Date') {
25
+ throw new Error('Invalid timestamp');
26
+ }
27
+ return true;
28
+ }
29
+
30
+ /**
31
+ * Build migration chains from a list of migration transactions
32
+ * @param {Array} migrations - List of migration transactions
33
+ */
34
+ buildChains(migrations) {
35
+ if (!Array.isArray(migrations)) {
36
+ throw new Error('Migrations must be an array');
37
+ }
38
+
39
+ migrations.forEach((migration) => this.validateMigration(migration));
40
+
41
+ // Sort migrations by timestamp
42
+ const sortedMigrations = [...migrations].sort((a, b) => new Date(a.time) - new Date(b.time));
43
+
44
+ for (const migration of sortedMigrations) {
45
+ const fromAddr = migration.tx.from;
46
+ const toAddr = migration.tx.itxJson.address;
47
+ const timestamp = new Date(migration.time);
48
+
49
+ this._processMigration(fromAddr, toAddr, timestamp);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Process a single migration and update the chains
55
+ * @param {string} fromAddr - Source address
56
+ * @param {string} toAddr - Destination address
57
+ * @param {Date} timestamp - Migration timestamp
58
+ * @private
59
+ */
60
+ _processMigration(fromAddr, toAddr, timestamp) {
61
+ // Find or create the root address for this chain
62
+ const rootAddr = this.rootAddresses.get(fromAddr) || fromAddr;
63
+
64
+ // Get or create the chain
65
+ if (!this.chains.has(rootAddr)) {
66
+ this.chains.set(rootAddr, []);
67
+ }
68
+ const chain = this.chains.get(rootAddr);
69
+
70
+ if (chain.length === 0) {
71
+ // First migration in the chain
72
+ chain.push(new MigrationNode(fromAddr, new Date(0), timestamp));
73
+ chain.push(new MigrationNode(toAddr, timestamp));
74
+ } else {
75
+ // Update the previous node's valid_until and next_address
76
+ const lastNode = chain[chain.length - 1];
77
+ lastNode.validUntil = timestamp;
78
+ lastNode.nextAddress = toAddr;
79
+ // Add the new node
80
+ chain.push(new MigrationNode(toAddr, timestamp));
81
+ }
82
+
83
+ // Update root address mapping for the new address
84
+ this.rootAddresses.set(toAddr, rootAddr);
85
+ }
86
+
87
+ /**
88
+ * Find the valid address at a specific timestamp
89
+ * @param {string} initialAddress - Address to look up
90
+ * @param {Date} timestamp - Timestamp to check
91
+ * @returns {string} Valid address at the timestamp
92
+ */
93
+ findAddressAtTime(initialAddress, timestamp) {
94
+ // Get the root address
95
+ const rootAddr = this.rootAddresses.get(initialAddress) || initialAddress;
96
+ const chain = this.chains.get(rootAddr);
97
+
98
+ if (!chain) {
99
+ return initialAddress;
100
+ }
101
+
102
+ // Binary search for the correct address
103
+ let left = 0;
104
+ let right = chain.length - 1;
105
+
106
+ while (left <= right) {
107
+ const mid = Math.floor((left + right) / 2);
108
+ const node = chain[mid];
109
+
110
+ if (node.validFrom <= timestamp && (!node.validUntil || timestamp < node.validUntil)) {
111
+ return node.address;
112
+ }
113
+ if (timestamp < node.validFrom) {
114
+ right = mid - 1;
115
+ } else {
116
+ left = mid + 1;
117
+ }
118
+ }
119
+
120
+ return chain[chain.length - 1].address;
121
+ }
122
+
123
+ /**
124
+ * Get the complete migration history for an address
125
+ * @param {string} address - Address to look up
126
+ * @returns {Array} List of migration nodes
127
+ */
128
+ getMigrationHistory(address) {
129
+ const rootAddr = this.rootAddresses.get(address) || address;
130
+ return this.chains.get(rootAddr) || [];
131
+ }
132
+ }
133
+
134
+ module.exports = {
135
+ MigrationNode,
136
+ MigrationChainManager,
137
+ };
package/lib/token-flow.js CHANGED
@@ -125,6 +125,21 @@ const getVaultAccounts = (config) => {
125
125
  return Object.values(config.vaults).flat();
126
126
  };
127
127
 
128
+ const fixMigrateReceipts = async ({ accountAddress, tx }, resolver) => {
129
+ const { migrationChain } = resolver;
130
+ const address = migrationChain.findAddressAtTime(accountAddress, new Date(tx.time));
131
+ const migrations = migrationChain.getMigrationHistory(accountAddress);
132
+
133
+ // fix receipts address
134
+ if (address && migrations.length) {
135
+ tx.receipts.forEach((receipt) => {
136
+ if (migrations.some((x) => x.address === receipt.address)) {
137
+ receipt.address = address;
138
+ }
139
+ });
140
+ }
141
+ };
142
+
128
143
  const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx = {}) => {
129
144
  // validate request params
130
145
  const { error } = paramsSchema.validate({ accountAddress, tokenAddress, resolver });
@@ -159,6 +174,7 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
159
174
  resolver.listTransactions({ paging, accountFilter: { accounts: [address] } }, ctx)
160
175
  );
161
176
  const accountState = await resolver.getAccountState({ address, traceMigration: false }, ctx);
177
+
162
178
  if (!accountState) {
163
179
  throw new CustomError('INVALID_REQUEST', `Invalid address ${address}`);
164
180
  }
@@ -169,6 +185,10 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
169
185
 
170
186
  // Parse txs to get transfer amounts
171
187
  for (const tx of transactions) {
188
+ // fix migrate receipts
189
+ if (accountState.migratedFrom?.length || accountState.migratedTo?.length) {
190
+ await fixMigrateReceipts({ accountAddress: address, tx }, resolver, ctx);
191
+ }
172
192
  // cache tx
173
193
  if (!checkedTx.has(tx.hash)) {
174
194
  checkedTx.set(tx.hash, await getTransferList(tx, tokenAddress));
@@ -291,6 +311,8 @@ const listTokenFlows = async (
291
311
  let accountsToQueue = [];
292
312
 
293
313
  for (const tx of transactions) {
314
+ // fix migrate receipts
315
+ await fixMigrateReceipts({ accountAddress: address, tx }, resolver, ctx);
294
316
  // cache tx
295
317
  if (!checkedTx.has(tx.hash)) {
296
318
  checkedTx.set(tx.hash, await getTransferFlow(tx, tokenAddress));
@@ -340,4 +362,5 @@ module.exports = {
340
362
  getTransferFlow,
341
363
  verifyAccountRisk,
342
364
  listTokenFlows,
365
+ fixMigrateReceipts,
343
366
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.18.146",
6
+ "version": "1.18.148",
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.146",
26
- "@arcblock/did-util": "1.18.146",
27
- "@arcblock/validator": "1.18.146",
28
- "@ocap/config": "1.18.146",
29
- "@ocap/indexdb": "1.18.146",
30
- "@ocap/mcrypto": "1.18.146",
31
- "@ocap/message": "1.18.146",
32
- "@ocap/state": "1.18.146",
33
- "@ocap/tx-protocols": "1.18.146",
34
- "@ocap/util": "1.18.146",
25
+ "@arcblock/did": "1.18.148",
26
+ "@arcblock/did-util": "1.18.148",
27
+ "@arcblock/validator": "1.18.148",
28
+ "@ocap/config": "1.18.148",
29
+ "@ocap/indexdb": "1.18.148",
30
+ "@ocap/mcrypto": "1.18.148",
31
+ "@ocap/message": "1.18.148",
32
+ "@ocap/state": "1.18.148",
33
+ "@ocap/tx-protocols": "1.18.148",
34
+ "@ocap/util": "1.18.148",
35
35
  "debug": "^4.3.6",
36
36
  "lodash": "^4.17.21"
37
37
  },
38
- "gitHead": "ea7f3d1ad606f277b89c390364f20bf295d9e4f9"
38
+ "gitHead": "5ba096b17be4f24fb47efce520525c96f8ff9660"
39
39
  }