@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
@@ -0,0 +1,127 @@
1
+ /* eslint-disable indent */
2
+ const MerkleTree = require('@ocap/merkle-tree');
3
+ const Error = require('@ocap/util/lib/error');
4
+ const Joi = require('@arcblock/validator');
5
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
6
+ const { account, rollup, stake, evidence } = require('@ocap/state');
7
+ const { toStakeAddress } = require('@arcblock/did-util');
8
+
9
+ // eslint-disable-next-line global-require
10
+ const debug = require('debug')(`${require('../../../package.json').name}:leave-rollup`);
11
+
12
+ const VerifySigners = require('./pipes/verify-signers');
13
+ const VerifyEvidence = require('./pipes/verify-evidence');
14
+ const VerifyPaused = require('./pipes/verify-paused');
15
+
16
+ const runner = new Runner();
17
+
18
+ // 1. verify itx
19
+ const schema = Joi.object({
20
+ rollup: Joi.DID().role('ROLE_ROLLUP').required(),
21
+ evidence: Joi.object({
22
+ hash: Joi.string().regex(Joi.patterns.txHash).required(),
23
+ }).required(),
24
+ signaturesList: Joi.schemas.multiSig.min(1).required(),
25
+ data: Joi.any().optional(),
26
+ }).options({ stripUnknown: true, noDefaults: false });
27
+ runner.use(({ itx }, next) => {
28
+ const { error } = schema.validate(itx);
29
+ return next(error ? new Error('INVALID_TX', `Invalid itx: ${error.message}`) : null);
30
+ });
31
+
32
+ // 2. verify rollup
33
+ runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
34
+ runner.use(VerifyPaused());
35
+
36
+ // 3. verify evidence signers, signatures, state
37
+ runner.use((context, next) => {
38
+ context.validatorsHash = MerkleTree.getListHash([context.tx.from]);
39
+ return next();
40
+ });
41
+ runner.use(VerifySigners({ signersKey: 'itx.signaturesList', allowSender: false, allowShrink: true }));
42
+ runner.use(
43
+ VerifyEvidence({ evidenceKey: 'validatorsHash', signaturesKey: 'itx.signaturesList', verifyMethod: 'ethVerify' })
44
+ );
45
+ runner.use(pipes.ExtractState({ from: 'itx.evidence.hash', to: 'evidenceState', status: 'OK', table: 'evidence' }));
46
+ runner.use(({ evidenceState }, next) => {
47
+ if (evidenceState) return next(new Error('INVALID_TX', 'Leave evidence already seen on this chain'));
48
+ return next();
49
+ });
50
+
51
+ // 4. verify tx signers and signatures
52
+ runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: false, allowShrink: true }));
53
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
54
+
55
+ // 5. verify validator
56
+ runner.use((context, next) => {
57
+ const { tx, rollupState } = context;
58
+ if (rollupState.seedValidators.some((x) => x.address === tx.from)) {
59
+ return next(new Error('INVALID_TX', `Address ${tx.from} can not leave since it's seed validator`));
60
+ }
61
+
62
+ if (rollupState.validators.some((x) => x.address === tx.from) === false) {
63
+ return next(new Error('INVALID_TX', `Address ${tx.from} not exist in validator whitelist`));
64
+ }
65
+
66
+ return next();
67
+ });
68
+
69
+ // 6. verify staking: get address, extract state
70
+ runner.use((context, next) => {
71
+ const { tx, itx } = context;
72
+ context.stakeAddress = toStakeAddress(tx.from, itx.rollup);
73
+ return next();
74
+ });
75
+ runner.use(
76
+ pipes.ExtractState({ from: 'stakeAddress', to: 'stakeState', status: 'INVALID_STAKE_STATE', table: 'stake' })
77
+ );
78
+
79
+ // 7. verify sender state
80
+ runner.use(
81
+ pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
82
+ );
83
+ runner.use(
84
+ pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })
85
+ );
86
+ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
87
+
88
+ // 8. update state
89
+ runner.use(
90
+ async (context, next) => {
91
+ const { tx, itx, rollupState, statedb, senderState, stakeState, stakeAddress } = context;
92
+ const newValidators = rollupState.validators.filter((x) => x.address !== tx.from);
93
+
94
+ const [newSenderState, newRollupState, newStakeState, evidenceState] = await Promise.all([
95
+ statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
96
+
97
+ // persist new validators list
98
+ statedb.rollup.update(
99
+ rollupState.address,
100
+ rollup.update(rollupState, { validators: newValidators }, context),
101
+ context
102
+ ),
103
+
104
+ // unlock the stake from the validator
105
+ statedb.stake.update(stakeAddress, stake.update(stakeState, { revocable: true }, context), context),
106
+
107
+ // Create evidence state
108
+ statedb.evidence.create(
109
+ itx.evidence.hash,
110
+ evidence.create({ hash: itx.evidence.hash, data: 'rollup-leave' }, context),
111
+ context
112
+ ),
113
+ ]);
114
+
115
+ context.senderState = newSenderState;
116
+ context.rollupState = newRollupState;
117
+ context.stakeState = newStakeState;
118
+ context.evidenceState = evidenceState;
119
+
120
+ debug('leave-rollup', newValidators);
121
+
122
+ next();
123
+ },
124
+ { persistError: true }
125
+ );
126
+
127
+ module.exports = runner;
@@ -0,0 +1,53 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@arcblock/validator');
3
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
+ const { account, rollup } = require('@ocap/state');
5
+
6
+ const VerifySigners = require('./pipes/verify-signers');
7
+ const EnsureValidator = require('./pipes/ensure-validator');
8
+
9
+ const runner = new Runner();
10
+
11
+ // 1. verify itx
12
+ const schema = Joi.object({
13
+ rollup: Joi.DID().role('ROLE_ROLLUP').required(),
14
+ to: Joi.DID().wallet('ethereum').required(),
15
+ data: Joi.any().optional(),
16
+ }).options({ stripUnknown: true, noDefaults: false });
17
+ runner.use(({ itx }, next) => {
18
+ const { error } = schema.validate(itx);
19
+ return next(error ? new Error('INVALID_TX', `Invalid itx: ${error.message}`) : null);
20
+ });
21
+
22
+ // 2. verify rollup
23
+ runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
24
+ runner.use(EnsureValidator(true));
25
+
26
+ // 3. verify tx signers and signatures
27
+ runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
28
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
29
+
30
+ // 4. verify sender and signer states
31
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
32
+ runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
33
+ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
34
+
35
+ // 5. update rollup state
36
+ runner.use(
37
+ async (context, next) => {
38
+ const { tx, itx, rollupState, statedb, senderState } = context;
39
+
40
+ const [newSenderState, newRollupState] = await Promise.all([
41
+ statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
42
+ statedb.rollup.update(itx.rollup, rollup.migrate(rollupState, itx.to, context), context),
43
+ ]);
44
+
45
+ context.senderState = newSenderState;
46
+ context.rollupState = newRollupState;
47
+
48
+ next();
49
+ },
50
+ { persistError: true }
51
+ );
52
+
53
+ module.exports = runner;
@@ -0,0 +1,66 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@arcblock/validator');
3
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
+ const { account } = require('@ocap/state');
5
+
6
+ const VerifySigners = require('./pipes/verify-signers');
7
+ const EnsureValidator = require('./pipes/ensure-validator');
8
+
9
+ const runner = new Runner();
10
+
11
+ // 1. verify itx
12
+ const schema = Joi.object({
13
+ rollup: Joi.DID().role('ROLE_ROLLUP').required(),
14
+ from: Joi.DID().wallet('ethereum').required(),
15
+ to: Joi.DID().wallet('ethereum').required(),
16
+ token: Joi.object({
17
+ address: Joi.DID().wallet('ethereum').required(),
18
+ value: Joi.BN().positive().required(),
19
+ }),
20
+ data: Joi.any().optional(),
21
+ }).options({ stripUnknown: true, noDefaults: false });
22
+ runner.use(({ itx }, next) => {
23
+ const { error } = schema.validate(itx);
24
+ return next(error ? new Error('INVALID_TX', `Invalid itx: ${error.message}`) : null);
25
+ });
26
+
27
+ // 2. verify rollup
28
+ runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
29
+ runner.use(EnsureValidator(true));
30
+ runner.use(({ itx, rollupState }, next) => {
31
+ if (rollupState.migrateHistory.includes(itx.from) === false) {
32
+ return next(new Error('INVALID_MIGRATE_ATTEMPT', 'itx.from must exist in rollup migrate history'));
33
+ }
34
+ if (itx.to !== rollupState.contractAddress) {
35
+ return next(new Error('INVALID_MIGRATE_ATTEMPT', 'itx.to must equal to latest rollup contract address'));
36
+ }
37
+
38
+ return next();
39
+ });
40
+
41
+ // 3. verify tx signers and signatures
42
+ runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
43
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
44
+
45
+ // 4. verify sender and signer states
46
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
47
+ runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
48
+ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
49
+
50
+ // 5. update state
51
+ runner.use(
52
+ async (context, next) => {
53
+ const { tx, statedb, senderState } = context;
54
+
55
+ const [newSenderState] = await Promise.all([
56
+ statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
57
+ ]);
58
+
59
+ context.senderState = newSenderState;
60
+
61
+ next();
62
+ },
63
+ { persistError: true }
64
+ );
65
+
66
+ module.exports = runner;
@@ -0,0 +1,52 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@arcblock/validator');
3
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
+ const { account, rollup } = require('@ocap/state');
5
+
6
+ const VerifySigners = require('./pipes/verify-signers');
7
+ const EnsureValidator = require('./pipes/ensure-validator');
8
+
9
+ const runner = new Runner();
10
+
11
+ // 1. verify itx
12
+ const schema = Joi.object({
13
+ rollup: Joi.DID().role('ROLE_ROLLUP').required(),
14
+ data: Joi.any().optional(),
15
+ }).options({ stripUnknown: true, noDefaults: false });
16
+ runner.use(({ itx }, next) => {
17
+ const { error } = schema.validate(itx);
18
+ return next(error ? new Error('INVALID_TX', `Invalid itx: ${error.message}`) : null);
19
+ });
20
+
21
+ // 2. verify rollup
22
+ runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
23
+ runner.use(EnsureValidator(true));
24
+
25
+ // 3. verify tx signers and signatures
26
+ runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
27
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
28
+
29
+ // 4. verify sender and signer states
30
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
31
+ runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
32
+ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
33
+
34
+ // 5. update rollup state
35
+ runner.use(
36
+ async (context, next) => {
37
+ const { tx, itx, rollupState, statedb, senderState } = context;
38
+
39
+ const [newSenderState, newRollupState] = await Promise.all([
40
+ statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
41
+ statedb.rollup.update(itx.rollup, rollup.pause(rollupState, context), context),
42
+ ]);
43
+
44
+ context.senderState = newSenderState;
45
+ context.rollupState = newRollupState;
46
+
47
+ next();
48
+ },
49
+ { persistError: true }
50
+ );
51
+
52
+ module.exports = runner;
@@ -0,0 +1,37 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const { fromTokenToUnit, BN } = require('@ocap/util');
3
+
4
+ const { applyTokenUpdates } = require('../../../util');
5
+
6
+ module.exports = async (context, next) => {
7
+ const { config, statedb, txType, senderState } = context;
8
+ const txFee = config.transaction.txFee[txType];
9
+ const vaultState = await statedb.account.get(config.vaults.txFee, context);
10
+
11
+ if (!txFee) {
12
+ context.senderUpdates = {};
13
+ context.vaultUpdates = {};
14
+ context.vaultState = vaultState;
15
+ context.updatedAccounts = [];
16
+ return next();
17
+ }
18
+
19
+ const expected = fromTokenToUnit(txFee, config.token.decimal);
20
+ const actual = new BN(senderState.tokens[config.token.address] || 0);
21
+ if (actual.lt(expected)) {
22
+ return next(new Error('INSUFFICIENT_FUND', `Insufficient fund to pay for tx fee, expected ${txFee}`));
23
+ }
24
+
25
+ const tokenChange = { address: config.token.address, value: expected.toString(10) };
26
+
27
+ context.senderUpdates = applyTokenUpdates([tokenChange], senderState, 'sub');
28
+ context.vaultUpdates = applyTokenUpdates([tokenChange], vaultState, 'add');
29
+ context.vaultState = vaultState;
30
+
31
+ context.updatedAccounts = [
32
+ { address: senderState.address, token: config.token.address, delta: `-${tokenChange.value}`, action: 'fee' },
33
+ { address: vaultState.address, token: config.token.address, delta: tokenChange.value, action: 'fee' },
34
+ ];
35
+
36
+ return next();
37
+ };
@@ -0,0 +1,10 @@
1
+ module.exports = (isSeed) => async (context, next) => {
2
+ const { rollupState, tx } = context;
3
+
4
+ const validators = isSeed ? rollupState.seedValidators : rollupState.validators;
5
+ if (validators.some((x) => x.address === tx.from) === false) {
6
+ return next(new Error('INVALID_TX', 'Tx sender is not an expected validator'));
7
+ }
8
+
9
+ return next();
10
+ };
@@ -0,0 +1,37 @@
1
+ const get = require('lodash/get');
2
+ const Error = require('@ocap/util/lib/error');
3
+ const { toHex } = require('@ocap/util');
4
+ const { toTypeInfo } = require('@arcblock/did');
5
+ const { fromPublicKey } = require('@ocap/wallet');
6
+
7
+ module.exports = function CreateVerifyEvidencePipe({ evidenceKey, signaturesKey, verifyMethod = 'verify' }) {
8
+ if (['verify', 'ethVerify'].includes(verifyMethod) === false) {
9
+ throw new Error(`Invalid verify method: ${verifyMethod}, supported methods are: verify, ethVerify`);
10
+ }
11
+
12
+ return (context, next) => {
13
+ const evidence = get(context, evidenceKey);
14
+ const signatures = get(context, signaturesKey);
15
+
16
+ for (const sig of signatures) {
17
+ const { signer, pk, delegator, signature } = sig;
18
+
19
+ // if delegator is empty, pk and signer shall be a pair
20
+ // if delegator is not empty, pk and delegator shall be a pair
21
+ const address = delegator || signer;
22
+ const wallet = fromPublicKey(pk, toTypeInfo(address));
23
+
24
+ try {
25
+ if (wallet[verifyMethod](toHex(evidence), toHex(signature)) === false) {
26
+ return next(new Error('INVALID_SIGNATURE', `Signature for evidence from ${address} is not valid`));
27
+ }
28
+ } catch (err) {
29
+ return next(
30
+ new Error('INVALID_SIGNATURE', `Signature for evidence from ${address} verify failed: ${err.message}`)
31
+ );
32
+ }
33
+ }
34
+
35
+ return next();
36
+ };
37
+ };
@@ -0,0 +1,10 @@
1
+ const { pipes } = require('@ocap/tx-pipeline');
2
+
3
+ module.exports = () =>
4
+ pipes.VerifyInfo([
5
+ {
6
+ error: 'INVALID_TX',
7
+ message: 'The rollup is paused',
8
+ fn: (context) => context.rollupState.paused === false,
9
+ },
10
+ ]);
@@ -0,0 +1,88 @@
1
+ const get = require('lodash/get');
2
+ const Error = require('@ocap/util/lib/error');
3
+
4
+ module.exports = function CreateVerifySignersPipe({ signersKey, allowSender = true, allowShrink = false }) {
5
+ return (context, next) => {
6
+ const { tx, rollupState } = context;
7
+ // eslint-disable-next-line prefer-const
8
+ let { minSignerCount, maxSignerCount } = rollupState;
9
+
10
+ const signatures = get(context, signersKey);
11
+ const signerCount = signatures.length;
12
+
13
+ const seedValidators = rollupState.seedValidators.map((x) => x.address);
14
+ const validators = rollupState.validators.map((x) => x.address);
15
+
16
+ // ensure singer blacklist
17
+ if (!allowSender && signatures.some((x) => x.signer === tx.from)) {
18
+ return next(new Error('INVALID_SIGNATURE', `Invalid: ${signersKey}, sender not allowed to sign`));
19
+ }
20
+
21
+ // ensure signer count
22
+ // If we have a large validators pool, we expect at least minSignerCount signers
23
+ if (validators.length > minSignerCount) {
24
+ if (signerCount < minSignerCount) {
25
+ return next(
26
+ new Error(
27
+ 'INVALID_SIGNATURE',
28
+ `Invalid: ${signersKey}, expect at least ${minSignerCount} signatures, got ${signerCount}`
29
+ )
30
+ );
31
+ }
32
+ if (signerCount > maxSignerCount) {
33
+ return next(
34
+ new Error(
35
+ 'INVALID_SIGNATURE',
36
+ `Invalid: ${signersKey}, Expect at most ${maxSignerCount} signatures, got ${signerCount}`
37
+ )
38
+ );
39
+ }
40
+ } else {
41
+ // If we have a small validators pool, eg, the minSignerCount is 3, and we have only 2 validator,
42
+ // We expect all validators to sign before we have enough validators
43
+ // But on some occasions, we need to shrink the signer requirement, such as a validator want to leave a small validator pool.
44
+ let actualValidatorCount = validators.length;
45
+ if (!allowSender && allowShrink) {
46
+ actualValidatorCount -= 1;
47
+ }
48
+
49
+ const expectedSignerCount = Math.min(actualValidatorCount, minSignerCount);
50
+
51
+ if (signerCount < expectedSignerCount) {
52
+ return next(
53
+ new Error(
54
+ 'INVALID_SIGNATURE',
55
+ `Invalid ${signersKey}, all validators are expected to sign because we have less than ${expectedSignerCount} validators`
56
+ )
57
+ );
58
+ }
59
+ }
60
+
61
+ // ensure seed validators exist
62
+ const signers = signatures.map((x) => x.signer);
63
+ const missingSigner = seedValidators.find((x) => signers.includes(x) === false);
64
+ if (missingSigner) {
65
+ return next(
66
+ new Error(
67
+ 'INVALID_SIGNATURE',
68
+ `Invalid: ${signersKey}, missing signature from seed validator: ${missingSigner}`
69
+ )
70
+ );
71
+ }
72
+
73
+ // ensure signers exist in validators
74
+ const invalidSigner = signers.find((x) => validators.includes(x) === false);
75
+ if (invalidSigner) {
76
+ return next(
77
+ new Error(
78
+ 'INVALID_SIGNATURE',
79
+ `Invalid: ${signersKey}, signer ${invalidSigner} does not exist in validator whitelist`
80
+ )
81
+ );
82
+ }
83
+
84
+ context.signers = signers;
85
+
86
+ return next();
87
+ };
88
+ };
@@ -0,0 +1,52 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@arcblock/validator');
3
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
+ const { account, rollup } = require('@ocap/state');
5
+
6
+ const VerifySigners = require('./pipes/verify-signers');
7
+ const EnsureValidator = require('./pipes/ensure-validator');
8
+
9
+ const runner = new Runner();
10
+
11
+ // 1. verify itx
12
+ const schema = Joi.object({
13
+ rollup: Joi.DID().role('ROLE_ROLLUP').required(),
14
+ data: Joi.any().optional(),
15
+ }).options({ stripUnknown: true, noDefaults: false });
16
+ runner.use(({ itx }, next) => {
17
+ const { error } = schema.validate(itx);
18
+ return next(error ? new Error('INVALID_TX', `Invalid itx: ${error.message}`) : null);
19
+ });
20
+
21
+ // 2. verify rollup
22
+ runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
23
+ runner.use(EnsureValidator(true));
24
+
25
+ // 3. verify tx signers and signatures
26
+ runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
27
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
28
+
29
+ // 4. verify sender and signer states
30
+ runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
31
+ runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
32
+ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
33
+
34
+ // 5. update rollup state
35
+ runner.use(
36
+ async (context, next) => {
37
+ const { tx, itx, rollupState, statedb, senderState } = context;
38
+
39
+ const [newSenderState, newRollupState] = await Promise.all([
40
+ statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
41
+ statedb.rollup.update(itx.rollup, rollup.resume(rollupState, context), context),
42
+ ]);
43
+
44
+ context.senderState = newSenderState;
45
+ context.rollupState = newRollupState;
46
+
47
+ next();
48
+ },
49
+ { persistError: true }
50
+ );
51
+
52
+ module.exports = runner;
@@ -0,0 +1,98 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const Joi = require('@arcblock/validator');
3
+ const { Runner, pipes } = require('@ocap/tx-pipeline');
4
+ const { account, rollup } = require('@ocap/state');
5
+
6
+ // eslint-disable-next-line global-require
7
+ const debug = require('debug')(`${require('../../../package.json').name}:update-rollup`);
8
+
9
+ const VerifySigners = require('./pipes/verify-signers');
10
+
11
+ const { decodeAnySafe } = require('../../util');
12
+
13
+ const runner = new Runner();
14
+
15
+ // 1. verify itx: client must use previous rollup state to construct this itx
16
+ const schema = Joi.object({
17
+ rollup: Joi.DID().role('ROLE_ROLLUP').required(),
18
+
19
+ minStakeAmount: Joi.BN().greater(0).required(),
20
+ maxStakeAmount: Joi.BN().greater(Joi.ref('minStakeAmount')).required(),
21
+
22
+ minSignerCount: Joi.number().integer().required(),
23
+ maxSignerCount: Joi.number().integer().min(1).max(8).greater(Joi.ref('minSignerCount')).required(),
24
+
25
+ minBlockSize: Joi.number().integer().min(1).required(),
26
+ maxBlockSize: Joi.number().integer().min(1).max(10).greater(Joi.ref('minBlockSize')).required(),
27
+
28
+ minBlockInterval: Joi.number().integer().min(1).max(60 * 60).required(), // prettier-ignore
29
+ minBlockConfirmation: Joi.number().integer().min(1).max(100).required(),
30
+
31
+ minDepositAmount: Joi.BN().positive().required(),
32
+ maxDepositAmount: Joi.BN().greater(Joi.ref('minDepositAmount')).required(),
33
+ minWithdrawAmount: Joi.BN().positive().required(),
34
+ maxWithdrawAmount: Joi.BN().greater(Joi.ref('minWithdrawAmount')).required(),
35
+
36
+ depositFeeRate: Joi.number().integer().min(1).max(10000).required(),
37
+ withdrawFeeRate: Joi.number().integer().min(1).max(10000).required(),
38
+ publisherFeeShare: Joi.number().integer().min(1).max(10000).required(),
39
+ minDepositFee: Joi.BN().positive().required(),
40
+ maxDepositFee: Joi.BN().greater(Joi.ref('minDepositFee')).required(),
41
+ minWithdrawFee: Joi.BN().positive().required(),
42
+ maxWithdrawFee: Joi.BN().greater(Joi.ref('minWithdrawFee')).required(),
43
+
44
+ data: Joi.any().optional(),
45
+ }).options({ stripUnknown: true, noDefaults: false });
46
+ runner.use(({ itx }, next) => {
47
+ const { error } = schema.validate(itx);
48
+ return next(error ? new Error('INVALID_TX', `Invalid itx: ${error.message}`) : null);
49
+ });
50
+
51
+ runner.use(
52
+ pipes.VerifyInfo([
53
+ {
54
+ error: 'INVALID_TX',
55
+ message: 'Sum of itx.proposerFeeShare and itx.publisherFeeShare must be less than 10000',
56
+ fn: ({ itx }) => itx.proposerFeeShare + itx.publisherFeeShare < 10000,
57
+ },
58
+ ])
59
+ );
60
+
61
+ // 2. verify rollup
62
+ runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
63
+
64
+ // 3. verify tx signers and signatures
65
+ runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
66
+ runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
67
+
68
+ // 4. verify sender and signer states
69
+ runner.use(
70
+ pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })
71
+ );
72
+ runner.use(
73
+ pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })
74
+ );
75
+ runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
76
+
77
+ // 5. update rollup state
78
+ runner.use(
79
+ async (context, next) => {
80
+ const { tx, itx, rollupState, statedb, senderState } = context;
81
+ const data = decodeAnySafe(itx.data);
82
+
83
+ const [newSenderState, newRollupState] = await Promise.all([
84
+ statedb.account.update(senderState.address, account.update(senderState, { nonce: tx.nonce }, context), context),
85
+ statedb.rollup.update(itx.rollup, rollup.update(rollupState, { ...itx, data }, context), context),
86
+ ]);
87
+
88
+ context.senderState = newSenderState;
89
+ context.rollupState = newRollupState;
90
+
91
+ debug('update-rollup', newRollupState);
92
+
93
+ next();
94
+ },
95
+ { persistError: true }
96
+ );
97
+
98
+ module.exports = runner;