@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,176 @@
|
|
|
1
|
+
const pick = require('lodash/pick');
|
|
2
|
+
const { promisify } = require('util');
|
|
3
|
+
const isEmpty = require('empty-value');
|
|
4
|
+
const Joi = require('@arcblock/validator');
|
|
5
|
+
const Error = require('@ocap/util/lib/error');
|
|
6
|
+
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
7
|
+
const { toStakeAddress } = require('@arcblock/did-util');
|
|
8
|
+
const { account, asset, stake } = require('@ocap/state');
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line global-require
|
|
11
|
+
const debug = require('debug')(`${require('../../../package.json').name}:acquire-asset-v2`);
|
|
12
|
+
|
|
13
|
+
const { applyTokenUpdates } = require('../../util');
|
|
14
|
+
|
|
15
|
+
const verifyAssetOwner = promisify(pipes.VerifyUpdater({ assetKey: 'assets', ownerKey: 'owner' }));
|
|
16
|
+
|
|
17
|
+
const runner = new Runner();
|
|
18
|
+
|
|
19
|
+
// 0. verify itx
|
|
20
|
+
const schema = Joi.object({
|
|
21
|
+
address: Joi.DID().role('ROLE_STAKE').required(),
|
|
22
|
+
receiver: Joi.DID().required(),
|
|
23
|
+
locked: Joi.boolean().default(false),
|
|
24
|
+
message: Joi.string().trim().min(1).max(256).required(),
|
|
25
|
+
revokeWaitingPeriod: Joi.number().integer().min(0).default(0),
|
|
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
|
+
runner.use(
|
|
32
|
+
pipes.VerifyTxInput({
|
|
33
|
+
fieldKey: 'itx.inputs',
|
|
34
|
+
inputsKey: 'inputs',
|
|
35
|
+
sendersKey: 'senders',
|
|
36
|
+
tokensKey: 'tokens',
|
|
37
|
+
assetsKey: 'assets',
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// 1. verify itx address
|
|
42
|
+
runner.use(
|
|
43
|
+
pipes.VerifyInfo([
|
|
44
|
+
{
|
|
45
|
+
error: 'INVALID_TX',
|
|
46
|
+
message: 'Invalid staking address',
|
|
47
|
+
fn: ({ tx, itx }) => toStakeAddress(tx.from, itx.receiver) === itx.address,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
error: 'INSUFFICIENT_DATA',
|
|
51
|
+
message: 'Can not stake without any token or asset',
|
|
52
|
+
fn: ({ assets, tokens }) => !(isEmpty(tokens) && isEmpty(assets)),
|
|
53
|
+
},
|
|
54
|
+
])
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// 2. verify itx size: set hard limit here because more inputs leads to longer tx execute time
|
|
58
|
+
runner.use(pipes.VerifyListSize({ listKey: ['inputs', 'senders', 'tokens', 'assets'] }));
|
|
59
|
+
|
|
60
|
+
// 3. verify multi sig
|
|
61
|
+
runner.use(pipes.VerifyMultiSigV2({ signersKey: 'senders' }));
|
|
62
|
+
|
|
63
|
+
// 4. verify against state
|
|
64
|
+
runner.use(pipes.ExtractState({ from: 'itx.address', to: 'stakeState', status: 'OK', table: 'stake' }));
|
|
65
|
+
|
|
66
|
+
// 5. verify sender & signer & receiver
|
|
67
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'OK', table: 'account' }));
|
|
68
|
+
runner.use(pipes.ExtractState({ from: 'senders', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
|
|
69
|
+
runner.use(pipes.ExtractState({ from: 'itx.receiver', to: 'receiverState', status: 'INVALID_RECEIVER_STATE' })); // can by any type
|
|
70
|
+
runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
|
|
71
|
+
|
|
72
|
+
// 6. verify token state and balance
|
|
73
|
+
runner.use(pipes.ExtractState({ from: 'tokens', to: 'tokenStates', status: 'INVALID_TOKEN', table: 'token' }));
|
|
74
|
+
runner.use(pipes.VerifyTokenBalance({ ownerKey: 'signerStates', conditionKey: 'inputs' }));
|
|
75
|
+
|
|
76
|
+
// 7. verify asset state and ownership
|
|
77
|
+
runner.use(pipes.ExtractState({ from: 'assets', to: 'assetStates', status: 'OK', table: 'asset' }));
|
|
78
|
+
runner.use(pipes.VerifyTransferrable({ assets: 'assetStates' }));
|
|
79
|
+
runner.use(async (context, next) => {
|
|
80
|
+
const { inputs, assetStates = [], signerStates } = context;
|
|
81
|
+
for (const input of inputs) {
|
|
82
|
+
const { owner, assetsList } = input;
|
|
83
|
+
const states = assetStates.filter((x) => assetsList.includes(x.address));
|
|
84
|
+
const signer = signerStates.find((x) => x.address === owner);
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// eslint-disable-next-line no-await-in-loop
|
|
88
|
+
await verifyAssetOwner({ assets: states, owner: signer });
|
|
89
|
+
} catch (err) {
|
|
90
|
+
return next(err);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return next();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// update statedb
|
|
98
|
+
runner.use(
|
|
99
|
+
async (context, next) => {
|
|
100
|
+
const { tx, itx, inputs, statedb, senderState, stakeState, signerStates, assetStates = [] } = context;
|
|
101
|
+
|
|
102
|
+
const signerUpdates = {};
|
|
103
|
+
const stakeUpdates = stakeState
|
|
104
|
+
? { tokens: stakeState.tokens, assets: stakeState.assets }
|
|
105
|
+
: { tokens: {}, assets: [] };
|
|
106
|
+
|
|
107
|
+
inputs.forEach((x) => {
|
|
108
|
+
const { owner, tokensList, assetsList } = x;
|
|
109
|
+
signerUpdates[owner] = applyTokenUpdates(
|
|
110
|
+
tokensList,
|
|
111
|
+
signerStates.find((s) => s.address === owner),
|
|
112
|
+
'sub'
|
|
113
|
+
);
|
|
114
|
+
Object.assign(stakeUpdates, applyTokenUpdates(tokensList, stakeUpdates, 'add'));
|
|
115
|
+
stakeUpdates.assets.push(...assetsList);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const isAlsoSigner = !!signerUpdates[tx.from];
|
|
119
|
+
|
|
120
|
+
const [newSenderState, newSignerStates, newStakeState, newAssetStates] = await Promise.all([
|
|
121
|
+
// Update sender state
|
|
122
|
+
statedb.account.updateOrCreate(
|
|
123
|
+
senderState,
|
|
124
|
+
account.updateOrCreate(
|
|
125
|
+
senderState,
|
|
126
|
+
Object.assign({ address: tx.from, nonce: tx.nonce, pk: tx.pk }, signerUpdates[tx.from] || {}),
|
|
127
|
+
context
|
|
128
|
+
),
|
|
129
|
+
context
|
|
130
|
+
),
|
|
131
|
+
|
|
132
|
+
// Update signer state
|
|
133
|
+
Promise.all(
|
|
134
|
+
signerStates
|
|
135
|
+
.filter((x) => x.address !== tx.from)
|
|
136
|
+
.map((x) => statedb.account.update(x.address, account.update(x, signerUpdates[x.address], context), context))
|
|
137
|
+
),
|
|
138
|
+
|
|
139
|
+
// Update/create stake state
|
|
140
|
+
stakeState
|
|
141
|
+
? statedb.stake.update(stakeState.address, stake.update(stakeState, stakeUpdates, context), context)
|
|
142
|
+
: statedb.stake.create(
|
|
143
|
+
itx.address,
|
|
144
|
+
stake.create(
|
|
145
|
+
{
|
|
146
|
+
sender: tx.from,
|
|
147
|
+
revocable: !itx.locked,
|
|
148
|
+
...pick(itx, ['address', 'receiver', 'message', 'data', 'revokeWaitingPeriod']),
|
|
149
|
+
...stakeUpdates,
|
|
150
|
+
},
|
|
151
|
+
context
|
|
152
|
+
),
|
|
153
|
+
context
|
|
154
|
+
),
|
|
155
|
+
|
|
156
|
+
// Transfer assets to staking account
|
|
157
|
+
Promise.all(
|
|
158
|
+
assetStates.map((x) =>
|
|
159
|
+
statedb.asset.update(x.address, asset.update(x, { owner: itx.address }, context), context)
|
|
160
|
+
)
|
|
161
|
+
),
|
|
162
|
+
]);
|
|
163
|
+
|
|
164
|
+
context.senderState = newSenderState;
|
|
165
|
+
context.signerStates = isAlsoSigner ? newSignerStates.concat(newSenderState) : newSignerStates;
|
|
166
|
+
context.stakeState = newStakeState;
|
|
167
|
+
context.assetStates = newAssetStates;
|
|
168
|
+
|
|
169
|
+
debug('stake', { address: itx.address, stakeUpdates, signerUpdates });
|
|
170
|
+
|
|
171
|
+
next();
|
|
172
|
+
},
|
|
173
|
+
{ persistError: true }
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
module.exports = runner;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
const uniqBy = require('lodash/uniqBy');
|
|
2
|
+
const groupBy = require('lodash/groupBy');
|
|
3
|
+
const cloneDeep = require('lodash/cloneDeep');
|
|
4
|
+
const Joi = require('@arcblock/validator');
|
|
5
|
+
const Error = require('@ocap/util/lib/error');
|
|
6
|
+
const { BN } = require('@ocap/util');
|
|
7
|
+
const { Runner, pipes } = require('@ocap/tx-pipeline');
|
|
8
|
+
const { account, stake, evidence, rollupBlock } = require('@ocap/state');
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line global-require
|
|
11
|
+
const debug = require('debug')(`${require('../../../package.json').name}:claim-block-reward`);
|
|
12
|
+
|
|
13
|
+
const { toStakeAddress } = require('@arcblock/did-util');
|
|
14
|
+
const VerifySigners = require('./pipes/verify-signers');
|
|
15
|
+
const { applyTokenChange, splitTxFee, getBNSum, getRewardLocker, RATE_BASE } = require('../../util');
|
|
16
|
+
|
|
17
|
+
const runner = new Runner();
|
|
18
|
+
|
|
19
|
+
// 1. verify itx
|
|
20
|
+
const schema = Joi.object({
|
|
21
|
+
rollup: Joi.DID().role('ROLE_ROLLUP').required(),
|
|
22
|
+
blockHeight: Joi.number().integer().greater(0).required(),
|
|
23
|
+
blockHash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
24
|
+
evidence: Joi.object({
|
|
25
|
+
hash: Joi.string().regex(Joi.patterns.txHash).required(),
|
|
26
|
+
}).required(),
|
|
27
|
+
publisher: Joi.DID().wallet('ethereum').required(),
|
|
28
|
+
data: Joi.any().optional(),
|
|
29
|
+
}).options({ stripUnknown: true, noDefaults: false });
|
|
30
|
+
runner.use((context, next) => {
|
|
31
|
+
const { tx, itx } = context;
|
|
32
|
+
|
|
33
|
+
const { error } = schema.validate(itx);
|
|
34
|
+
if (error) {
|
|
35
|
+
return next(new Error('INVALID_TX', error.message));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ensure publisher same with tx.from
|
|
39
|
+
if (tx.from !== itx.publisher) {
|
|
40
|
+
return next(new Error('INVALID_TX', 'itx.publisher must be same with tx.from'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
context.lockerAddress = getRewardLocker(itx.rollup);
|
|
44
|
+
|
|
45
|
+
return next();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 2. ensure evidence not exist
|
|
49
|
+
runner.use(pipes.ExtractState({ from: 'itx.evidence.hash', to: 'evidenceSeen', status: 'OK', table: 'evidence' }));
|
|
50
|
+
runner.use(pipes.ExtractState({ from: 'itx.blockHash', to: 'blockClaimed', status: 'OK', table: 'evidence' }));
|
|
51
|
+
runner.use(({ evidenceSeen, blockClaimed }, next) => {
|
|
52
|
+
if (evidenceSeen) return next(new Error('INVALID_TX', 'Claim evidence already seen on this chain'));
|
|
53
|
+
if (blockClaimed) return next(new Error('INVALID_TX', 'Block reward already claimed before this tx'));
|
|
54
|
+
return next();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 3. verify rollup and block state
|
|
58
|
+
runner.use(pipes.ExtractState({ from: 'itx.rollup', to: 'rollupState', status: 'INVALID_ROLLUP', table: 'rollup' }));
|
|
59
|
+
runner.use(pipes.ExtractState({ from: 'itx.blockHash', to: 'blockState', status: 'INVALID_ROLLUP_BLOCK', table: 'rollupBlock' })); // prettier-ignore
|
|
60
|
+
runner.use((context, next) => {
|
|
61
|
+
const { blockState, rollupState, itx } = context;
|
|
62
|
+
if (blockState.rollup !== rollupState.address) {
|
|
63
|
+
return next(new Error('INVALID_TX', 'Rollup block belonging does not match'));
|
|
64
|
+
}
|
|
65
|
+
if (blockState.height !== itx.blockHeight) {
|
|
66
|
+
return next(new Error('INVALID_TX', 'Rollup block height does not match'));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// If the publisher is not the producer, he should wait at least rollupState.publishWaitingPeriod
|
|
70
|
+
if (blockState.proposer !== itx.publisher) {
|
|
71
|
+
const proposedAt = +new Date(blockState.context.genesisTime);
|
|
72
|
+
const publishedAt = +new Date(context.txTime);
|
|
73
|
+
if (proposedAt + rollupState.publishWaitingPeriod * 1000 > publishedAt) {
|
|
74
|
+
return next(new Error('INVALID_TX', 'Rollup block can only be published by producer during waiting period'));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
context.producerStake = toStakeAddress(blockState.proposer, itx.rollup);
|
|
79
|
+
|
|
80
|
+
return next();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// 4. verify tx signers & signatures
|
|
84
|
+
runner.use(VerifySigners({ signersKey: 'tx.signaturesList', allowSender: true }));
|
|
85
|
+
runner.use(pipes.VerifyMultiSigV2({ signersKey: 'signers' }));
|
|
86
|
+
|
|
87
|
+
// 5. verify block reward locker
|
|
88
|
+
runner.use(pipes.ExtractState({ from: 'lockerAddress', to: 'lockerState', status: 'INVALID_LOCKER_STATE', table: 'stake' })); // prettier-ignore
|
|
89
|
+
runner.use(pipes.ExtractState({ from: 'producerStake', to: 'stakeState', status: 'INVALID_STAKE_STATE', table: 'stake' })); // prettier-ignore
|
|
90
|
+
|
|
91
|
+
// 6. verify sender and signer states
|
|
92
|
+
runner.use(pipes.ExtractState({ from: 'tx.from', to: 'senderState', status: 'INVALID_SENDER_STATE', table: 'account' })); // prettier-ignore
|
|
93
|
+
runner.use(pipes.ExtractState({ from: 'signers', to: 'signerStates', status: 'INVALID_SIGNER_STATE', table: 'account' })); // prettier-ignore
|
|
94
|
+
runner.use(pipes.VerifyAccountMigration({ signerKey: 'signerStates', senderKey: 'senderState' }));
|
|
95
|
+
|
|
96
|
+
// 7. verify token state
|
|
97
|
+
runner.use(pipes.ExtractState({ from: 'rollupState.tokenAddress', to: 'tokenState', status: 'INVALID_TOKEN', table: 'token' })); // prettier-ignore
|
|
98
|
+
|
|
99
|
+
// 8. split and aggregate block reward for each tx and the block
|
|
100
|
+
runner.use(pipes.ExtractState({ from: 'blockState.txs', to: 'txs', status: 'INVALID_TX', table: 'tx' }));
|
|
101
|
+
runner.use(async (context, next) => {
|
|
102
|
+
const { itx, txs, rollupState, blockState, lockerState, tokenState, stakeState } = context;
|
|
103
|
+
const { proposerFeeShare, publisherFeeShare } = rollupState;
|
|
104
|
+
|
|
105
|
+
const shares = {
|
|
106
|
+
publisher: publisherFeeShare,
|
|
107
|
+
proposer: proposerFeeShare,
|
|
108
|
+
validator: 10000 - publisherFeeShare - proposerFeeShare,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const changes = { account: [], stake: [] };
|
|
112
|
+
|
|
113
|
+
// 0. handle slash case
|
|
114
|
+
// TODO: the producer stake balance should be enough for slashing
|
|
115
|
+
const shouldSlash = blockState.proposer !== itx.publisher;
|
|
116
|
+
if (shouldSlash) {
|
|
117
|
+
const slashAmount = new BN(rollupState.minStakeAmount).mul(new BN(rollupState.publishSlashRate)).div(RATE_BASE);
|
|
118
|
+
const slashShare = slashAmount.div(new BN(2));
|
|
119
|
+
changes.stake.push({ address: stakeState.address, delta: `-${slashAmount.toString(10)}`, action: 'slash' });
|
|
120
|
+
changes.stake.push({ address: lockerState.address, delta: slashShare.toString(10), action: 'slash' });
|
|
121
|
+
changes.account.push({ address: itx.publisher, delta: slashShare.toString(10), action: 'reward' });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 1. loop through the tx and split reward
|
|
125
|
+
txs.forEach((x) => {
|
|
126
|
+
const { actualFee } = x.tx.itxJson;
|
|
127
|
+
const { publisher, proposer, validator } = splitTxFee({ total: actualFee, shares, stringify: false });
|
|
128
|
+
|
|
129
|
+
changes.stake.push({ address: lockerState.address, delta: `-${actualFee}`, action: 'claim' });
|
|
130
|
+
changes.account.push({ address: itx.publisher, delta: publisher.toString(10), action: 'fee' });
|
|
131
|
+
|
|
132
|
+
if (x.type === 'deposit_token_v2') {
|
|
133
|
+
changes.account.push({ address: x.tx.itxJson.proposer, delta: proposer.toString(10), action: 'fee' });
|
|
134
|
+
|
|
135
|
+
// block and tx signers share the validator part
|
|
136
|
+
// FIXME: delegation is not supported here yet
|
|
137
|
+
const validators = x.tx.signatures.map((s) => s.signer).concat(blockState.signatures.map((s) => s.signer));
|
|
138
|
+
const validatorShare = validator.div(new BN(validators.length));
|
|
139
|
+
validators.forEach((v) =>
|
|
140
|
+
changes.account.push({ address: v, delta: validatorShare.toString(10), action: 'fee' })
|
|
141
|
+
);
|
|
142
|
+
} else if (x.type === 'withdraw_token_v2') {
|
|
143
|
+
changes.account.push({ address: blockState.proposer, delta: proposer.toString(10), action: 'fee' });
|
|
144
|
+
|
|
145
|
+
// block signers share the validator part
|
|
146
|
+
// FIXME: delegation is not supported here yet
|
|
147
|
+
const validators = blockState.signatures.map((s) => s.signer);
|
|
148
|
+
const validatorShare = validator.div(new BN(validators.length));
|
|
149
|
+
validators.forEach((v) =>
|
|
150
|
+
changes.account.push({ address: v, delta: validatorShare.toString(10), action: 'fee' })
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 2. aggregate changes to updates
|
|
156
|
+
const grouped = {
|
|
157
|
+
account: groupBy(changes.account, 'address'),
|
|
158
|
+
stake: groupBy(changes.stake, 'address'),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const updates = {
|
|
162
|
+
stake: Object.keys(grouped.stake).reduce((acc, x) => {
|
|
163
|
+
acc[x] = {
|
|
164
|
+
address: x,
|
|
165
|
+
token: tokenState.address,
|
|
166
|
+
delta: getBNSum(...grouped.stake[x].map((c) => c.delta)),
|
|
167
|
+
action: grouped.stake[x][grouped.stake[x].length - 1].action,
|
|
168
|
+
};
|
|
169
|
+
return acc;
|
|
170
|
+
}, {}),
|
|
171
|
+
account: Object.keys(grouped.account).reduce((acc, x) => {
|
|
172
|
+
acc[x] = {
|
|
173
|
+
address: x,
|
|
174
|
+
token: tokenState.address,
|
|
175
|
+
delta: getBNSum(...grouped.account[x].map((c) => c.delta)),
|
|
176
|
+
action: grouped.account[x][grouped.account[x].length - 1].action,
|
|
177
|
+
};
|
|
178
|
+
return acc;
|
|
179
|
+
}, {}),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
context.changes = changes;
|
|
183
|
+
context.updates = updates;
|
|
184
|
+
|
|
185
|
+
return next();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// 9. extract all accounts that will be updated exist
|
|
189
|
+
runner.use((context, next) => {
|
|
190
|
+
const { updates, senderState, signerStates } = context;
|
|
191
|
+
const requiredAccounts = Object.keys(updates.account);
|
|
192
|
+
const knownAccounts = { [senderState.address]: senderState };
|
|
193
|
+
|
|
194
|
+
// eslint-disable-next-line no-return-assign
|
|
195
|
+
signerStates.forEach((x) => (knownAccounts[x.address] = x));
|
|
196
|
+
|
|
197
|
+
context.missingAccounts = requiredAccounts.filter((x) => !knownAccounts[x]);
|
|
198
|
+
return next();
|
|
199
|
+
});
|
|
200
|
+
runner.use(
|
|
201
|
+
pipes.ExtractState({
|
|
202
|
+
from: 'missingAccounts',
|
|
203
|
+
to: 'missingAccountStates',
|
|
204
|
+
status: 'INVALID_SIGNER_STATE',
|
|
205
|
+
table: 'account',
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
runner.use((context, next) => {
|
|
209
|
+
const { senderState, signerStates, missingAccountStates } = context;
|
|
210
|
+
context.accountStates = uniqBy([senderState, ...signerStates, ...(missingAccountStates || [])], 'address');
|
|
211
|
+
return next();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// 10. update state
|
|
215
|
+
runner.use(
|
|
216
|
+
async (context, next) => {
|
|
217
|
+
const { tx, itx, updates, statedb, senderState, blockState, lockerState, stakeState, accountStates } = context;
|
|
218
|
+
|
|
219
|
+
const [newStakeStates, newAccountStates, newBlockState] = await Promise.all([
|
|
220
|
+
// update stake states
|
|
221
|
+
Promise.all(
|
|
222
|
+
[lockerState, stakeState].map((x) =>
|
|
223
|
+
updates.stake[x.address]
|
|
224
|
+
? statedb.stake.update(
|
|
225
|
+
x.address,
|
|
226
|
+
stake.update(x, applyTokenChange(x, updates.stake[x.address]), context),
|
|
227
|
+
context
|
|
228
|
+
)
|
|
229
|
+
: x
|
|
230
|
+
)
|
|
231
|
+
),
|
|
232
|
+
|
|
233
|
+
// update accounts(proposer, publisher, validator)
|
|
234
|
+
Promise.all(
|
|
235
|
+
accountStates.map((x) =>
|
|
236
|
+
statedb.account.update(
|
|
237
|
+
x.address,
|
|
238
|
+
account.update(
|
|
239
|
+
x,
|
|
240
|
+
Object.assign(
|
|
241
|
+
x.address === senderState.address ? { nonce: tx.nonce } : {},
|
|
242
|
+
applyTokenChange(x, updates.account[x.address])
|
|
243
|
+
),
|
|
244
|
+
context
|
|
245
|
+
),
|
|
246
|
+
context
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
),
|
|
250
|
+
|
|
251
|
+
// Update block state, just update the context
|
|
252
|
+
statedb.rollupBlock.update(blockState.hash, rollupBlock.update(blockState, context), context),
|
|
253
|
+
|
|
254
|
+
// Create evidence
|
|
255
|
+
statedb.evidence.create(
|
|
256
|
+
itx.evidence.hash,
|
|
257
|
+
evidence.create({ hash: itx.evidence.hash, data: 'rollup-claim-block-reward' }, context),
|
|
258
|
+
context
|
|
259
|
+
),
|
|
260
|
+
statedb.evidence.create(
|
|
261
|
+
itx.blockHash,
|
|
262
|
+
evidence.create({ hash: itx.blockHash, data: 'rollup-claim-block-reward' }, context),
|
|
263
|
+
context
|
|
264
|
+
),
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
// to avoid this to be incorrectly indexed
|
|
268
|
+
delete context.stakeState;
|
|
269
|
+
|
|
270
|
+
context.stakeStates = newStakeStates.filter((x) => updates.stake[x.address]);
|
|
271
|
+
context.accountStates = newAccountStates;
|
|
272
|
+
context.blockState = newBlockState;
|
|
273
|
+
|
|
274
|
+
context.updatedAccounts = cloneDeep([...Object.values(updates.account), ...Object.values(updates.stake)]);
|
|
275
|
+
|
|
276
|
+
debug('claim-block-reward', itx);
|
|
277
|
+
|
|
278
|
+
next();
|
|
279
|
+
},
|
|
280
|
+
{ persistError: true }
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
module.exports = runner;
|