@ocap/tx-protocols 1.20.1 → 1.20.2

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/execute.js CHANGED
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable consistent-return */
2
- const { promisify } = require('util');
3
2
  const get = require('lodash/get');
4
3
  const pick = require('lodash/pick');
5
4
  const camelCase = require('lodash/camelCase');
@@ -9,9 +8,8 @@ const { Runner, pipes } = require('@ocap/tx-pipeline');
9
8
  const createEnsureGasFn = require('./pipes/ensure-gas');
10
9
  const createEnsureCostFn = require('./pipes/ensure-cost');
11
10
 
12
- // FIXME: DeprecationWarning: Calling promisify on a function that returns a Promise is likely a mistake.
13
- const ensureTxGas = promisify(createEnsureGasFn(() => ({ create: 0, update: 2 })));
14
- const ensureTxCost = promisify(createEnsureCostFn({ attachSenderChanges: true, gasOnly: true }));
11
+ const ensureTxGas = createEnsureGasFn(() => ({ create: 0, update: 2 }));
12
+ const ensureTxCost = createEnsureCostFn({ attachSenderChanges: true, gasOnly: true });
15
13
 
16
14
  const getTxName = (typeUrl) => camelCase(typeUrl.split(':').pop());
17
15
 
@@ -50,24 +48,45 @@ module.exports = ({ filter, runAsLambda }) => {
50
48
  pre.use(pipes.VerifySignature);
51
49
 
52
50
  // charge gas fee for errored tx
53
- const ensureGasFeePaid = async (context) => {
54
- try {
55
- await ensureTxGas(context);
56
- await ensureTxCost(context);
51
+ const ensureGasFeePaid = (context) => {
52
+ const gasRunner = new Runner();
53
+
54
+ gasRunner.use(ensureTxGas);
55
+ gasRunner.use(ensureTxCost);
56
+ gasRunner.use(async (ctx, next) => {
57
+ const { tx, statedb, senderUpdates, updateVaults } = ctx;
58
+
59
+ // If senderState does not exist and there is no stakeId for gas, an error will be thrown in ensureTxCost before here.
60
+ // So if there is no senderState here, it means there has a stake for gas, so allowed to continue.
61
+ if (ctx.senderState) {
62
+ const senderState = ctx.stateSnapshot?.[ctx.senderState.address] || ctx.senderState;
63
+ await statedb.account.update(
64
+ senderState.address,
65
+ ctx.states.account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, ctx),
66
+ ctx
67
+ );
68
+ await updateVaults();
69
+ }
57
70
 
58
- const { tx, statedb, senderState, senderUpdates, updateVaults } = context;
71
+ return next();
72
+ });
59
73
 
60
- await Promise.all([
61
- statedb.account.update(
62
- senderState.address,
63
- context.states.account.update(senderState, { nonce: tx.nonce, pk: tx.pk, ...senderUpdates }, context),
64
- context
65
- ),
66
- ]);
67
- await updateVaults();
68
- } catch (err) {
69
- context.logger?.error('Failed to charge gas fee for errored tx', { error: err, txHash: context.txHash });
70
- }
74
+ return new Promise((resolve, reject) => {
75
+ gasRunner.run(context, (err) => {
76
+ if (err) {
77
+ context.logger?.error('Failed to charge gas fee for errored tx', {
78
+ error: err,
79
+ tx: context.tx,
80
+ txBase64: context.txBase64,
81
+ txHash: context.txHash,
82
+ });
83
+ reject(err);
84
+ return;
85
+ }
86
+
87
+ resolve();
88
+ });
89
+ });
71
90
  };
72
91
 
73
92
  // eslint-disable-next-line require-await
@@ -86,123 +105,121 @@ module.exports = ({ filter, runAsLambda }) => {
86
105
  }
87
106
 
88
107
  // eslint-disable-next-line no-shadow
89
- protocol.run(context, async (err) => {
90
- // we should only flush events when retry is not supported
91
- // otherwise the outer caller should handle these 2
92
- if (shouldPersistTx(err) && isRetrySupported === false) {
93
- if (err) {
94
- await ensureGasFeePaid(context);
108
+ protocol.run(context, async (error) => {
109
+ if (isRetrySupported) {
110
+ if (error) {
111
+ return reject(error);
95
112
  }
113
+ return resolve(context);
114
+ }
115
+
116
+ const txStatus = error ? error.code || 'INTERNAL' : 'OK';
117
+ let txState = context.states.tx.create(context, txStatus);
96
118
 
97
- let txState;
119
+ if (shouldPersistTx(error)) {
98
120
  try {
99
- txState = context.states.tx.create(context, err ? err.code || 'INTERNAL' : 'OK');
121
+ if (error) {
122
+ await ensureGasFeePaid(context);
123
+ // Recreate tx to pick up gas related fields
124
+ txState = context.states.tx.create(context, txStatus);
125
+ }
126
+
100
127
  await context.statedb.tx.create(txState.hash, txState, context);
101
- context.logger?.info('Tx finalized', {
102
- txHash: context.txHash,
103
- txStatus: err ? err.code || 'INTERNAL' : 'OK',
104
- txState,
105
- error: err,
106
- });
128
+ flushEvents(context, { txState });
107
129
  } catch (e) {
108
- context.logger?.error('Failed to save invalid transaction to statedb', {
130
+ context.logger?.error('Failed to save transaction to statedb', {
109
131
  error: e,
132
+ txError: error,
110
133
  txHash: context.txHash,
111
134
  txState,
112
135
  });
113
- return reject(e);
136
+ // If we get an internal error here, should return the original tx error to client
137
+ return reject(error);
114
138
  }
115
-
116
- flushEvents(context, { txState });
117
139
  }
118
140
 
119
- // after executing the transaction
120
- if (err) {
121
- context.logger?.error('Failed to execute transaction', { error: err, txHash: context.txHash });
122
- return reject(err);
123
- }
141
+ context.logger?.info('Tx finalized', {
142
+ txHash: context.txHash,
143
+ txStatus,
144
+ txState,
145
+ error,
146
+ });
124
147
 
125
- resolve(context);
148
+ if (error) {
149
+ return reject(error);
150
+ }
151
+ return resolve(context);
126
152
  });
127
153
  });
128
154
  });
129
155
 
130
156
  if (typeof runAsLambda === 'function') {
131
157
  return async (context, protocols) => {
132
- let ctx = null;
133
- let error = null;
134
- let runCount = 0;
135
- const retryLimit = context.statedb?.config?.retryLimit || 0;
136
- const shouldRetry = context.statedb?.config?.shouldRetry || (() => false);
158
+ let ctx = context;
137
159
  const startTime = Date.now();
138
160
 
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 });
161
+ try {
162
+ await runAsLambda(async (txn) => {
163
+ // create a new context each time in case we are retrying
164
+ ctx = pick(context, ['txBase64', 'statedb', 'indexdb', 'config', 'states', 'filter', 'extra', 'logger']);
165
+ ctx.txn = txn;
145
166
 
146
- try {
147
167
  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
- }
155
-
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);
172
- }
173
168
 
169
+ const txState = context.states.tx.create(ctx, 'OK');
174
170
  await context.statedb.tx.create(txState.hash, txState, ctx);
171
+
172
+ // update indexdb
175
173
  flushEvents(ctx, { txState });
176
174
 
177
175
  ctx.logger?.info('Tx finalized', {
178
176
  txHash: ctx.txHash,
179
- txStatus,
180
177
  txState,
181
- error,
182
- runCount,
178
+ txStatus: 'OK',
183
179
  duration: Date.now() - startTime,
184
180
  });
185
- } catch (err) {
186
- if (runCount <= retryLimit && shouldRetry(err)) {
187
- // throw error to retry
188
- throw err;
189
- }
181
+ });
182
+ } catch (error) {
183
+ const txStatus = error.code || 'INTERNAL';
184
+ let txState = context.states.tx.create(ctx, txStatus, false);
190
185
 
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
- });
186
+ ctx.logger?.error('Failed to execute transaction', { error, txHash: ctx.txHash, txState });
187
+
188
+ if (shouldPersistTx(error)) {
189
+ try {
190
+ await runAsLambda(async (txn) => {
191
+ ctx = Object.assign({}, ctx, { txn, cacheStates: null });
192
+
193
+ await ensureGasFeePaid(ctx);
194
+
195
+ // Recreate tx to pick up gas related fields
196
+ txState = context.states.tx.create(ctx, txStatus);
199
197
 
200
- // throw error to abort transaction
201
- throw err;
198
+ await ctx.statedb.tx.create(txState.hash, txState, ctx);
199
+
200
+ // update indexdb
201
+ flushEvents(ctx, { txState });
202
+
203
+ ctx.logger?.info('Tx finalized', {
204
+ txHash: ctx.txHash,
205
+ txState,
206
+ txStatus,
207
+ error,
208
+ duration: Date.now() - startTime,
209
+ });
210
+ });
211
+ } catch (err) {
212
+ ctx.logger?.error('Failed to save invalid transaction to statedb', {
213
+ error: err,
214
+ txError: error,
215
+ txHash: ctx.txHash,
216
+ txState,
217
+ });
218
+ // If we get an error here, should return the original tx error to client
219
+ throw error;
220
+ }
202
221
  }
