@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
|
@@ -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,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;
|