@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 +13 -1
- package/lib/index.js +26 -10
- package/lib/migration-chain.js +137 -0
- package/lib/token-flow.js +23 -0
- package/package.json +12 -12
package/lib/hooks.js
CHANGED
|
@@ -285,11 +285,21 @@ const onClaimStake = async (tx, ctx, indexdb) => {
|
|
|
285
285
|
}
|
|
286
286
|
};
|
|
287
287
|
|
|
288
|
-
const
|
|
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
|
|
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
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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.
|
|
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.
|
|
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.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": "
|
|
38
|
+
"gitHead": "5ba096b17be4f24fb47efce520525c96f8ff9660"
|
|
39
39
|
}
|