@ocap/tx-protocols 1.6.3 → 1.6.10

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.
Files changed (52) hide show
  1. package/README.md +36 -0
  2. package/lib/execute.js +97 -30
  3. package/lib/index.js +56 -14
  4. package/lib/protocols/account/declare.js +36 -30
  5. package/lib/protocols/account/delegate.js +78 -40
  6. package/lib/protocols/account/migrate.js +65 -49
  7. package/lib/protocols/account/revoke-delegate.js +39 -23
  8. package/lib/protocols/asset/acquire-v2.js +159 -0
  9. package/lib/protocols/asset/acquire-v3.js +242 -0
  10. package/lib/protocols/asset/calls/README.md +5 -0
  11. package/lib/protocols/asset/calls/transfer-token.js +37 -0
  12. package/lib/protocols/asset/calls/transfer.js +29 -0
  13. package/lib/protocols/asset/create.js +85 -36
  14. package/lib/protocols/asset/mint.js +133 -0
  15. package/lib/protocols/asset/pipes/exec-mint-hook.js +59 -0
  16. package/lib/protocols/asset/pipes/extract-factory-tokens.js +18 -0
  17. package/lib/protocols/asset/pipes/verify-acquire-params.js +30 -0
  18. package/lib/protocols/asset/pipes/verify-itx-address.js +41 -0
  19. package/lib/protocols/asset/pipes/verify-itx-assets.js +49 -0
  20. package/lib/protocols/asset/pipes/verify-itx-variables.js +26 -0
  21. package/lib/protocols/asset/pipes/verify-mint-limit.js +13 -0
  22. package/lib/protocols/asset/update.js +76 -44
  23. package/lib/protocols/factory/create.js +146 -0
  24. package/lib/protocols/governance/claim-stake.js +219 -0
  25. package/lib/protocols/governance/revoke-stake.js +136 -0
  26. package/lib/protocols/governance/stake.js +176 -0
  27. package/lib/protocols/rollup/claim-reward.js +283 -0
  28. package/lib/protocols/rollup/create-block.js +333 -0
  29. package/lib/protocols/rollup/create.js +169 -0
  30. package/lib/protocols/rollup/join.js +156 -0
  31. package/lib/protocols/rollup/leave.js +127 -0
  32. package/lib/protocols/rollup/migrate-contract.js +53 -0
  33. package/lib/protocols/rollup/migrate-token.js +66 -0
  34. package/lib/protocols/rollup/pause.js +52 -0
  35. package/lib/protocols/rollup/pipes/ensure-service-fee.js +37 -0
  36. package/lib/protocols/rollup/pipes/ensure-validator.js +10 -0
  37. package/lib/protocols/rollup/pipes/verify-evidence.js +37 -0
  38. package/lib/protocols/rollup/pipes/verify-paused.js +10 -0
  39. package/lib/protocols/rollup/pipes/verify-signers.js +88 -0
  40. package/lib/protocols/rollup/resume.js +52 -0
  41. package/lib/protocols/rollup/update.js +98 -0
  42. package/lib/protocols/token/create.js +150 -0
  43. package/lib/protocols/token/deposit-v2.js +241 -0
  44. package/lib/protocols/token/withdraw-v2.js +255 -0
  45. package/lib/protocols/trade/exchange-v2.js +179 -0
  46. package/lib/protocols/trade/transfer-v2.js +136 -0
  47. package/lib/protocols/trade/transfer-v3.js +241 -0
  48. package/lib/util.js +325 -2
  49. package/package.json +23 -16
  50. package/lib/protocols/misc/poke.js +0 -106
  51. package/lib/protocols/trade/exchange.js +0 -139
  52. package/lib/protocols/trade/transfer.js +0 -101
package/README.md CHANGED
@@ -49,3 +49,39 @@ pipeline
49
49
  console.error('Transaction execution failed', err.message);
50
50
  });
