@ocap/tx-protocols 1.19.14 → 1.19.16

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2018-2019 ArcBlock
1
+ Copyright 2018-2025 ArcBlock
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
package/lib/execute.js CHANGED
@@ -2,7 +2,6 @@
2
2
  const { promisify } = require('util');
3
3
  const get = require('lodash/get');
4
4
  const pick = require('lodash/pick');
5
- const omit = require('lodash/omit');
6
5
  const camelCase = require('lodash/camelCase');
7
6
  const { CustomError: Error } = require('@ocap/util/lib/error');
8
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
@@ -132,49 +131,76 @@ module.exports = ({ filter, runAsLambda }) => {
132
131
  return async (context, protocols) => {
133
132
  let ctx = null;
134
133
  let error = null;
134
+ let runCount = 0;
135
+ const retryLimit = context.statedb?.config?.retryLimit || 0;
136
+ const shouldRetry = context.statedb?.config?.shouldRetry || (() => false);
137
+ const startTime = Date.now();
138
+
139
+ await runAsLambda(async (txn) => {
140
+ runCount += 1;
141
+
142
+ // create a new context each time in case we are retrying
143
+ ctx = pick(context, ['txBase64', 'statedb', 'indexdb', 'config', 'states', 'filter', 'extra', 'logger']);
144
+ Object.defineProperty(ctx, 'txn', { value: txn });
145
+
146
+ try {
147
+ await execute(ctx, protocols, true);
148
+ } catch (err) {
149
+ if (runCount <= retryLimit && shouldRetry(err)) {
150
+ // throw the error to retry
151
+ throw err;
152
+ }
153
+ error = err;
154
+ }
135
155
 
136
- try {
137
- await runAsLambda((txn) => {
138
- // create a new context each time in case we are retrying
139
- ctx = pick(context, ['txBase64', 'statedb', 'indexdb', 'config', 'states', 'filter', 'extra', 'logger']);
140
- Object.defineProperty(ctx, 'txn', { value: txn });
141
- return execute(ctx, protocols, true);
142
- });
143
- } catch (err) {
144
- error = err;
145
- } finally {
146
- if (shouldPersistTx(error)) {
147
- try {
148
- let txState = context.states.tx.create(ctx, error ? error.code || 'INTERNAL' : 'OK');
149
- flushEvents(ctx, { txState });
150
- await runAsLambda(async (txn) => {
151
- ctx = { ...omit(ctx, ['txn']), txn };
152
- if (error) {
153
- await ensureGasFeePaid(ctx);
154
- // Recreate tx to pick up gas related fields
155
- txState = context.states.tx.create(ctx, error ? error.code || 'INTERNAL' : 'OK');
156
- }
157
- await context.statedb.tx.create(txState.hash, txState, ctx);
158
- });
159
- flushEvents(ctx, { txState });
160
- ctx?.logger?.info('Tx finalized', {
161
- txHash: ctx.txHash,
162
- txStatus: error ? error.code || 'INTERNAL' : 'OK',
163
- txState,
164
- error,
165
- });
166
- } catch (err) {
167
- const txState = context.states.tx.create(ctx, error ? error.code || 'INTERNAL' : 'OK', false);
168
- ctx?.logger?.error('Failed to save invalid transaction to statedb', {
169
- error: err,
170
- txHash: ctx.txHash,
171
- txState,
172
- });
156
+ if (error && !shouldPersistTx(error)) {
157
+ ctx.logger?.error('Failed to execute transaction', { error, txHash: ctx.txHash });
158
+ // throw the error to the abort transaction
159
+ throw error;
160
+ }
161
+
162
+ // create tx
163
+ const txStatus = error ? error.code || 'INTERNAL' : 'OK';
164
+ try {
165
+ let txState = context.states.tx.create(ctx, txStatus);
166
+ flushEvents(ctx, { txState });
167
+
168
+ if (error) {
169
+ await ensureGasFeePaid(ctx);
170
+ // Recreate tx to pick up gas related fields
171
+ txState = context.states.tx.create(ctx, txStatus);
173
172
  }
174
- } else {
175
- ctx?.logger?.error('Failed to execute transaction', { error, txHash: ctx.txHash });
173
+
174
+ await context.statedb.tx.create(txState.hash, txState, ctx);
175
+ flushEvents(ctx, { txState });
176
+
177
+ ctx.logger?.info('Tx finalized', {
178
+ txHash: ctx.txHash,
179
+ txStatus,
180
+ txState,
181
+ error,
182
+ runCount,
183
+ duration: Date.now() - startTime,
184
+ });
185
+ } catch (err) {
186
+ if (runCount <= retryLimit && shouldRetry(err)) {
187
+ // throw error to retry
188
+ throw err;
189
+ }
190
+
191
+ const txState = context.states.tx.create(ctx, txStatus, false);
192
+ ctx.logger?.error('Failed to save invalid transaction to statedb', {
193
+ error: err,
194
+ protocolError: error,
195
+ txHash: ctx.txHash,
196
+ txState,
197
+ duration: Date.now() - startTime,
198
+ });
199
+
200
+ // throw error to abort transaction
201
+ throw err;
176
202
  }
177
- }
203
+ });
178
204
 
179
205
  if (error) {
180
206
  throw error;
@@ -1,6 +1,6 @@
1
1
  const noop = require('lodash/noop');
2
2
  const { CustomError: Error } = require('@ocap/util/lib/error');
3
- const { fromTokenToUnit, BN, fromUnitToToken } = require('@ocap/util');
3
+ const { fromTokenToUnit, BN, fromUnitToToken, hexToNumber } = require('@ocap/util');
4
4
  const JWT = require('@arcblock/jwt');
5
5
  const { account } = require('@ocap/state');
6
6
  const { toAddress } = require('@arcblock/did');
@@ -71,6 +71,17 @@ module.exports = function CreateEnsureTxCostPipe({
71
71
  context.gasVaultChange = changes.gas;
72
72
  }
73
73
 
74
+ // pick fee vault
75
+ if (changes.fee) {
76
+ if (config.vaults.txFees?.length) {
77
+ const feeIndex = hexToNumber(txHash.slice(-8)) % config.vaults.txFees.length;
78
+ const feeVault = config.vaults.txFees[feeIndex];
79
+ context.feeVault = feeVault;
80
+ } else {
81
+ context.feeVault = config.vaults.txFee;
82
+ }
83
+ }
84
+
74
85
  let isCostCharged = false;
75
86
  if (senderState && txCost.gt(ZERO)) {
76
87
  const expected = new BN(gasEstimate.payment || 0).add(txCost);
@@ -96,8 +107,8 @@ module.exports = function CreateEnsureTxCostPipe({
96
107
  // to be called in later pipes
97
108
  context.updateVaults = async function updateVaults() {
98
109
  const [feeVaultState, gasVaultState] = await Promise.all([
99
- statedb.account.get(config.vaults.txFee, context),
100
- statedb.account.get(gasVault, context),
110
+ changes.fee ? statedb.account.get(context.feeVault, context) : null,
111
+ changes.gas ? statedb.account.get(gasVault, context) : null,
101
112
  ]);
102
113
 
103
114
  const [newFeeVaultState, newGasVaultState] = await Promise.all([
@@ -107,18 +118,18 @@ module.exports = function CreateEnsureTxCostPipe({
107
118
  account.update(feeVaultState, applyTokenUpdates([changes.fee], feeVaultState, 'add'), context),
108
119
  context
109
120
  )
110
- : Promise.resolve(feeVaultState),
121
+ : null,
111
122
  changes.gas
112
123
  ? statedb.account.update(
113
124
  gasVaultState.address,
114
125
  account.update(gasVaultState, applyTokenUpdates([changes.gas], gasVaultState, 'add'), context),
115
126
  context
116
127
  )
117
- : Promise.resolve(gasVaultState),
128
+ : null,
118
129
  ]);
119
130
 
120
- context.feeVaultState = newFeeVaultState;
121
- context.gasVaultState = newGasVaultState;
131
+ if (newFeeVaultState) context.feeVaultState = newFeeVaultState;
132
+ if (newGasVaultState) context.gasVaultState = newGasVaultState;
122
133
 
123
134
  context.updatedAccounts = context.updatedAccounts || [];
124
135
  if (changes.fee) {
@@ -27,8 +27,8 @@ module.exports = function CreateGasEnsureFn(estimateTxGas) {
27
27
 
28
28
  // gas receiver address
29
29
  const { txGas: gasVaults } = config.vaults;
30
- const index = hexToNumber(txHash.slice(txHash.length - 4)) % gasVaults.length;
31
- const gasVault = gasVaults[index];
30
+ const gasIndex = hexToNumber(txHash.slice(-8)) % gasVaults.length;
31
+ const gasVault = gasVaults[gasIndex];
32
32
  context.gasVault = gasVault;
33
33
 
34
34
  debug({
@@ -34,6 +34,7 @@ module.exports = async (context, next) => {
34
34
  txn: context.txn,
35
35
  txTime: context.txTime,
36
36
  txHash: context.txHash,
37
+ logger: context.logger,
37
38
  args: x.args,
38
39
  });
39
40
 
@@ -6,9 +6,7 @@ const { Joi, schemas } = require('@arcblock/validator');
6
6
  const { BN } = require('@ocap/util');
7
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
8
8
  const { account, delegation } = require('@ocap/state');
9
-
10
- // eslint-disable-next-line global-require
11
- const debug = require('debug')(`${require('../../../package.json').name}:transfer-v2`);
9
+ const { toStakeAddress } = require('@arcblock/did-util');
12
10
 
13
11
  const EnsureTxGas = require('../../pipes/ensure-gas');
14
12
  const EnsureTxCost = require('../../pipes/ensure-cost');
@@ -82,7 +80,38 @@ runner.use((context, next) => {
82
80
  next();
83
81
  });
84
82
 
85
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
83
+ runner.use(
84
+ EnsureTxGas((context) => {
85
+ // FIXME: payment check
86
+ const result = { create: 0, update: 2, payment: 0 };
87
+ result.update += context.assetStates?.length || 0;
88
+
89
+ if (context.receiverState) {
90
+ result.update += 1;
91
+ } else {
92
+ result.create += 1;
93
+ }
94
+
95
+ return result;
96
+ })
97
+ );
98
+
99
+ // Pre-query states to cache them for later use
100
+ runner.use(async (context, next) => {
101
+ const { statedb, tx, gasVault } = context;
102
+ const accounts = [tx.from, context.receiver, gasVault].filter(Boolean);
103
+
104
+ await Promise.all([
105
+ ...accounts.map((x) => statedb.account.get(x, context)),
106
+ statedb.stake.get(toStakeAddress(tx.from, tx.from), context),
107
+ ]);
108
+
109
+ next();
110
+ });
111
+
112
+ runner.use(
113
+ pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
114
+ );
86
115
  runner.use(pipes.VerifyAccountMigration({ stateKey: 'senderState', addressKey: 'tx.from' }));
87
116
 
88
117
  runner.use(pipes.ExtractState({ from: 'tokenAddress', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
@@ -119,21 +148,6 @@ runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
119
148
  runner.use(pipes.VerifyUpdater({ assetKey: 'assetStates', ownerKey: 'senderState' }));
120
149
 
121
150
  // Ensure tx fee and gas
122
- runner.use(
123
- EnsureTxGas((context) => {
124
- // FIXME: payment check
125
- const result = { create: 0, update: 2, payment: 0 };
126
- result.update += context.assetStates?.length || 0;
127
-
128
- if (context.receiverState) {
129
- result.update += 1;
130
- } else {
131
- result.create += 1;
132
- }
133
-
134
- return result;
135
- })
136
- );
137
151
  runner.use(EnsureTxCost({ attachSenderChanges: true }));
138
152
 
139
153
  // transfer assets to new owner
@@ -152,7 +166,6 @@ runner.use(
152
166
  async (context, next) => {
153
167
  const {
154
168
  tx,
155
- itx,
156
169
  tokens,
157
170
  senderState,
158
171
  receiverAddr,
@@ -196,13 +209,11 @@ runner.use(
196
209
  : delegationState,
197
210
  ]);
198
211
 
199
- await updateVaults();
200
-
201
212
  context.senderState = newSenderState;
202
213
  context.receiverState = newReceiverState;
203
214
  context.delegationState = newDelegationState;
204
215
 
205
- debug('transfer-v2', { from: tx.from, to: itx.to, tokens });
216
+ await updateVaults();
206
217
 
207
218
  next();
208
219
  },
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.19.14",
6
+ "version": "1.19.16",
7
7
  "description": "Predefined tx pipeline sets to execute certain type of transactions",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -21,18 +21,18 @@
21
21
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "@arcblock/did": "1.19.14",
25
- "@arcblock/did-util": "1.19.14",
26
- "@arcblock/jwt": "1.19.14",
27
- "@arcblock/validator": "1.19.14",
28
- "@ocap/asset": "1.19.14",
29
- "@ocap/mcrypto": "1.19.14",
30
- "@ocap/merkle-tree": "1.19.14",
31
- "@ocap/message": "1.19.14",
32
- "@ocap/state": "1.19.14",
33
- "@ocap/tx-pipeline": "1.19.14",
34
- "@ocap/util": "1.19.14",
35
- "@ocap/wallet": "1.19.14",
24
+ "@arcblock/did": "1.19.16",
25
+ "@arcblock/did-util": "1.19.16",
26
+ "@arcblock/jwt": "1.19.16",
27
+ "@arcblock/validator": "1.19.16",
28
+ "@ocap/asset": "1.19.16",
29
+ "@ocap/mcrypto": "1.19.16",
30
+ "@ocap/merkle-tree": "1.19.16",
31
+ "@ocap/message": "1.19.16",
32
+ "@ocap/state": "1.19.16",
33
+ "@ocap/tx-pipeline": "1.19.16",
34
+ "@ocap/util": "1.19.16",
35
+ "@ocap/wallet": "1.19.16",
36
36
  "debug": "^4.3.6",
37
37
  "deep-diff": "^1.0.2",
38
38
  "empty-value": "^1.0.1",
@@ -40,12 +40,12 @@
40
40
  "url-join": "^4.0.1"
41
41
  },
42
42
  "resolutions": {
43
- "bn.js": "5.1.3",
43
+ "bn.js": "5.2.1",
44
44
  "elliptic": "6.5.3"
45
45
  },
46
46
  "devDependencies": {
47
47
  "jest": "^29.7.0",
48
48
  "start-server-and-test": "^1.14.0"
49
49
  },
50
- "gitHead": "97e9e70872b2b6c46bdda5d4f4bce8a890d4bd63"
50
+ "gitHead": "9c3c349e48fc3c5a63aba6068ba5d31cc46d81c9"
51
51
  }