@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.
- package/README.md +36 -0
- package/lib/execute.js +97 -30
- package/lib/index.js +56 -14
- package/lib/protocols/account/declare.js +36 -30
- package/lib/protocols/account/delegate.js +78 -40
- package/lib/protocols/account/migrate.js +65 -49
- package/lib/protocols/account/revoke-delegate.js +39 -23
- package/lib/protocols/asset/acquire-v2.js +159 -0
- package/lib/protocols/asset/acquire-v3.js +242 -0
- package/lib/protocols/asset/calls/README.md +5 -0
- package/lib/protocols/asset/calls/transfer-token.js +37 -0
- package/lib/protocols/asset/calls/transfer.js +29 -0
- package/lib/protocols/asset/create.js +85 -36
- package/lib/protocols/asset/mint.js +133 -0
- package/lib/protocols/asset/pipes/exec-mint-hook.js +59 -0
- package/lib/protocols/asset/pipes/extract-factory-tokens.js +18 -0
- package/lib/protocols/asset/pipes/verify-acquire-params.js +30 -0
- package/lib/protocols/asset/pipes/verify-itx-address.js +41 -0
- package/lib/protocols/asset/pipes/verify-itx-assets.js +49 -0
- package/lib/protocols/asset/pipes/verify-itx-variables.js +26 -0
- package/lib/protocols/asset/pipes/verify-mint-limit.js +13 -0
- package/lib/protocols/asset/update.js +76 -44
- package/lib/protocols/factory/create.js +146 -0
- package/lib/protocols/governance/claim-stake.js +219 -0
- package/lib/protocols/governance/revoke-stake.js +136 -0
- package/lib/protocols/governance/stake.js +176 -0
- package/lib/protocols/rollup/claim-reward.js +283 -0
- package/lib/protocols/rollup/create-block.js +333 -0
- package/lib/protocols/rollup/create.js +169 -0
- package/lib/protocols/rollup/join.js +156 -0
- package/lib/protocols/rollup/leave.js +127 -0
- package/lib/protocols/rollup/migrate-contract.js +53 -0
- package/lib/protocols/rollup/migrate-token.js +66 -0
- package/lib/protocols/rollup/pause.js +52 -0
- package/lib/protocols/rollup/pipes/ensure-service-fee.js +37 -0
- package/lib/protocols/rollup/pipes/ensure-validator.js +10 -0
- package/lib/protocols/rollup/pipes/verify-evidence.js +37 -0
- package/lib/protocols/rollup/pipes/verify-paused.js +10 -0
- package/lib/protocols/rollup/pipes/verify-signers.js +88 -0
- package/lib/protocols/rollup/resume.js +52 -0
- package/lib/protocols/rollup/update.js +98 -0
- package/lib/protocols/token/create.js +150 -0
- package/lib/protocols/token/deposit-v2.js +241 -0
- package/lib/protocols/token/withdraw-v2.js +255 -0
- package/lib/protocols/trade/exchange-v2.js +179 -0
- package/lib/protocols/trade/transfer-v2.js +136 -0
- package/lib/protocols/trade/transfer-v3.js +241 -0
- package/lib/util.js +325 -2
- package/package.json +23 -16
- package/lib/protocols/misc/poke.js +0 -106
- package/lib/protocols/trade/exchange.js +0 -139
- 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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
if (!protocol) {
|
|
33
|
-
throw new Error('UNSUPPORTED_TX', `Unsupported tx type ${context.txType}`);
|
|
34
|
-
}
|
|
72
|
+
flushEvents(context, { txState });
|
|
73
|
+
}
|
|
35
74
|
|
|
36
|
-
|
|
37
|
-
|
|
75
|
+
// after executing the transaction
|
|
76
|
+
if (err) {
|
|
77
|
+
// console.error('Failed to execute transaction', err);
|
|
78
|
+
return reject(err);
|
|
79
|
+
}
|
|
38
80
|
|
|
39
|
-
|
|
40
|
-
|
|
81
|
+
resolve(context);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
41
85
|
|
|
42
86
|
if (typeof runAsLambda === 'function') {
|
|
43
|
-
return (context, protocols) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
|
5
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
36
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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: '
|
|
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: '
|
|
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
|
-
|
|
77
|
-
|
|
69
|
+
// Extract delegation
|
|
70
|
+
runner.use(pipes.ExtractState({ from: 'itx.address', to: 'delegationState', table: 'delegation', status: 'OK' }));
|
|
78
71
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
context
|
|
72
|
+
// Create delegation state
|
|
73
|
+
runner.use(
|
|
74
|
+
async (context, next) => {
|
|
75
|
+
const { tx, itx, ops, statedb, senderState, receiverState, delegationState } = context;
|
|
82
76
|
|
|
83
|
-
|
|
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
|
|
2
|
-
const
|
|
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
|
-
//
|
|
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(
|
|
34
|
-
|
|
43
|
+
runner.use(
|
|
44
|
+
async (context, next) => {
|
|
45
|
+
const { tx, itx, statedb } = context;
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
47
|
+
const sender = context.senderState;
|
|
48
|
+
const receiver = await statedb.account.get(itx.address, context);
|
|
38
49
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
await statedb.account.update(address, state, context);
|
|
84
|
+
return state;
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
debug('old accounts', states);
|
|
74
88
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
// Update context
|
|
90
|
+
context.receiverState = newAccount;
|
|
91
|
+
context.senderState = states.find((x) => x.address === sender.from);
|
|
78
92
|
|
|
79
|
-
|
|
80
|
-
}
|
|
93
|
+
return next();
|
|
94
|
+
},
|
|
95
|
+
{ persistError: true }
|
|
96
|
+
);
|
|
81
97
|
|
|
82
98
|
module.exports = runner;
|