51
51
  ```
52
+
53
+ ## Misc
54
+
55
+ ### The mysterious `delegator/delegatee`
56
+
57
+ Generally, `delegator` means the party that signed an transaction to allow others to spend his token/asset, and `delegatee` means the party that received that grant.
58
+
59
+ In earlier Forge implementation, the `Transaction.delegator` should be `Transaction.delegatee` because whether it's delegated transaction or not, the `Transaction.from` should always point to the account whose balance will be updated on successful transaction execution.
60
+
61
+ But this will make some transaction protocol logic harder to understand.
62
+
63
+ - `transfer_v2`:
64
+ - balance of `tx.from` will be reduced
65
+ - balance of `tx.delegator` does not change at all
66
+ - `exchange_v2`: a bit more complicated, let's add details for this later
67
+ - `acquire_asset_v2`:
68
+ - balance of `tx.from` will be reduced, as payment for purchasing the asset
69
+ - balance of `tx.delegator` does not change at all
70
+ - owner of minted asset will be set to `tx.delegator`
71
+
72
+ The are some inconsistencies in server and client implementation code.
73
+
74
+ On the server side, `delegator` must be interpreted as `delegatee`, but in `@ocap/client`, `delegator` is interpreted as `delegator`.
75
+ For delegated create-x transactions, the service-fee is charged against `tx.from`.
76
+
77
+ ### How to add a new tx protocol
78
+
79
+ - Define the itx proto in `core/proto/src/tx.proto`
80
+ - Rebuild proto: `cd core/proto && make dep && make build`
81
+ - Enable the protocol in `core/config/lib/default.js` and fix the unit test
82
+ - Create the protocol file in `core/tx-protocols/lib/protocols`
83
+ - Add receipts logic for the protocol at: `core/state/lib/states/tx.js`
84
+ - Add test case for the protocol: both unit and integration tests
85
+ - Add shortcut method in `@ocap/client`
86
+ - Support the protocol in block-explorer if needed: https://github.com/ArcBlock/block-explorer
87
+ - Add use cases for the protocol in ocap-playground: https://github.com/blocklet/ocap-playground
package/lib/execute.js CHANGED
@@ -1,57 +1,124 @@
1
1
  /* eslint-disable consistent-return */
2
+ const get = require('lodash/get');
3
+ const pick = require('lodash/pick');
4
+ const omit = require('lodash/omit');
2
5
  const camelCase = require('lodash/camelCase');
6
+ const Error = require('@ocap/util/lib/error');
3
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
4
8
 
5
9
  const getTxName = (typeUrl) => camelCase(typeUrl.split(':').pop());
6
10
 
11
+ const flushEvents = (context, extra = {}) => {
12
+ (context.events || []).forEach((e) => {
13
+ context.statedb[e.table].emit(e.name, e.data, { ...e.ctx, ...extra });
14
+ });
15
+ context.events = [];
16
+ };
17
+
18
+ const shouldPersistTx = (err) => !err || get(err, 'props.persist');
19
+
7
20
  module.exports = ({ filter, runAsLambda }) => {
8
21
  // pipeline before executing the transaction
9
22
  const pre = new Runner();
10
23
  pre.use(pipes.DecodeTx);
11
- pre.use(pipes.VerifyTx);
12
- pre.use(pipes.VerifyReplay({ filter, key: 'txHash' }));
13
24
  pre.use(pipes.DecodeItx);
25
+ pre.use(pipes.VerifyTx);
26
+ pre.use(pipes.VerifyServiceFee);
27
+
28
+ pre.use(async (context, next) => {
29
+ if (filter.has(context.txHash)) {
30
+ // Since bloom-filters may give false positive, we need to check for statedb if replay detected
31
+ const exist = await context.statedb.tx.get(context.txHash, context);
32
+ if (exist) {
33
+ return next(new Error('DUPLICATE_TX', 'Can not execute duplicate transaction'));
34
+ }
35
+ }
36
+
37
+ filter.add(context.txHash);
38
+ return next();
39
+ });
40
+
14
41
  pre.use(pipes.VerifyTxSize);
15
42
  pre.use(pipes.VerifySignature);
16
43
 
17
- // pipeline after executing the transaction
18
- const post = new Runner();
19
- post.use(async (context, next) => {
20
- // TODO: do we want to save failed transactions here, if we do, just attach an error handling pipe
21
- const tx = context.states.tx.create(context);
22
- await context.statedb.tx.create(tx.hash, tx, context);
44
+ const execute = async (context, protocols, isRetrySupported = false) =>
45
+ new Promise((resolve, reject) => {
46
+ pre.run(context, (err) => {
47
+ if (err) {
48
+ if (process.env.NODE_ENV !== 'test') {
49
+ console.error('Failed to prepare transaction', err);
50
+ }
51
+ return reject(err);
52
+ }
23
53
 
24
- next();
25
- });
54
+ const name = getTxName(context.txType);
55
+ const protocol = protocols[name];
56
+ if (!protocol) {
57
+ return reject(new Error('UNSUPPORTED_TX', `Unsupported tx type ${context.txType}`));
58
+ }
26
59
 
27
- const execute = async (context, protocols) => {
28
- await pre.runAsync(context);
60
+ // eslint-disable-next-line no-shadow
61
+ protocol.run(context, async (err) => {
62
+ // we should only flush events when retry is not supported
63
+ // otherwise the outer caller should handle these 2
64
+ if (shouldPersistTx(err) && isRetrySupported === false) {
65
+ const txState = context.states.tx.create(context, err ? err.code || 'INTERNAL' : 'OK');
66
+ try {
67
+ await context.statedb.tx.create(txState.hash, txState, context);
68
+ } catch (e) {
69
+ console.error('Failed to save transaction to statedb', e);
70
+ }
29
71
 
30
- const name = getTxName(context.txType);
31
- const protocol = protocols[name];
32
- if (!protocol) {
33
- throw new Error('UNSUPPORTED_TX', `Unsupported tx type ${context.txType}`);
34
- }
72
+ flushEvents(context, { txState });
73
+ }
35
74
 
36
- await protocol.runAsync(context);
37
- await post.runAsync(context);
75
+ // after executing the transaction
76
+ if (err) {
77
+ // console.error('Failed to execute transaction', err);
78
+ return reject(err);
79
+ }
38
80
 
39
- return context;
40
- };
81
+ resolve(context);
82
+ });
83
+ });
84
+ });
41
85
 
42
86
  if (typeof runAsLambda === 'function') {
43
- return (context, protocols) =>
44
- new Promise((resolve, reject) => {
45
- runAsLambda(async (txn) => {
46
- Object.defineProperty(context, 'txn', { value: txn });
87
+ return async (context, protocols) => {
88
+ let ctx = null;
89
+ let error = null;
90
+
91
+ try {
92
+ await runAsLambda((txn) => {
93
+ // create a new context each time in case we are retrying
94
+ ctx = pick(context, ['txBase64', 'statedb', 'config', 'states']);
95
+ Object.defineProperty(ctx, 'txn', { value: txn });
96
+ return execute(ctx, protocols, true);
97
+ });
98
+ } catch (err) {
99
+ error = err;
100
+ } finally {
101
+ if (shouldPersistTx(error)) {
47
102
  try {
48
- await execute(context, protocols);
49
- resolve(context);
103
+ const txState = context.states.tx.create(ctx, error ? error.code || 'INTERNAL' : 'OK');
104
+ flushEvents(ctx, { txState });
105
+ await runAsLambda(async (txn) => {
106
+ const newCtx = { ...omit(ctx, ['txn']), txn };
107
+ await context.statedb.tx.create(txState.hash, txState, newCtx);
108
+ flushEvents(newCtx, { txState });
109
+ });
50
110
  } catch (err) {
51
- reject(err);
111
+ console.error('failed to save invalid transaction to statedb', err);
52
112
  }
53
- });
54
- });
113
+ }
114
+ }
115
+
116
+ if (error) {
117
+ throw error;
118
+ }
119
+
120
+ return ctx;
121
+ };
55
122
  }
56
123
 
57
124
  return execute;
package/lib/index.js CHANGED
@@ -1,26 +1,44 @@
1
- const EventEmitter = require('events');
2
1
  const states = require('@ocap/state');
3
2
 
4
- const transfer = require('./protocols/trade/transfer');
5
- const exchange = require('./protocols/trade/exchange');
3
+ const transferV2 = require('./protocols/trade/transfer-v2');
4
+ const transferV3 = require('./protocols/trade/transfer-v3');
5
+ const exchangeV2 = require('./protocols/trade/exchange-v2');
6
6
  const declare = require('./protocols/account/declare');
7
7
  const migrate = require('./protocols/account/migrate');
8
8
  const delegate = require('./protocols/account/delegate');
9
9
  const revokeDelegate = require('./protocols/account/revoke-delegate');
10
10
  const createAsset = require('./protocols/asset/create');
11
11
  const updateAsset = require('./protocols/asset/update');
12
- const poke = require('./protocols/misc/poke');
12
+ const createFactory = require('./protocols/factory/create');
13
+ const acquireAssetV2 = require('./protocols/asset/acquire-v2');
14
+ const acquireAssetV3 = require('./protocols/asset/acquire-v3');
15
+ const mintAsset = require('./protocols/asset/mint');
16
+ const createToken = require('./protocols/token/create');
17
+ const stake = require('./protocols/governance/stake');
18
+ const revokeStake = require('./protocols/governance/revoke-stake');
19
+ const claimStake = require('./protocols/governance/claim-stake');
20
+ const depositTokenV2 = require('./protocols/token/deposit-v2');
21
+ const withdrawTokenV2 = require('./protocols/token/withdraw-v2');
22
+ const createRollup = require('./protocols/rollup/create');
23
+ const updateRollup = require('./protocols/rollup/update');
24
+ const joinRollup = require('./protocols/rollup/join');
25
+ const leaveRollup = require('./protocols/rollup/leave');
26
+ const pauseRollup = require('./protocols/rollup/pause');
27
+ const resumeRollup = require('./protocols/rollup/resume');
28
+ const createRollupBlock = require('./protocols/rollup/create-block');
29
+ const claimBlockReward = require('./protocols/rollup/claim-reward');
30
+ const migrateRollupContract = require('./protocols/rollup/migrate-contract');
31
+ const migrateRollupToken = require('./protocols/rollup/migrate-token');
13
32
 
14
33
  const executor = require('./execute');
15
34
 
16
35
  const createExecutor = ({ filter, runAsLambda }) => {
17
- const events = new EventEmitter();
18
- const emit = (...args) => events.emit(...args);
19
-
20
36
  const protocols = {
21
37
  // trade
22
- transfer,
23
- exchange,
38
+ transfer: transferV2,
39
+ transferV2,
40
+ transferV3,
41
+ exchangeV2,
24
42
 
25
43
  // account
26
44
  declare,
@@ -31,18 +49,42 @@ const createExecutor = ({ filter, runAsLambda }) => {
31
49
  // asset
32
50
  createAsset,
33
51
  updateAsset,
52
+ acquireAssetV2,
53
+ acquireAssetV3,
54
+ mintAsset,
55
+
56
+ // factory
57
+ createFactory,
58
+
59
+ // token
60
+ createToken,
61
+ depositTokenV2,
62
+ withdrawTokenV2,
34
63
 
35
- // misc
36
- poke,
64
+ // governance
65
+ stake,
66
+ revokeStake,
67
+ claimStake,
68
+
69
+ // rollup
70
+ createRollup,
71
+ updateRollup,
72
+ joinRollup,
73
+ leaveRollup,
74
+ pauseRollup,
75
+ resumeRollup,
76
+ createRollupBlock,
77
+ claimBlockReward,
78
+ migrateRollupContract,
79
+ migrateRollupToken,
37
80
  };
38
81
 
39
82
  const execute = executor({ filter, runAsLambda });
40
83
 
41
- return Object.assign(events, {
84
+ return {
42
85
  ...protocols,
43
86
 
44
87
  execute: (context, done) => {
45
- Object.defineProperty(context, 'emit', { value: emit });
46
88
  Object.defineProperty(context, 'states', { value: states });
47
89
 
48
90
  if (typeof done === 'function') {
@@ -53,7 +95,7 @@ const createExecutor = ({ filter, runAsLambda }) => {
53
95
 
54
96
  return execute(context, protocols);
55
97
  },
56
- });
98
+ };
57
99
  };
58
100
 
59
101
  // default in-memory tx-hash-filter
@@ -1,28 +1,28 @@
1
- const isEmpty = require('empty-value');
1
+ const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@arcblock/validator');
2
3
  const { Runner, pipes } = require('@ocap/tx-pipeline');
3
4
  const { account } = require('@ocap/state');
4
5
  const { toBase58 } = require('@ocap/util');
5
6
 
6
- // eslint-disable-next-line global-require
7
- const debug = require('debug')(`${require('../../../package.json').name}:declare`);
8
-
9
7
  const runner = new Runner();
10
8
 
11
9
  runner.use(pipes.VerifyMultiSig(0));
12
10
 
13
- // Verify itx
14
- runner.use(
15
- pipes.VerifyInfo([
16
- {
17
- error: 'INSUFFICIENT_DATA',
18
- fn: ({ itx }) => !isEmpty(itx.moniker),
19
- },
20
- {
21
- error: 'INVALID_MONIKER',
22
- fn: ({ itx }) => /^[a-zA-Z0-9][-a-zA-Z0-9_]{2,40}$/.test(itx.moniker),
23
- },
24
- ])
25
- );
11
+ // verify itx
12
+ const schema = Joi.object({
13
+ issuer: Joi.DID().optional().allow(''),
14
+ moniker: Joi.string()
15
+ .regex(/^[a-zA-Z0-9][-a-zA-Z0-9_]{2,40}$/)
16
+ .required(),
17
+ data: Joi.any().optional(),
18
+ }).options({ stripUnknown: true, noDefaults: false });
19
+ runner.use(({ itx }, next) => {
20
+ const { error } = schema.validate(itx);
21
+ if (error) {
22
+ return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
23
+ }
24
+ return next();
25
+ });
26
26
 
27
27
  // Ensure sender does not exist
28
28
  runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState' }));
@@ -33,18 +33,24 @@ runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
33
33
  runner.use(pipes.ExtractState({ from: 'itx.issuer', to: 'issuerState', status: 'INVALID_TX' }));
34
34
 
35
35
  // Create account state
36
- runner.use(async (context, next) => {
37
- const { tx, itx, statedb } = context;
38
-
39
- const attrs = { address: tx.from, pk: toBase58(tx.pk), ...itx };
40
- const state = account.create(attrs, context);
41
- context.senderState = state;
42
-
43
- debug('attrs', attrs);
44
-
45
- await statedb.account.create(tx.from, state, context);
46
-
47
- next();
48
- });
36
+ runner.use(
37
+ async (context, next) => {
38
+ const { tx, itx, statedb } = context;
39
+ const tokens = { [context.config.token.address]: '0' };
40
+
41
+ const [senderState] = await Promise.all([
42
+ statedb.account.create(
43
+ tx.from,
44
+ account.create({ address: tx.from, pk: toBase58(tx.pk), nonce: tx.nonce, tokens, ...itx }, context),
45
+ context
46
+ ),
47
+ ]);
48
+
49
+ context.senderState = senderState;
50
+
51
+ next();
52
+ },
53
+ { persistError: true }
54
+ );
49
55
 
50
56
  module.exports = runner;
@@ -1,5 +1,7 @@
1
+ /* eslint-disable max-len */
1
2
  const get = require('lodash/get');
2
- const isEmpty = require('empty-value');
3
+ const Joi = require('@arcblock/validator');
4
+ const Error = require('@ocap/util/lib/error');
3
5
  const getListField = require('@ocap/util/lib/get-list-field');
4
6
  const { delegation, account } = require('@ocap/state');
5
7
  const { Runner, pipes } = require('@ocap/tx-pipeline');
@@ -12,6 +14,27 @@ const runner = new Runner();
12
14
 
13
15
  runner.use(pipes.VerifyMultiSig(0));
14
16
 
17
+ // verify itx
18
+ const schema = Joi.object({
19
+ address: Joi.DID().role('ROLE_DELEGATION').required(),
20
+ to: Joi.DID().required(),
21
+ opsList: Joi.array()
22
+ .items(
23
+ Joi.object({
24
+ typeUrl: Joi.string().required(),
25
+ })
26
+ )
27
+ .min(1)
28
+ .required(),
29
+ data: Joi.any().optional(),
30
+ }).options({ stripUnknown: true, noDefaults: false });
31
+ runner.use(({ itx }, next) => {
32
+ const { error } = schema.validate(itx);
33
+ if (error) {
34
+ return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
35
+ }
36
+ return next();
37
+ });
15
38
  runner.use(
16
39
  pipes.VerifyInfo([
17
40
  {
@@ -21,11 +44,6 @@ runner.use(
21
44
  return true;
22
45
  },
23
46
  },
24
- {
25
- error: 'INSUFFICIENT_DATA',
26
- message: 'itx.address, itx.to and itx.ops should not be empty',
27
- fn: ({ itx, ops }) => !isEmpty(itx.address) && !isEmpty(itx.to) && ops.length > 0,
28
- },
29
47
  {
30
48
  error: 'INVALID_TX',
31
49
  message: 'Delegation address does not match',
@@ -44,43 +62,63 @@ runner.use(
44
62
  );
45
63
 
46
64
  // Ensure sender/receiver exist
47
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
65
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
48
66
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
49
- runner.use(pipes.ExtractState({ from: 'itx.to', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' }));
50
-
51
- // Create account state, and update old accounts
52
- runner.use(async (context, next) => {
53
- const { tx, itx, ops, statedb, senderState } = context;
54
-
55
- const opsObj = ops.reduce((acc, x) => {
56
- acc[x.typeUrl] = {
57
- balance: 0,
58
- numTxs: 0,
59
- rule: true, // TODO: support join x.rules to an expression
60
- };
61
- return acc;
62
- }, {});
63
-
64
- const exist = await statedb.delegation.get(itx.address, context);
65
- let state = null;
66
- if (exist) {
67
- state = delegation.update(exist, { ...itx, ops: opsObj }, context);
68
- debug('update', { from: tx.from, to: itx.to, state });
69
- await statedb.delegation.update(itx.address, state, context);
70
- } else {
71
- state = delegation.create({ ...itx, ops: opsObj }, context);
72
- debug('create', { from: tx.from, to: itx.to, state });
73
- await statedb.delegation.create(itx.address, state, context);
74
- }
67
+ runner.use(pipes.ExtractState({ from: 'itx.to', to: 'receiverState', status: 'OK', table: 'account' }));
75
68
 
76
- const newSenderState = account.update(senderState, { nonce: tx.nonce }, context);
77
- await statedb.account.update(senderState.address, newSenderState, context);
69
+ // Extract delegation
70
+ runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', table: 'delegation', status: 'OK' }));
78
71
 
79
- // Update context
80
- context.delegationState = state;
81
- context.senderState = newSenderState;
72
+ // Create delegation state
73
+ runner.use(
74
+ async (context, next) => {
75
+ const { tx, itx, ops, statedb, senderState, receiverState, delegationState } = context;
82
76
 
83
- return next();
84
- });
77
+ const opsObj = ops.reduce(
78
+ (acc, x) => {
79
+ acc[x.typeUrl] = {
80
+ balance: 0,
81
+ numTxs: 0,
82
+ // TODO: 可以考虑结合现有的 DSL 去实现
83
+ rule: true,
84
+ };
85
+ return acc;
86
+ },
87
+ delegationState ? delegationState.ops : {}
88
+ );
89
+
90
+ const sender = senderState ? senderState.address : tx.from;
91
+ const receiver = receiverState ? receiverState.address : itx.to;
92
+
93
+ const [newSenderState, newReceiverState, newDelegationState] = await Promise.all([
94
+ // update sender
95
+ statedb.account.updateOrCreate(
96
+ senderState,
97
+ account.updateOrCreate(senderState, { address: sender, nonce: tx.nonce, pk: tx.pk }, context),
98
+ context
99
+ ),
100
+
101
+ // update receiver
102
+ receiverState
103
+ ? Promise.resolve(receiverState)
104
+ : statedb.account.create(receiver, account.create({ address: receiver }, context), context),
105
+
106
+ // update delegation
107
+ delegationState
108
+ ? statedb.delegation.update(itx.address, delegation.update(delegationState, { ...itx, ops: opsObj }, context), context) // prettier-ignore
109
+ : statedb.delegation.create(itx.address, delegation.create({ ...itx, ops: opsObj }, context), context),
110
+ ]);
111
+
112
+ debug(delegationState ? 'update' : 'create', newDelegationState);
113
+
114
+ // Update context
115
+ context.senderState = newSenderState;
116
+ context.receiverState = newReceiverState;
117
+ context.delegationState = newDelegationState;
118
+
119
+ return next();
120
+ },
121
+ { persistError: true }
122
+ );
85
123
 
86
124
  module.exports = runner;
@@ -1,5 +1,7 @@
1
- const isEmpty = require('empty-value');
2
- const { Runner, Error, pipes } = require('@ocap/tx-pipeline');
1
+ const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@arcblock/validator');
3
+ const getRelatedAddresses = require('@ocap/util/lib/get-related-addr');
4
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
3
5
  const { fromPublicKey, toTypeInfo } = require('@arcblock/did');
4
6
  const { account } = require('@ocap/state');
5
7
 
@@ -10,13 +12,21 @@ const runner = new Runner();
10
12
 
11
13
  runner.use(pipes.VerifyMultiSig(0));
12
14
 
13
- // Verify itx
15
+ // verify itx
16
+ const schema = Joi.object({
17
+ address: Joi.DID().required(),
18
+ pk: Joi.any().required(),
19
+ data: Joi.any().optional(),
20
+ }).options({ stripUnknown: true, noDefaults: false });
21
+ runner.use(({ itx }, next) => {
22
+ const { error } = schema.validate(itx);
23
+ if (error) {
24
+ return next(new Error('INVALID_TX', `Invalid itx: ${error.message}`));
25
+ }
26
+ return next();
27
+ });
14
28
  runner.use(
15
29
  pipes.VerifyInfo([
16
- {
17
- error: 'INSUFFICIENT_DATA',
18
- fn: ({ itx }) => !isEmpty(itx.pk) && !isEmpty(itx.address),
19
- },
20
30
  {
21
31
  error: 'INVALID_RECEIVER_STATE',
22
32
  message: 'Receiver pk and address does not match',
@@ -26,57 +36,63 @@ runner.use(
26
36
  );
27
37
 
28
38
  // Ensure sender exist
29
- runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE' }));
39
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
30
40
  runner.use(pipes.VerifyAccountMigration({ senderKey: 'senderState' }));
31
41
 
32
42
  // Create account state, and update old accounts
33
- runner.use(async (context, next) => {
34
- const { tx, itx, statedb } = context;
43
+ runner.use(
44
+ async (context, next) => {
45
+ const { tx, itx, statedb } = context;
35
46
 
36
- const sender = context.senderState;
37
- const receiver = await statedb.account.get(itx.address, context);
47
+ const sender = context.senderState;
48
+ const receiver = await statedb.account.get(itx.address, context);
38
49
 
39
- // Ensure receiver does not exist
40
- if (receiver) {
41
- return next(new Error('INVALID_SENDER_STATE', 'Can not migrate to an existing account'));
42
- }
50
+ // Ensure receiver does not exist
51
+ if (receiver) {
52
+ return next(new Error('INVALID_RECEIVER_STATE', 'Can not migrate to an existing account'));
53
+ }
43
54
 
44
- // Create new account
45
- const newAccount = account.create(
46
- {
47
- ...sender, // copy all old account data to new account
48
- address: itx.address,
49
- pk: itx.pk,
50
- migratedFrom: [sender.address],
51
- },
52
- context
53
- );
54
- debug('new account', newAccount);
55
- await statedb.account.create(itx.address, newAccount, context);
55
+ // Create new account
56
+ const newAccount = await statedb.account.create(
57
+ itx.address,
58
+ account.create(
59
+ {
60
+ ...sender, // copy all old account data to new account
61
+ address: itx.address,
62
+ pk: itx.pk,
63
+ migratedFrom: [sender.address],
64
+ },
65
+ context
66
+ ),
67
+ context
68
+ );
69
+ debug('new account', newAccount);
56
70
 
57
- // Update old accounts
58
- const addresses = account.getRelatedAddresses(sender);
59
- const states = await Promise.all(
60
- addresses.map(async (address) => {
61
- let state = null;
62
- if (address === sender.address) {
63
- state = account.update(sender, { migratedTo: itx.address, nonce: tx.nonce }, context);
64
- } else {
65
- const old = await statedb.account.get(address, { final: false, ...context });
66
- state = account.update(old, { migratedTo: itx.address }, context);
67
- }
71
+ // Update old accounts
72
+ const addresses = getRelatedAddresses(sender);
73
+ const states = await Promise.all(
74
+ addresses.map(async (address) => {
75
+ let state = null;
76
+ if (address === sender.address) {
77
+ state = account.update(sender, { migratedTo: itx.address, nonce: tx.nonce }, context);
78
+ } else {
79
+ const old = await statedb.account.get(address, { final: false, ...context });
80
+ state = account.update(old, { migratedTo: itx.address }, context);
81
+ }
68
82
 
69
- await statedb.account.update(address, state, context);
70
- return state;
71
- })
72
- );
73
- debug('old accounts', states);
83
+ await statedb.account.update(address, state, context);
84
+ return state;
85
+ })
86
+ );
87
+ debug('old accounts', states);
74
88
 
75
- // Update context
76
- context.receiverState = newAccount;
77
- context.senderState = states.find((x) => x.address === sender.from);
89
+ // Update context
90
+ context.receiverState = newAccount;
91
+ context.senderState = states.find((x) => x.address === sender.from);
78
92
 
79
- return next();
80
- });
93
+ return next();
94
+ },
95
+ { persistError: true }
96
+ );
81
97
 
82
98
  module.exports = runner;