@ocap/tx-protocols 1.18.0 → 1.18.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.
@@ -1,12 +1,15 @@
1
1
  const noop = require('lodash/noop');
2
2
  const { CustomError: Error } = require('@ocap/util/lib/error');
3
3
  const { fromTokenToUnit, BN } = require('@ocap/util');
4
+ const JWT = require('@arcblock/jwt');
4
5
  const { account } = require('@ocap/state');
6
+ const { toAddress } = require('@arcblock/did');
7
+ const { toStakeAddress } = require('@arcblock/did-util');
5
8
 
6
9
  // eslint-disable-next-line global-require
7
10
  const debug = require('debug')(`${require('../../package.json').name}:pipes:ensure-cost`);
8
11
 
9
- const { applyTokenUpdates } = require('../util');
12
+ const { applyTokenUpdates, isGasStakeValid } = require('../util');
10
13
 
11
14
  // We have a layered transaction cost design
12
15
  // - gas: charged for every tx for creating/updating states on the ledger
@@ -18,9 +21,9 @@ module.exports = function CreateEnsureTxCostPipe({ attachSenderChanges = true }
18
21
  const { config, statedb, txType, senderState, gasEstimate, gasVaultState, totalGas } = context;
19
22
  const feeVaultState = await statedb.account.get(config.vaults.txFee, context);
20
23
 
24
+ // Get service fee and gas fee
21
25
  const changes = {};
22
26
  let txCost = new BN(0);
23
-
24
27
  const txFee = config.transaction.txFee[txType];
25
28
  if (txFee) {
26
29
  const totalFee = fromTokenToUnit(txFee, config.token.decimal);
@@ -34,7 +37,28 @@ module.exports = function CreateEnsureTxCostPipe({ attachSenderChanges = true }
34
37
  context.gasVaultChange = changes.gas;
35
38
  }
36
39
 
37
- if (senderState) {
40
+ // verify gas staking headers
41
+ const { tx, gasStakeHeaders = {} } = context;
42
+ const { token, pk } = gasStakeHeaders || {};
43
+ const gasStake = {};
44
+ if (token && pk) {
45
+ if (JWT.verify(token, pk) === false) {
46
+ return next(new Error('INVALID_GAS_HEADER', 'Gas headers can not be verified'));
47
+ }
48
+
49
+ const decoded = JWT.decode(token);
50
+ gasStake.owner = toAddress(decoded.iss);
51
+ gasStake.stakeId = toStakeAddress(gasStake.owner, gasStake.owner);
52
+ } else {
53
+ gasStake.owner = tx.from;
54
+ gasStake.stakeId = toStakeAddress(gasStake.owner, gasStake.owner);
55
+ }
56
+ gasStake.state = await statedb.stake.get(gasStake.stakeId, context);
57
+ gasStake.valid = isGasStakeValid(gasStake.state, config);
58
+ context.gasStake = gasStake;
59
+
60
+ // If we have someone to pay for this tx
61
+ if (senderState && !gasStake.valid) {
38
62
  const expected = new BN(gasEstimate.payment || 0).add(txCost);
39
63
  const actual = new BN(senderState.tokens[config.token.address] || 0);
40
64
  if (actual.lt(expected)) {
@@ -121,7 +121,11 @@ runner.use(
121
121
  const senderUpdates = {
122
122
  nonce: tx.nonce,
123
123
  pk: tx.pk,
124
- ...applyTokenUpdates(factoryTokens, applyTokenChange(senderState, senderChange), 'sub'),
124
+ ...applyTokenUpdates(
125
+ factoryTokens,
126
+ senderChange ? applyTokenChange(senderState, senderChange) : senderState,
127
+ 'sub'
128
+ ),
125
129
  };
126
130
 
127
131
  // Owner updates
@@ -3,6 +3,7 @@ const { promisify } = require('util');
3
3
  const isEmpty = require('empty-value');
4
4
  const { Joi } = require('@arcblock/validator');
5
5
  const { CustomError: Error } = require('@ocap/util/lib/error');
6
+ const { BN, fromTokenToUnit } = require('@ocap/util');
6
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
7
8
  const { toStakeAddress } = require('@arcblock/did-util');
8
9
  const { account, asset, stake } = require('@ocap/state');
@@ -10,7 +11,7 @@ const { account, asset, stake } = require('@ocap/state');
10
11
  // eslint-disable-next-line global-require
11
12
  const debug = require('debug')(`${require('../../../package.json').name}:stake`);
12
13
 
13
- const { applyTokenUpdates, applyTokenChange } = require('../../util');
14
+ const { applyTokenUpdates, applyTokenChange, isGasStakeAddress, isGasStakeInput } = require('../../util');
14
15
 
15
16
  const EnsureTxGas = require('../../pipes/ensure-gas');
16
17
  const EnsureTxCost = require('../../pipes/ensure-cost');
@@ -97,6 +98,37 @@ runner.use(async (context, next) => {
97
98
  return next();
98
99
  });
99
100
 
101
+ // 8. handle tx gas stakes
102
+ runner.use(async (context, next) => {
103
+ const { tx, itx, inputs, config } = context;
104
+ const { token, transaction } = config;
105
+ const { minStake, maxStake } = transaction.txGas;
106
+
107
+ // FIXME: delegation not supported here
108
+ context.isGasStake = isGasStakeAddress(tx.from, itx.address) && isGasStakeInput(inputs, token.address);
109
+
110
+ if (context.isGasStake) {
111
+ const [tokenInput] = inputs[0].tokensList;
112
+ const actualStake = new BN(tokenInput.value);
113
+ const expectedMinStake = fromTokenToUnit(minStake, token.decimal);
114
+ const expectedMaxStake = fromTokenToUnit(maxStake, token.decimal);
115
+ if (actualStake.lt(expectedMinStake)) {
116
+ return next(new Error('INVALID_TX', `Stake for gas should be greater than: ${minStake}`));
117
+ }
118
+ if (actualStake.gt(expectedMaxStake)) {
119
+ return next(new Error('INVALID_TX', `Stake for gas should be less than: ${maxStake}`));
120
+ }
121
+ }
122
+
123
+ if (context.isGasStake) {
124
+ context.revokeWaitingPeriod = transaction.txGas.stakeLockPeriod;
125
+ } else {
126
+ context.revokeWaitingPeriod = itx.revokeWaitingPeriod;
127
+ }
128
+
129
+ return next();
130
+ });
131
+
100
132
  // Ensure tx fee and gas
101
133
  runner.use(
102
134
  EnsureTxGas((context) => {
@@ -192,7 +224,8 @@ runner.use(
192
224
  {
193
225
  sender: tx.from,
194
226
  revocable: !itx.locked,
195
- ...pick(itx, ['address', 'receiver', 'message', 'data', 'revokeWaitingPeriod']),
227
+ revokeWaitingPeriod: context.revokeWaitingPeriod,
228
+ ...pick(itx, ['address', 'receiver', 'message', 'data']),
196
229
  ...stakeUpdates,
197
230
  },
198
231
  context
@@ -268,7 +268,7 @@ runner.use(
268
268
  let updateSet = applyTokenChange(x, updates.account[x.address]);
269
269
  if (x.address === senderState.address) {
270
270
  updateSet.nonce = tx.nonce;
271
- updateSet = Object.assign(updateSet, applyTokenChange(updateSet, senderChange));
271
+ updateSet = Object.assign(updateSet, senderChange ? applyTokenChange(updateSet, senderChange) : {});
272
272
  }
273
273
 
274
274
  return statedb.account.update(x.address, account.update(x, updateSet, context), context);
@@ -204,7 +204,9 @@ runner.use(
204
204
  const fee = getBNSum(itx.actualFee, itx.maxFee);
205
205
 
206
206
  let senderUpdates = applyTokenUpdates([{ address: itx.token.address, value: total }], senderState, 'sub');
207
- senderUpdates = applyTokenChange(senderUpdates, senderChange);
207
+ if (senderChange) {
208
+ senderUpdates = applyTokenChange(senderUpdates, senderChange);
209
+ }
208
210
 
209
211
  // Burned amount should equal to user received amount
210
212
  const stakeUpdates = applyTokenUpdates(
@@ -174,7 +174,9 @@ runner.use(
174
174
  receiverStateTokens[address] = new BN(receiverStateTokens[address] || '0').sub(delta).toString();
175
175
  }
176
176
 
177
- const senderUpdates = applyTokenChange({ tokens: senderStateTokens }, senderChange);
177
+ const senderUpdates = senderChange
178
+ ? applyTokenChange({ tokens: senderStateTokens }, senderChange)
179
+ : { tokens: senderStateTokens };
178
180
 
179
181
  const [newSenderState, newReceiverState] = await Promise.all([
180
182
  // Update sender state
@@ -128,7 +128,9 @@ runner.use(
128
128
  receiverTokens[token.address] = new BN(receiverTokens[token.address] || '0').add(delta).toString();
129
129
  }
130
130
 
131
- const senderUpdates = applyTokenChange({ tokens: senderTokens }, senderChange);
131
+ const senderUpdates = senderChange
132
+ ? applyTokenChange({ tokens: senderTokens }, senderChange)
133
+ : { tokens: senderTokens };
132
134
 
133
135
  const [newSenderState, newReceiverState] = await Promise.all([
134
136
  // Update sender state
package/lib/util.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const groupBy = require('lodash/groupBy');
2
2
  const flattenDeep = require('lodash/flattenDeep');
3
3
  const { CustomError: Error } = require('@ocap/util/lib/error');
4
- const { BN } = require('@ocap/util');
4
+ const { BN, fromTokenToUnit } = require('@ocap/util');
5
5
  const { decodeAny } = require('@ocap/message');
6
6
  const { toStakeAddress } = require('@arcblock/did-util');
7
7
  const cloneDeep = require('lodash/cloneDeep');
@@ -313,6 +313,53 @@ const ensureBlockReward = (rollupState, minReward, txStates) => {
313
313
  return result;
314
314
  };
315
315
 
316
+ const isGasStakeAddress = (sender, stakeId) => toStakeAddress(sender, sender) === stakeId;
317
+ const isGasStakeInput = (inputs, token) => {
318
+ if (inputs.length !== 1) {
319
+ return false;
320
+ }
321
+
322
+ const [{ assetsList, tokensList }] = inputs;
323
+ if (assetsList.length > 0) {
324
+ return false;
325
+ }
326
+ if (tokensList.length !== 1) {
327
+ return false;
328
+ }
329
+
330
+ const [tokenInput] = tokensList;
331
+ if (tokenInput.address !== token) {
332
+ return false;
333
+ }
334
+
335
+ return true;
336
+ };
337
+ const isGasStakeValid = (state, config) => {
338
+ if (!state) {
339
+ return false;
340
+ }
341
+
342
+ const { token, transaction } = config;
343
+ const { minStake } = transaction.txGas;
344
+ const expectedMin = fromTokenToUnit(minStake, token.decimal);
345
+
346
+ if (state.tokens) {
347
+ const actualStaked = new BN(state.tokens[token.address] || 0);
348
+ if (actualStaked.gte(expectedMin)) {
349
+ return true;
350
+ }
351
+ }
352
+
353
+ if (state.revokedTokens) {
354
+ const actualRevoked = new BN(state.revokedTokens[token.address] || 0);
355
+ if (actualRevoked.gte(expectedMin)) {
356
+ return true;
357
+ }
358
+ }
359
+
360
+ return false;
361
+ };
362
+
316
363
  module.exports = {
317
364
  decodeAnySafe,
318
365
  applyTokenUpdates,
@@ -324,5 +371,8 @@ module.exports = {
324
371
  ensureBlockReward,
325
372
  getBNSum,
326
373
  isFixedFee,
374
+ isGasStakeAddress,
375
+ isGasStakeInput,
376
+ isGasStakeValid,
327
377
  RATE_BASE,
328
378
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.18.0",
6
+ "version": "1.18.2",
7
7
  "description": "Predefined tx pipeline sets to execute certain type of transactions",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -21,17 +21,18 @@
21
21
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
- "@arcblock/did": "1.18.0",
25
- "@arcblock/did-util": "1.18.0",
26
- "@arcblock/validator": "1.18.0",
27
- "@ocap/asset": "1.18.0",
28
- "@ocap/mcrypto": "1.18.0",
29
- "@ocap/merkle-tree": "1.18.0",
30
- "@ocap/message": "1.18.0",
31
- "@ocap/state": "1.18.0",
32
- "@ocap/tx-pipeline": "1.18.0",
33
- "@ocap/util": "1.18.0",
34
- "@ocap/wallet": "1.18.0",
24
+ "@arcblock/did": "1.18.2",
25
+ "@arcblock/did-util": "1.18.2",
26
+ "@arcblock/jwt": "1.18.2",
27
+ "@arcblock/validator": "1.18.2",
28
+ "@ocap/asset": "1.18.2",
29
+ "@ocap/mcrypto": "1.18.2",
30
+ "@ocap/merkle-tree": "1.18.2",
31
+ "@ocap/message": "1.18.2",
32
+ "@ocap/state": "1.18.2",
33
+ "@ocap/tx-pipeline": "1.18.2",
34
+ "@ocap/util": "1.18.2",
35
+ "@ocap/wallet": "1.18.2",
35
36
  "debug": "^4.3.4",
36
37
  "deep-diff": "^1.0.2",
37
38
  "empty-value": "^1.0.1",
@@ -46,5 +47,5 @@
46
47
  "jest": "^27.5.1",
47
48
  "start-server-and-test": "^1.14.0"
48
49
  },
49
- "gitHead": "c48f928ee4f0deddf0f5e4bcb82fd6ffd7f2bc99"
50
+ "gitHead": "7bd3abefcf732fcf5d4521622ca06db67671d853"
50
51
  }