@ocap/resolver 1.18.147 → 1.18.149
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 +17 -1
- package/lib/migration-chain.js +137 -0
- package/lib/token-flow.js +28 -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 });
|
|
@@ -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 });
|
|
@@ -154,11 +169,17 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
|
|
|
154
169
|
const address = accountQueue.pop();
|
|
155
170
|
// Avoid circular query
|
|
156
171
|
if (checkedAccounts.has(address)) continue;
|
|
172
|
+
// skip trusted accounts
|
|
173
|
+
if (await resolver.filter.isTrusted(accountAddress)) {
|
|
174
|
+
checkedAccounts.set(accountAddress, {});
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
157
177
|
|
|
158
178
|
const transactions = await resolver._getAllResults('transactions', (paging) =>
|
|
159
179
|
resolver.listTransactions({ paging, accountFilter: { accounts: [address] } }, ctx)
|
|
160
180
|
);
|
|
161
181
|
const accountState = await resolver.getAccountState({ address, traceMigration: false }, ctx);
|
|
182
|
+
|
|
162
183
|
if (!accountState) {
|
|
163
184
|
throw new CustomError('INVALID_REQUEST', `Invalid address ${address}`);
|
|
164
185
|
}
|
|
@@ -169,6 +190,10 @@ const verifyAccountRisk = async ({ accountAddress, tokenAddress }, resolver, ctx
|
|
|
169
190
|
|
|
170
191
|
// Parse txs to get transfer amounts
|
|
171
192
|
for (const tx of transactions) {
|
|
193
|
+
// fix migrate receipts
|
|
194
|
+
if (accountState.migratedFrom?.length || accountState.migratedTo?.length) {
|
|
195
|
+
await fixMigrateReceipts({ accountAddress: address, tx }, resolver, ctx);
|
|
196
|
+
}
|
|
172
197
|
// cache tx
|
|
173
198
|
if (!checkedTx.has(tx.hash)) {
|
|
174
199
|
checkedTx.set(tx.hash, await getTransferList(tx, tokenAddress));
|
|
@@ -291,6 +316,8 @@ const listTokenFlows = async (
|
|
|
291
316
|
let accountsToQueue = [];
|
|
292
317
|
|
|
293
318
|
for (const tx of transactions) {
|
|
319
|
+
// fix migrate receipts
|
|
320
|
+
await fixMigrateReceipts({ accountAddress: address, tx }, resolver, ctx);
|
|
294
321
|
// cache tx
|
|
295
322
|
if (!checkedTx.has(tx.hash)) {
|
|
296
323
|
checkedTx.set(tx.hash, await getTransferFlow(tx, tokenAddress));
|
|
@@ -340,4 +367,5 @@ module.exports = {
|
|
|
340
367
|
getTransferFlow,
|
|
341
368
|
verifyAccountRisk,
|
|
342
369
|
listTokenFlows,
|
|
370
|
+
fixMigrateReceipts,
|
|
343
371
|
};
|
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.149",
|
|
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.149",
|
|
26
|
+
"@arcblock/did-util": "1.18.149",
|
|
27
|
+
"@arcblock/validator": "1.18.149",
|
|
28
|
+
"@ocap/config": "1.18.149",
|
|
29
|
+
"@ocap/indexdb": "1.18.149",
|
|
30
|
+
"@ocap/mcrypto": "1.18.149",
|
|
31
|
+
"@ocap/message": "1.18.149",
|
|
32
|
+
"@ocap/state": "1.18.149",
|
|
33
|
+
"@ocap/tx-protocols": "1.18.149",
|
|
34
|
+
"@ocap/util": "1.18.149",
|
|
35
35
|
"debug": "^4.3.6",
|
|
36
36
|
"lodash": "^4.17.21"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "8979a303ba489127e429df9ce09738995f716d71"
|
|
39
39
|
}
|