203
- });
204
222
 
205
- if (error) {
206
223
  throw error;
207
224
  }
208
225
 
@@ -24,7 +24,7 @@ module.exports = function CreateEnsureTxCostPipe({
24
24
  } = {}) {
25
25
  return async function EnsureTxCost(context, next) {
26
26
  // TODO: we are using the sender as gas payer, this may change in future
27
- const { config, statedb, txType, senderState, gasEstimate, gasVault, totalGas } = context;
27
+ const { config, statedb, txType, senderState, gasEstimate, totalGas } = context;
28
28
 
29
29
  // verify gas staking headers
30
30
  const { tx, extra = {}, txHash } = context;
@@ -83,11 +83,11 @@ module.exports = function CreateEnsureTxCostPipe({
83
83
  }
84
84
 
85
85
  let isCostCharged = false;
86
- if (senderState && txCost.gt(ZERO)) {
86
+ if (txCost.gt(ZERO)) {
87
87
  const expected = new BN(gasEstimate.payment || 0).add(txCost);
88
- const actual = new BN(senderState.tokens[config.token.address] || 0);
88
+ const actual = new BN(senderState?.tokens?.[config.token.address] || 0);
89
89
  // If we have someone with enough balance to pay for this tx
90
- if (actual.gte(expected)) {
90
+ if (senderState && actual.gte(expected)) {
91
91
  isCostCharged = true;
92
92
 
93
93
  // to be merged into later pipe
@@ -108,7 +108,7 @@ module.exports = function CreateEnsureTxCostPipe({
108
108
  context.updateVaults = async function updateVaults() {
109
109
  const [feeVaultState, gasVaultState] = await Promise.all([
110
110
  changes.fee ? statedb.account.get(context.feeVault, context) : null,
111
- changes.gas ? statedb.account.get(gasVault, context) : null,
111
+ changes.gas ? statedb.account.get(context.gasVault, context) : null,
112
112
  ]);
113
113
 
114
114
  const [newFeeVaultState, newGasVaultState] = await Promise.all([
@@ -167,11 +167,13 @@ module.exports = function CreateEnsureTxCostPipe({
167
167
 
168
168
  context.gasPaid = true;
169
169
  };
170
+ } else if (!senderState) {
171
+ return next(new Error('INVALID_GAS_PAYER', `Gas payer ${tx.from} does not exist on chain`));
170
172
  } else if (throwOnInsufficientFund) {
171
173
  return next(
172
174
  new Error(
173
175
  'INSUFFICIENT_FUND',
174
- `Insufficient fund to pay for tx cost from ${senderState.address}, expected ${fromUnitToToken(
176
+ `Insufficient fund to pay for tx cost from ${senderState?.address || tx.from}, expected ${fromUnitToToken(
175
177
  expected,
176
178
  config.token.decimal
177
179
  )}, got ${fromUnitToToken(actual, config.token.decimal)}`
@@ -69,7 +69,6 @@ runner.use(
69
69
  })
70
70
  );
71
71
  runner.use(EnsureTxCost({ attachSenderChanges: true, throwOnInsufficientFund: false }));
72
- runner.use(pipes.VerifyGasPayer());
73
72
 
74
73
  // Save context snapshot before updating states
75
74
  runner.use(pipes.TakeStateSnapshot());
@@ -178,7 +178,6 @@ runner.use(
178
178
  })
179
179
  );
180
180
  runner.use(EnsureTxCost({ attachSenderChanges: true }));
181
- runner.use(pipes.VerifyGasPayer());
182
181
 
183
182
  // Save context snapshot before updating states
184
183
  runner.use(pipes.TakeStateSnapshot());
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.20.1",
6
+ "version": "1.20.2",
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.20.1",
25
- "@arcblock/did-util": "1.20.1",
26
- "@arcblock/jwt": "1.20.1",
27
- "@arcblock/validator": "1.20.1",
28
- "@ocap/asset": "1.20.1",
29
- "@ocap/mcrypto": "1.20.1",
30
- "@ocap/merkle-tree": "1.20.1",
31
- "@ocap/message": "1.20.1",
32
- "@ocap/state": "1.20.1",
33
- "@ocap/tx-pipeline": "1.20.1",
34
- "@ocap/util": "1.20.1",
35
- "@ocap/wallet": "1.20.1",
24
+ "@arcblock/did": "1.20.2",
25
+ "@arcblock/did-util": "1.20.2",
26
+ "@arcblock/jwt": "1.20.2",
27
+ "@arcblock/validator": "1.20.2",
28
+ "@ocap/asset": "1.20.2",
29
+ "@ocap/mcrypto": "1.20.2",
30
+ "@ocap/merkle-tree": "1.20.2",
31
+ "@ocap/message": "1.20.2",
32
+ "@ocap/state": "1.20.2",
33
+ "@ocap/tx-pipeline": "1.20.2",
34
+ "@ocap/util": "1.20.2",
35
+ "@ocap/wallet": "1.20.2",
36
36
  "debug": "^4.3.6",
37
37
  "deep-diff": "^1.0.2",
38
38
  "empty-value": "^1.0.1",
@@ -47,5 +47,5 @@
47
47
  "jest": "^29.7.0",
48
48
  "start-server-and-test": "^1.14.0"
49
49
  },
50
- "gitHead": "f73bddbe4b86106fd348e43ce9e19a626acdc9f6"
50
+ "gitHead": "70ddd9b43070605879fed7dff50a9f19f5325ea8"
51
51
  }