@ocap/tx-pipeline 1.6.5 → 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 CHANGED
@@ -14,7 +14,7 @@ Then:
14
14
  const TxPipeline = require('@ocap/tx-pipeline');
15
15
  const NedbAdapter = require('@ocap/statedb-nedb');
16
16
 
17
- const { DecodeTx, VerifyTx, VerifyBlacklist, VerifyReplay, DecodeItx, VerifySignature, ExtractState } = TxPipeline;
17
+ const { DecodeTx, VerifyTx, VerifyBlacklist, DecodeItx, VerifySignature, ExtractState } = TxPipeline;
18
18
  // Pre-pipelines
19
19
  // Check-pipelines: formal
20
20
  // Verify-pipelines: against state db
@@ -27,7 +27,6 @@ const pipeline = new TxPipeline('transfer');
27
27
  pipeline.use(DecodeTx);
28
28
  pipeline.use(VerifyTx);
29
29
  pipeline.use(VerifyBlacklist);
30
- pipeline.use(VerifyReplay);
31
30
  pipeline.use(DecodeItx);
32
31
  pipeline.use(VerifySignature);
33
32
  pipeline.use(ExtractState({ from: 'tx.from', to: 'senderState' }));
@@ -52,13 +51,6 @@ pipeline
52
51
 
53
52
  ## Pipes not supported
54
53
 
55
- - [ ] VerifyDelegation
56
- - [ ] VerifyAccountMigration
57
- - [ ] VerifySender: enforce declare
58
- - [ ] VerifyModifiable: asset related
59
54
  - [ ] VerifyModerator: chain administration
60
- - [ ] UpdateReceiptDB: bloom-filter for anti-reply check
61
- - [ ] UpdateState: should do nothing
62
- - [ ] CheckDID: white list
63
55
  - [ ] CheckError: log info on error
64
56
  - [ ] AntiReplayExchangeAttack
package/lib/index.js CHANGED
@@ -1,31 +1,30 @@
1
1
  const Runner = require('./runner');
2
- const Error = require('./error');
3
2
  const AntiLandAttack = require('./pipes/anti-land-attack');
4
3
  const DecodeItx = require('./pipes/decode-itx');
5
4
  const DecodeTx = require('./pipes/decode-tx');
6
5
  const ExtractReceiver = require('./pipes/extract-receiver');
7
6
  const ExtractSigner = require('./pipes/extract-signer');
8
7
  const ExtractState = require('./pipes/extract-state');
9
- const UpdateDelegationState = require('./pipes/update-delegation-state');
10
8
  const UpdateOwner = require('./pipes/update-owner');
11
9
  const VerifyAccountMigration = require('./pipes/verify-account-migration');
12
- const VerifyBalance = require('./pipes/verify-balance');
13
10
  const VerifyDelegation = require('./pipes/verify-delegation');
14
11
  const VerifyInfo = require('./pipes/verify-info');
15
- const VerifyItxSize = require('./pipes/verify-itx-size');
12
+ const VerifyListSize = require('./pipes/verify-list-size');
16
13
  const VerifyMultiSig = require('./pipes/verify-multisig');
17
- const VerifyReplay = require('./pipes/verify-replay');
14
+ const VerifyMultiSigV2 = require('./pipes/verify-multisig-v2');
18
15
  const VerifySender = require('./pipes/verify-sender');
16
+ const VerifyServiceFee = require('./pipes/verify-service-fee');
19
17
  const VerifySignature = require('./pipes/verify-signature');
20
18
  const VerifySigner = require('./pipes/verify-signer');
21
19
  const VerifyTransferrable = require('./pipes/verify-transferrable');
22
20
  const VerifyTx = require('./pipes/verify-tx');
23
21
  const VerifyTxSize = require('./pipes/verify-tx-size');
22
+ const VerifyTxInput = require('./pipes/verify-tx-input');
24
23
  const VerifyUpdater = require('./pipes/verify-updater');
24
+ const VerifyTokenBalance = require('./pipes/verify-token-balance');
25
25
 
26
26
  module.exports = {
27
27
  Runner,
28
- Error,
29
28
  pipes: {
30
29
  AntiLandAttack,
31
30
  DecodeItx,
@@ -33,21 +32,22 @@ module.exports = {
33
32
  ExtractReceiver,
34
33
  ExtractSigner,
35
34
  ExtractState,
36
- UpdateDelegationState,
37
35
  UpdateOwner,
38
36
  VerifyAccountMigration,
39
- VerifyBalance,
40
37
  VerifyDelegation,
41
38
  VerifyInfo,
42
- VerifyItxSize,
39
+ VerifyListSize,
43
40
  VerifyMultiSig,
41
+ VerifyMultiSigV2,
44
42
  VerifyUpdater,
45
- VerifyReplay,
46
43
  VerifySender,
44
+ VerifyServiceFee,
47
45
  VerifySignature,
48
46
  VerifySigner,
49
47
  VerifyTransferrable,
50
48
  VerifyTx,
51
49
  VerifyTxSize,
50
+ VerifyTxInput,
51
+ VerifyTokenBalance,
52
52
  },
53
53
  };
@@ -1,16 +1,22 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
- const Error = require('../error');
3
+ const Error = require('@ocap/util/lib/error');
4
4
 
5
5
  module.exports = function CreateAntiLandAttackPipe({
6
6
  senderState = 'senderState',
7
7
  receiverState = 'receiverState',
8
- }) {
8
+ receiverAddress = 'receiver',
9
+ } = {}) {
9
10
  return function AntiLandAttack(context, next) {
10
11
  const sender = get(context, senderState);
11
12
  const receiver = get(context, receiverState);
12
13
 
13
- if (sender.address === receiver.address) {
14
+ if (receiver && sender.address === receiver.address) {
15
+ return next(new Error('INVALID_TX', 'Sender and receiver are the same'));
16
+ }
17
+
18
+ const receiverAddr = get(context, receiverAddress);
19
+ if (sender.address === receiverAddr) {
14
20
  return next(new Error('INVALID_TX', 'Sender and receiver are the same'));
15
21
  }
16
22
 
@@ -1,12 +1,12 @@
1
1
  const { decodeAny } = require('@ocap/message');
2
2
 
3
- const Error = require('../error');
3
+ const Error = require('@ocap/util/lib/error');
4
4
 
5
5
  module.exports = function DecodeItx(context, next) {
6
6
  try {
7
7
  const decoded = decodeAny(context.tx.itx);
8
- Object.defineProperty(context, 'itx', { value: decoded.value });
9
- Object.defineProperty(context, 'txType', { value: context.tx.itx.typeUrl });
8
+ Object.defineProperty(context, 'itx', { value: decoded.value, enumerable: true });
9
+ Object.defineProperty(context, 'txType', { value: context.tx.itx.typeUrl, enumerable: true });
10
10
  } catch (err) {
11
11
  return next(new Error('INVALID_TX', 'Failed to decode itx'));
12
12
  }
@@ -1,12 +1,11 @@
1
1
  const Mcrypto = require('@ocap/mcrypto');
2
2
  const { getMessageType } = require('@ocap/message');
3
3
  const { fromBase64, toUint8Array } = require('@ocap/util');
4
+ const Error = require('@ocap/util/lib/error');
4
5
 
5
6
  // eslint-disable-next-line global-require
6
7
  const debug = require('debug')(`${require('../../package.json').name}:pipe:decode-tx`);
7
8
 
8
- const CustomError = require('../error');
9
-
10
9
  const sha256 = Mcrypto.Hasher.SHA2.hash256;
11
10
  const toTxHash = (buffer) => sha256(buffer, 1).replace(/^0x/, '').toUpperCase();
12
11
  const Transaction = getMessageType('Transaction').fn;
@@ -15,13 +14,16 @@ module.exports = function DecodeTx(context, next) {
15
14
  try {
16
15
  const txBuffer = toUint8Array(fromBase64(context.txBase64));
17
16
 
18
- Object.defineProperty(context, 'tx', { value: Transaction.deserializeBinary(txBuffer).toObject() });
19
- Object.defineProperty(context, 'txSize', { value: Buffer.byteLength(txBuffer) });
20
- Object.defineProperty(context, 'txHash', { value: toTxHash(txBuffer) });
21
- Object.defineProperty(context, 'txTime', { value: new Date().toISOString() });
17
+ Object.defineProperty(context, 'tx', {
18
+ value: Transaction.deserializeBinary(txBuffer).toObject(),
19
+ enumerable: true,
20
+ });
21
+ Object.defineProperty(context, 'txSize', { value: Buffer.byteLength(txBuffer), enumerable: true });
22
+ Object.defineProperty(context, 'txHash', { value: toTxHash(txBuffer), enumerable: true });
23
+ Object.defineProperty(context, 'txTime', { value: new Date().toISOString(), enumerable: true });
22
24
  } catch (err) {
23
25
  debug('failed to decode transaction', { txBase64: context.txBase64, error: err });
24
- return next(new CustomError('INVALID_TX', 'Can not decode base64 encoded transaction'));
26
+ return next(new Error('INVALID_TX', 'Can not decode base64 encoded transaction'));
25
27
  }
26
28
 
27
29
  next();
@@ -1,7 +1,6 @@
1
1
  const set = require('lodash/set');
2
2
  const getListField = require('@ocap/util/lib/get-list-field');
3
-
4
- const Error = require('../error');
3
+ const Error = require('@ocap/util/lib/error');
5
4
 
6
5
  module.exports = function CreateExtractSignerPipe({
7
6
  signerKey = 'signerStates',
@@ -2,15 +2,31 @@
2
2
  const get = require('lodash/get');
3
3
  const set = require('lodash/set');
4
4
  const isEmpty = require('empty-value');
5
+ const Error = require('@ocap/util/lib/error');
6
+ const getRelatedAddresses = require('@ocap/util/lib/get-related-addr');
5
7
  const { types } = require('@ocap/mcrypto');
8
+ const { tables } = require('@ocap/state');
6
9
  const { toTypeInfo } = require('@arcblock/did');
7
10
 
8
11
  // eslint-disable-next-line global-require
9
12
  const debug = require('debug')(`${require('../../package.json').name}:pipe:extract-state`);
10
13
 
11
- const Error = require('../error');
14
+ const getRelatedAddrList = (x) => (x.hash ? [x.hash] : getRelatedAddresses(x));
15
+
16
+ /**
17
+ * Extract state from state db
18
+ *
19
+ * @param {object} params
20
+ * @return {string} params.from path to extract id for state
21
+ * @return {string} params.to path to populate extracted state
22
+ * @return {string} params.table which statedb to query, default is auto detect, can be [account,asset,delegation,factory,token]
23
+ * @return {string} params.status error code when the extracted state is empty
24
+ */
25
+ module.exports = function CreateExtractStatePipe({ from, to, table, status = 'OK' }) {
26
+ if (table && tables.includes(table) === false) {
27
+ throw new Error('INTERNAL', 'Invalid table param for ExtractState pipe');
28
+ }
12
29
 
13
- module.exports = function CreateExtractStatePipe({ from, to, status = 'OK' }) {
14
30
  return async function ExtractState(context, next) {
15
31
  if (typeof get(context, to) !== 'undefined') {
16
32
  // Do not overwrite if the key already exists
@@ -19,38 +35,56 @@ module.exports = function CreateExtractStatePipe({ from, to, status = 'OK' }) {
19
35
 
20
36
  const address = get(context, from);
21
37
  if (isEmpty(address)) {
22
- set(context, to, null);
38
+ set(context, to, undefined);
23
39
  return next();
24
40
  }
25
41
 
26
- if (address) {
27
- const addresses = Array.isArray(address) ? address : [address];
28
- const value = (
29
- await Promise.all(
30
- addresses.map((x) => {
42
+ const list = Array.isArray(address) ? address : [address];
43
+ const result = (
44
+ await Promise.all(
45
+ list.map((x) => {
46
+ if (typeof table === 'undefined') {
31
47
  const type = toTypeInfo(x);
32
48
  if (type.role === types.RoleType.ROLE_ASSET) {
33
49
  return context.statedb.asset.get(x, context);
34
50
  }
35
- if (type.role === types.RoleType.ROLE_DELEGATE) {
51
+ if (type.role === types.RoleType.ROLE_DELEGATION) {
36
52
  return context.statedb.delegation.get(x, context);
37
53
  }
54
+ if (type.role === types.RoleType.ROLE_TOKEN) {
55
+ return context.statedb.token.get(x, context);
56
+ }
57
+ if (type.role === types.RoleType.ROLE_FACTORY) {
58
+ return context.statedb.factory.get(x, context);
59
+ }
60
+ if (type.role === types.RoleType.ROLE_STAKE) {
61
+ return context.statedb.stake.get(x, context);
62
+ }
63
+ if (type.role === types.RoleType.ROLE_ROLLUP) {
64
+ return context.statedb.rollup.get(x, context);
65
+ }
38
66
 
39
67
  // FIXME: maybe buggy here, since we have many role types
40
68
  return context.statedb.account.get(x, context);
41
- })
42
- )
43
- ).filter(Boolean);
69
+ }
44
70
 
45
- debug('extract', { address, value });
71
+ return context.statedb[table].get(x, context);
72
+ })
73
+ )
74
+ ).filter(Boolean);
46
75
 
47
- if (isEmpty(value) && status !== 'OK') {
48
- return next(new Error(status, `Invalid state extracted for ${address}`));
76
+ if (status !== 'OK') {
77
+ const missing = list.find((x) => !result.find((r) => getRelatedAddrList(r).includes(x)));
78
+ debug('extract', { address, result, missing });
79
+ if (missing) {
80
+ const type = toTypeInfo(missing, true);
81
+ const role = (type.role || '').replace('ROLE_', '');
82
+ return next(new Error(status, `Invalid state extracted for ${role || table}: ${missing}`, { persist: true }));
49
83
  }
50
-
51
- set(context, to, Array.isArray(address) ? value : value.shift());
52
84
  }
53
85
 
86
+ set(context, to, Array.isArray(address) ? result : result.shift());
87
+
54
88
  next();
55
89
  };
56
90
  };
@@ -1,23 +1,21 @@
1
1
  const get = require('lodash/get');
2
2
  const set = require('lodash/set');
3
+ const Error = require('@ocap/util/lib/error');
3
4
 
4
5
  // eslint-disable-next-line global-require
5
6
  const debug = require('debug')(`${require('../../package.json').name}:pipe:update-owner`);
6
7
 
7
- const Error = require('../error');
8
-
9
8
  module.exports = function CreateUpdateOwnerPipe({ assets, owner }) {
10
9
  return async function UpdateOwner(context, next) {
11
- const { statedb, states } = context;
12
-
13
- const ownerState = get(context, owner);
14
- const ownerAddress = typeof ownerState === 'string' ? ownerState : ownerState.address;
15
-
16
10
  let items = get(context, assets);
17
11
  if (!items) {
18
12
  return next();
19
13
  }
20
14
 
15
+ const { statedb, states } = context;
16
+ const ownerState = get(context, owner);
17
+ const ownerAddress = typeof ownerState === 'string' ? ownerState : ownerState.address;
18
+
21
19
  const isList = Array.isArray(items);
22
20
  items = isList ? items : [items];
23
21
 
@@ -33,7 +31,7 @@ module.exports = function CreateUpdateOwnerPipe({ assets, owner }) {
33
31
  );
34
32
  set(context, assets, isList ? updated : updated.shift());
35
33
  } catch (err) {
36
- return next(new Error('INTERNAL', err.message));
34
+ return next(new Error('INTERNAL', err.message, { persist: true }));
37
35
  }
38
36
 
39
37
  next();
@@ -1,8 +1,7 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
3
  const getListField = require('@ocap/util/lib/get-list-field');
4
-
5
- const Error = require('../error');
4
+ const Error = require('@ocap/util/lib/error');
6
5
 
7
6
  // 因为 statedb 层读取数据时会自动获取迁移之后的账户,所以这里可以直接对比 state,而不用检查 migratedTo
8
7
  module.exports = function CreateVerifyAccountMigrationPipe({ signerKey = 'signerStates', senderKey = 'senderState' }) {
@@ -11,7 +10,7 @@ module.exports = function CreateVerifyAccountMigrationPipe({ signerKey = 'signer
11
10
 
12
11
  const sender = get(context, senderKey);
13
12
  if (sender && sender.address !== tx.from) {
14
- return next(new Error('ACCOUNT_MIGRATED', `Sender account ${sender.address} is migrated`));
13
+ return next(new Error('ACCOUNT_MIGRATED', `Sender account ${sender.address} is migrated`, { persist: true }));
15
14
  }
16
15
 
17
16
  const signers = get(context, signerKey);
@@ -20,7 +19,9 @@ module.exports = function CreateVerifyAccountMigrationPipe({ signerKey = 'signer
20
19
  for (const signature of signatures) {
21
20
  const state = signers.find((x) => x.address === signature.signer);
22
21
  if (!state || state.address !== signature.signer) {
23
- return next(new Error('ACCOUNT_MIGRATED', `Signer account ${signature.signer} is migrated`));
22
+ return next(
23
+ new Error('ACCOUNT_MIGRATED', `Signer account ${signature.signer} is migrated`, { persist: true })
24
+ );
24
25
  }
25
26
  }
26
27
  }
@@ -3,14 +3,13 @@
3
3
  const get = require('lodash/get');
4
4
  const set = require('lodash/set');
5
5
  const isEmpty = require('empty-value');
6
+ const Error = require('@ocap/util/lib/error');
6
7
  const getListField = require('@ocap/util/lib/get-list-field');
7
8
  const { toDelegateAddress } = require('@arcblock/did-util');
8
9
 
9
10
  // eslint-disable-next-line global-require
10
11
  const debug = require('debug')(`${require('../../package.json').name}:pipe:verify-delegation`);
11
12
 
12
- const Error = require('../error');
13
-
14
13
  // Must be piped after `extract-state` or `extract-signer` pipe
15
14
  module.exports = function CreateVerifyDelegationPipe({
16
15
  type = 'signature',
@@ -21,7 +20,7 @@ module.exports = function CreateVerifyDelegationPipe({
21
20
  return async function VerifyDelegation(context, next) {
22
21
  const { tx, txType, statedb } = context;
23
22
 
24
- const getDelegationState = (signerState, delegatorAddress, delegatorState) => {
23
+ const getDelegationState = (signerState, delegatorState) => {
25
24
  if (delegatorState) {
26
25
  const address = toDelegateAddress(signerState.address, delegatorState.address);
27
26
  debug('query', { from: signerState.address, to: signerState.address, address });
@@ -49,14 +48,21 @@ module.exports = function CreateVerifyDelegationPipe({
49
48
  const signerState = get(context, signerKey);
50
49
  const delegatorState = get(context, delegatorKey);
51
50
 
52
- const delegationState = await getDelegationState(signerState, tx.delegator, delegatorState);
51
+ const delegationState = await getDelegationState(signerState, delegatorState);
53
52
  if (!delegationState) {
54
- return next(new Error('INVALID_DELEGATION', 'No delegation found between sender and delegator'));
53
+ console.error('invalid_delegation', {
54
+ signerAddress: signerState.address,
55
+ delegatorAddress: get(delegatorState, 'address'),
56
+ });
57
+
58
+ return next(
59
+ new Error('INVALID_DELEGATION', 'No delegation found between sender and delegator', { persist: true })
60
+ );
55
61
  }
56
62
 
57
63
  const errorCode = await checkDelegation(delegationState);
58
64
  if (errorCode) {
59
- return next(new Error(errorCode, 'Delegation check failed'));
65
+ return next(new Error(errorCode, 'Delegation check failed', { persist: true }));
60
66
  }
61
67
 
62
68
  set(context, delegationKey, delegationState);
@@ -79,14 +85,21 @@ module.exports = function CreateVerifyDelegationPipe({
79
85
  const signerState = signerStates.find((x) => x.address === signer);
80
86
  const delegatorState = delegatorStates.find((x) => x.address === delegator);
81
87
 
82
- const delegationState = await getDelegationState(signerState, delegator, delegatorState);
88
+ const delegationState = await getDelegationState(signerState, delegatorState);
83
89
  if (!delegationState) {
84
- return next(new Error('INVALID_DELEGATION', 'No delegation found between signer and delegator'));
90
+ console.error('invalid_delegation', {
91
+ signerAddress: signerState.address,
92
+ delegatorAddress: get(delegatorState, 'address'),
93
+ });
94
+
95
+ return next(
96
+ new Error('INVALID_DELEGATION', 'No delegation found between signer and delegator', { persist: true })
97
+ );
85
98
  }
86
99
 
87
100
  const errorCode = await checkDelegation(delegationState);
88
101
  if (errorCode) {
89
- return next(new Error(errorCode, 'Delegation check failed'));
102
+ return next(new Error(errorCode, 'Delegation check failed', { persist: true }));
90
103
  }
91
104
 
92
105
  delegationStates.push(delegationState);
@@ -1,12 +1,11 @@
1
- /* eslint-disable no-restricted-syntax */
2
- const Error = require('../error');
1
+ const Error = require('@ocap/util/lib/error');
3
2
 
4
3
  module.exports = function CreateVerifyInfoPipe(conditions) {
5
- return function VerifyInfo(context, next) {
4
+ return async function VerifyInfo(context, next) {
6
5
  const checks = Array.isArray(conditions) ? conditions : [conditions];
7
6
  for (const check of checks) {
8
7
  if (!check.fn(context)) {
9
- return next(new Error(check.error, check.message || 'Transaction info verify failed'));
8
+ return next(new Error(check.error, check.message || check.error, { persist: !!check.persist }));
10
9
  }
11
10
  }
12
11
 
@@ -0,0 +1,19 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ const get = require('lodash/get');
3
+ const Error = require('@ocap/util/lib/error');
4
+
5
+ module.exports = function CreateVerifyListSizePipe({ listKey }) {
6
+ return function VerifyListSize(context, next) {
7
+ const maxListSize = get(context, 'config.transaction.maxListSize');
8
+
9
+ const keys = Array.isArray(listKey) ? listKey : [listKey];
10
+ for (const key of keys) {
11
+ const tmp = get(context, key);
12
+ if (Array.isArray(tmp) && tmp.length > maxListSize) {
13
+ return next(new Error('INVALID_TX_SIZE', `Size of ${listKey} can not exceeded ${maxListSize} items`));
14
+ }
15
+ }
16
+
17
+ next();
18
+ };
19
+ };
@@ -0,0 +1,71 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ const get = require('lodash/get');
3
+ const omit = require('lodash/omit');
4
+ const cloneDeep = require('lodash/cloneDeep');
5
+ const Error = require('@ocap/util/lib/error');
6
+ const getListField = require('@ocap/util/lib/get-list-field');
7
+ const { toTypeInfo } = require('@arcblock/did');
8
+ const { fromPublicKey } = require('@ocap/wallet');
9
+ const { createMessage, decodeAny } = require('@ocap/message');
10
+
11
+ module.exports = function CreateVerifyMultiSigV2Pipe({ signersKey }) {
12
+ return function VerifyMultiSigV2(context, next) {
13
+ const signers = get(context, signersKey);
14
+ if (signers.length === 0) {
15
+ return next();
16
+ }
17
+
18
+ // Verify signature count
19
+ const tx = cloneDeep(context.tx);
20
+ const signatures = getListField(tx, 'signatures');
21
+ if (signatures.length !== signers.length) {
22
+ return next(new Error('INVALID_SIGNATURE', `Expect ${signers.length} signatures but found ${signatures.length}`));
23
+ }
24
+
25
+ // Ensure each signer has signed
26
+ for (const signer of signers) {
27
+ const sig = signatures.find((x) => [x.delegator, x.signer].includes(signer));
28
+ if (!sig) {
29
+ return next(new Error('INVALID_SIGNATURE', `Signature for ${signer} not found in tx.signatures`));
30
+ }
31
+ }
32
+
33
+ // Cleanup signatures for later verification
34
+ signatures.forEach((sig) => {
35
+ const decoded = sig.data ? decodeAny(sig.data) : sig.data;
36
+ // HACK: this exist because duplicate serialization of Any type data for json/vc
37
+ if (decoded && ['json', 'vc'].includes(decoded.type)) {
38
+ try {
39
+ decoded.value = JSON.parse(Buffer.from(decoded.value, 'base64'));
40
+ } catch (e) {
41
+ // Do nothing
42
+ }
43
+ }
44
+
45
+ sig.data = decoded;
46
+ });
47
+
48
+ // Verify Signature content
49
+ tx.signature = '';
50
+ tx.signaturesList = signatures.map((x) => omit(x, 'signature'));
51
+ for (let i = 0; i < signatures.length; i++) {
52
+ const { signer, pk, delegator, signature } = signatures[i];
53
+
54
+ // if delegator is empty, pk and signer shall be a pair
55
+ // if delegator is not empty, pk and delegator shall be a pair
56
+ const address = delegator || signer;
57
+ const wallet = fromPublicKey(pk, toTypeInfo(address));
58
+
59
+ const message = createMessage('Transaction', tx);
60
+ try {
61
+ if (wallet.verify(message.serializeBinary(), signature) === false) {
62
+ return next(new Error('INVALID_SIGNATURE', `Signature for ${address} is not valid`));
63
+ }
64
+ } catch (err) {
65
+ return next(new Error('INVALID_SIGNATURE', `Signature for ${address} verify failed: ${err.message}`));
66
+ }
67
+ }
68
+
69
+ next();
70
+ };
71
+ };
@@ -1,11 +1,10 @@
1
1
  const cloneDeep = require('lodash/cloneDeep');
2
+ const Error = require('@ocap/util/lib/error');
2
3
  const getListField = require('@ocap/util/lib/get-list-field');
3
4
  const { toTypeInfo } = require('@arcblock/did');
4
5
  const { fromPublicKey } = require('@ocap/wallet');
5
6
  const { createMessage, decodeAny } = require('@ocap/message');
6
7
 
7
- const Error = require('../error');
8
-
9
8
  module.exports = function CreateVerifyMultiSigPipe(numSigs = 0) {
10
9
  return function VerifyMultiSig(context, next) {
11
10
  const tx = cloneDeep(context.tx);
@@ -23,6 +22,9 @@ module.exports = function CreateVerifyMultiSigPipe(numSigs = 0) {
23
22
 
24
23
  while (signatures.length > 0) {
25
24
  const { signer, pk, delegator, signature, data } = signatures.shift();
25
+
26
+ // if delegator is empty, pk and signer shall be a pair
27
+ // if delegator is not empty, pk and delegator shall be a pair
26
28
  const address = delegator || signer;
27
29
  const wallet = fromPublicKey(pk, toTypeInfo(address));
28
30
 
@@ -1,13 +1,13 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
- const Error = require('../error');
3
+ const Error = require('@ocap/util/lib/error');
4
4
 
5
5
  module.exports = function CreateVerifySenderPipe({ state = 'senderState' } = {}) {
6
6
  return function VerifySender(context, next) {
7
7
  const sender = get(context, state);
8
8
  if (context.txType === 'fg:t:declare') {
9
9
  if (sender) {
10
- return next(new Error('INVALID_SENDER_STATE', 'Sender account already exist in ledger'));
10
+ return next(new Error('INVALID_SENDER_STATE', 'Sender account already exist in ledger', { persist: true }));
11
11
  }
12
12
 
13
13
  return next();
@@ -17,6 +17,6 @@ module.exports = function CreateVerifySenderPipe({ state = 'senderState' } = {})
17
17
  return next();
18
18
  }
19
19
 
20
- return next(new Error('INVALID_SENDER_STATE', 'Sender account not exist in ledger'));
20
+ return next(new Error('INVALID_SENDER_STATE', 'Sender account not exist in ledger', { persist: true }));
21
21
  };
22
22
  };
@@ -0,0 +1,15 @@
1
+ const Error = require('@ocap/util/lib/error');
2
+ const { BN, fromTokenToUnit } = require('@ocap/util');
3
+
4
+ module.exports = function VerifyServiceFee(context, next) {
5
+ const { tx, config, txType } = context;
6
+ const fee = config.transaction.txFee[txType] || 0;
7
+ const expected = fromTokenToUnit(fee, config.token.decimal);
8
+ const actual = new BN(tx.serviceFee || 0);
9
+
10
+ if (actual.eq(expected) === false) {
11
+ return next(new Error('INVALID_SERVICE_FEE', 'Service fee for tx does not match chain config'));
12
+ }
13
+
14
+ next();
15
+ };
@@ -1,34 +1,38 @@
1
+ const get = require('lodash/get');
1
2
  const cloneDeep = require('lodash/cloneDeep');
3
+ const Error = require('@ocap/util/lib/error');
2
4
  const { toTypeInfo, isFromPublicKey } = require('@arcblock/did');
3
5
  const { fromPublicKey } = require('@ocap/wallet');
4
6
  const { createMessage } = require('@ocap/message');
5
7
 
6
- const Error = require('../error');
7
-
8
8
  module.exports = function VerifySignature(context, next) {
9
9
  const tx = cloneDeep(context.tx);
10
10
  const { signature } = tx;
11
11
 
12
12
  delete tx.signature;
13
- delete tx.signatures;
14
- delete tx.signaturesList;
13
+ // The latest multi sig is done before the sender signature
14
+ const multiSignV2Txs = get(context, 'config.transaction.multiSignV2Txs', []);
15
+ if (multiSignV2Txs.includes(context.txType) === false) {
16
+ delete tx.signatures;
17
+ delete tx.signaturesList;
18
+ }
15
19
 
16
20
  // if delegator is empty, pk / from address shall be a pair
17
21
  // if delegator is not empty, pk / delegator address shall be a pair
18
22
  const { pk, delegator, from } = tx;
19
23
  const signer = delegator || from;
20
24
  if (isFromPublicKey(signer, pk) === false) {
21
- return next(new Error('INVALID_SIGNATURE', 'Signature is invalid: signer and pk does not match'));
25
+ return next(new Error('INVALID_SIGNATURE', 'signer and pk does not match'));
22
26
  }
23
27
 
24
28
  const message = createMessage('Transaction', tx);
25
29
  const wallet = fromPublicKey(pk, toTypeInfo(signer));
26
30
  try {
27
31
  if (wallet.verify(message.serializeBinary(), signature) === false) {
28
- return next(new Error('INVALID_SIGNATURE', 'Signature is invalid'));
32
+ return next(new Error('INVALID_SIGNATURE', 'tx.signature is invalid'));
29
33
  }
30
34
  } catch (err) {
31
- return next(new Error('INVALID_SIGNATURE', 'Signature is invalid'));
35
+ return next(new Error('INVALID_SIGNATURE', 'tx.signature verify failed'));
32
36
  }
33
37
 
34
38
  next();
@@ -1,12 +1,11 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
+ const Error = require('@ocap/util/lib/error');
3
4
  const getListField = require('@ocap/util/lib/get-list-field');
4
5
 
5
6
  // eslint-disable-next-line global-require
6
7
  const debug = require('debug')(`${require('../../package.json').name}:pipe:verify-signer`);
7
8
 
8
- const Error = require('../error');
9
-
10
9
  module.exports = function CreateVerifySignerPipe({ signer }) {
11
10
  return function VerifySigner(context, next) {
12
11
  const expected = get(context, signer);
@@ -19,7 +18,8 @@ module.exports = function CreateVerifySignerPipe({ signer }) {
19
18
  return next(new Error('INVALID_TX', 'Multisig can not be empty'));
20
19
  }
21
20
 
22
- const [{ signer: actual }] = signatures;
21
+ const [{ signer: signerDid, delegator }] = signatures;
22
+ const actual = delegator || signerDid;
23
23
  debug('verify', { expected, actual });
24
24
  if (actual !== expected) {
25
25
  return next(new Error('INVALID_TX', `Expect multisig to be signed by ${expected} but got ${actual}`));
@@ -0,0 +1,73 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ const get = require('lodash/get');
3
+ const isEmpty = require('empty-value');
4
+ const Error = require('@ocap/util/lib/error');
5
+ const getListField = require('@ocap/util/lib/get-list-field');
6
+ const { BN } = require('@ocap/util');
7
+
8
+ module.exports = function CreateVerifyTokenBalancePipe({ ownerKey, conditionKey, tokensKey = 'tokens' }) {
9
+ return function VerifyTokenBalance(context, next) {
10
+ let conditions = get(context, conditionKey);
11
+ if (isEmpty(conditions)) {
12
+ return next();
13
+ }
14
+
15
+ let owners = get(context, ownerKey, []);
16
+ if (isEmpty(owners)) {
17
+ return next(new Error('INVALID_TX', 'Token owner not found', { persist: true }));
18
+ }
19
+
20
+ conditions = Array.isArray(conditions) ? conditions : [conditions];
21
+ owners = Array.isArray(owners) ? owners : [owners];
22
+
23
+ for (const condition of conditions) {
24
+ const owner = owners.find((x) => x.address === condition.owner);
25
+ if (!owner) {
26
+ return next(new Error('INVALID_TX', `Token owner ${condition.owner} not found`, { persist: true }));
27
+ }
28
+
29
+ const { tokens: actual } = owner;
30
+ const requirements = getListField(condition, tokensKey);
31
+
32
+ for (const requirement of requirements) {
33
+ const { address, value } = requirement;
34
+
35
+ // ensure requirement is valid
36
+ if (!address || !value) {
37
+ return next(
38
+ new Error('INTERNAL', 'Invalid requirement params provided for VerifyTokenBalance pipe', { persist: true })
39
+ );
40
+ }
41
+
42
+ // ensure secondary token
43
+ if (isEmpty(actual)) {
44
+ return next(
45
+ new Error('INSUFFICIENT_FUND', `Account(${owner.address}) does not own any token`, { persist: true })
46
+ );
47
+ }
48
+
49
+ if (!actual[address]) {
50
+ return next(
51
+ new Error('INSUFFICIENT_FUND', `Account(${owner.address}) does not own token(${address})`, {
52
+ persist: true,
53
+ })
54
+ );
55
+ }
56
+
57
+ const expectedBalance = new BN(value);
58
+ const actualBalance = new BN(actual[address]);
59
+ if (actualBalance.lt(expectedBalance)) {
60
+ return next(
61
+ new Error(
62
+ 'INSUFFICIENT_FUND',
63
+ `Account(${owner.address}) does not have enough token(${address}) to complete the transaction, expected (${expectedBalance}), actual (${actualBalance})`,
64
+ { persist: true }
65
+ )
66
+ );
67
+ }
68
+ }
69
+ }
70
+
71
+ next();
72
+ };
73
+ };
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
- const Error = require('../error');
3
+ const Error = require('@ocap/util/lib/error');
4
4
 
5
5
  module.exports = function CreateVerifyTransferrablePipe({ assets }) {
6
6
  return function VerifyTransferrable(context, next) {
@@ -8,7 +8,9 @@ module.exports = function CreateVerifyTransferrablePipe({ assets }) {
8
8
  items = (Array.isArray(items) ? items : [items]).filter(Boolean);
9
9
  for (const item of items) {
10
10
  if (item.transferrable === false) {
11
- return next(new Error('UNTRANSFERRABLE_ASSET', `Asset ${item.address} is not transferrable`));
11
+ return next(
12
+ new Error('UNTRANSFERRABLE_ASSET', `Asset ${item.address} is not transferrable`, { persist: true })
13
+ );
12
14
  }
13
15
  }
14
16
 
@@ -0,0 +1,79 @@
1
+ /* eslint-disable no-restricted-syntax */
2
+ const get = require('lodash/get');
3
+ const isEmpty = require('empty-value');
4
+ const { BN } = require('@ocap/util');
5
+ const Error = require('@ocap/util/lib/error');
6
+ const getListField = require('@ocap/util/lib/get-list-field');
7
+ const createSortedList = require('@ocap/util/lib/create-sorted-list');
8
+
9
+ const ZERO = new BN(0);
10
+
11
+ module.exports = function CreateVerifyTxInputPipe({
12
+ fieldKey = 'itx.inputs',
13
+ inputsKey = 'inputs',
14
+ sendersKey = 'senders',
15
+ tokensKey = 'tokens',
16
+ assetsKey = 'assets',
17
+ }) {
18
+ return function VerifyTxInput(context, next) {
19
+ const inputs = getListField(context, fieldKey);
20
+ const getTokens = (x) => x.tokensList || x.tokens;
21
+ const getAssets = (x) => x.assetsList || x.assets;
22
+
23
+ // For backwards compatibility: merge primary and secondary tokens
24
+ const address = get(context, 'config.token.address');
25
+ inputs.forEach((x) => {
26
+ getTokens(x).forEach((t) => {
27
+ if (!t.address) {
28
+ t.address = address;
29
+ }
30
+ });
31
+ });
32
+
33
+ const senders = createSortedList(inputs.map((x) => x.owner));
34
+ const tokens = createSortedList(inputs.map((x) => getTokens(x).map((t) => t.address)));
35
+ const assets = createSortedList(inputs.map((x) => getAssets(x)));
36
+
37
+ const checks = [
38
+ {
39
+ error: 'INSUFFICIENT_DATA',
40
+ message: `${fieldKey} should not be empty`,
41
+ fn: () => !isEmpty(inputs),
42
+ },
43
+ {
44
+ error: 'INVALID_TX',
45
+ message: `${fieldKey} should not contain duplicate owner`,
46
+ fn: () => inputs.length === senders.length,
47
+ },
48
+ {
49
+ error: 'INVALID_TX',
50
+ message: `Each ${fieldKey} item should not contain duplicate token`,
51
+ fn: () =>
52
+ inputs.every((x) => createSortedList(getTokens(x).map((t) => t.address)).length === getTokens(x).length),
53
+ },
54
+ {
55
+ error: 'INVALID_TX',
56
+ message: `Each ${fieldKey} item should not contain duplicate asset`,
57
+ fn: () => inputs.every((x) => createSortedList(getAssets(x)).length === getAssets(x).length),
58
+ },
59
+ {
60
+ error: 'INVALID_TX',
61
+ message: `Token amount must be greater than 0 in each ${fieldKey} item`,
62
+ fn: () => inputs.every((x) => getTokens(x).every(({ value }) => new BN(value).gt(ZERO))),
63
+ },
64
+ ];
65
+
66
+ for (const { error, message, fn } of checks) {
67
+ if (!fn()) {
68
+ return next(new Error(error, message));
69
+ }
70
+ }
71
+
72
+ context[inputsKey] = inputs;
73
+ context[sendersKey] = senders;
74
+ context[tokensKey] = tokens;
75
+ context[assetsKey] = assets;
76
+
77
+ next();
78
+ };
79
+ };
@@ -1,12 +1,15 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
- const Error = require('../error');
3
+ const Error = require('@ocap/util/lib/error');
4
4
 
5
5
  module.exports = function VerifyTxSize(context, next) {
6
- const defaultSizeLimit = get(context, 'config.transaction.maxTxSize.default');
7
- const sizeLimit = get(context, `config.transaction.maxTxSize.${context.tx.itx.typeUrl}`, defaultSizeLimit);
8
- if (context.txSize > sizeLimit) {
9
- return next(new Error('INVALID_TX_SIZE', `Transaction size exceeded ${sizeLimit} bytes`));
6
+ const { txSize, txType, tx, config } = context;
7
+ const defaultSizeLimit = get(config, 'transaction.maxTxSize.default');
8
+ const sizeLimit = get(config, `transaction.maxTxSize.${tx.itx.typeUrl}`, defaultSizeLimit);
9
+ if (txSize > sizeLimit) {
10
+ return next(
11
+ new Error('INVALID_TX_SIZE', `Transaction size too large for ${txType}: expect ${sizeLimit}, got ${txSize}`)
12
+ );
10
13
  }
11
14
 
12
15
  next();
@@ -1,9 +1,9 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
3
  const isEmpty = require('empty-value');
4
+ const Error = require('@ocap/util/lib/error');
4
5
  const getListField = require('@ocap/util/lib/get-list-field');
5
6
  const { isFromPublicKey } = require('@arcblock/did');
6
- const Error = require('../error');
7
7
 
8
8
  module.exports = function VerifyTx(context, next) {
9
9
  // 0. validate required fields
@@ -17,8 +17,9 @@ module.exports = function VerifyTx(context, next) {
17
17
  const { itx, chainId, nonce, pk, from, delegator } = context.tx;
18
18
 
19
19
  // 1. validate field values
20
- if (chainId !== get(context, 'config.chainId')) {
21
- return next(new Error('INVALID_CHAIN_ID', 'Invalid chain id'));
20
+ const expected = get(context, 'config.chainId');
21
+ if (chainId !== expected) {
22
+ return next(new Error('INVALID_CHAIN_ID', `Invalid chain id: expected ${expected}, got ${chainId}`));
22
23
  }
23
24
  const n = Number(nonce);
24
25
  // eslint-disable-next-line no-restricted-globals
@@ -1,13 +1,11 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  const get = require('lodash/get');
3
+ const Error = require('@ocap/util/lib/error');
4
+ const getRelatedAddresses = require('@ocap/util/lib/get-related-addr');
3
5
 
4
6
  // eslint-disable-next-line global-require
5
7
  const debug = require('debug')(`${require('../../package.json').name}:pipe:verify-owner`);
6
8
 
7
- const Error = require('../error');
8
-
9
- const getRelatedAddresses = (state) => [state.address].concat(state.migratedFrom || []).filter(Boolean);
10
-
11
9
  /**
12
10
  * Usage scenarios
13
11
  * - Verify the asset owner, only the owner can change ownership of the asset
@@ -21,18 +19,32 @@ const getRelatedAddresses = (state) => [state.address].concat(state.migratedFrom
21
19
  module.exports = function CreateVerifyUpdaterPipe({ assetKey, ownerKey, updaterKey = 'owner' }) {
22
20
  return function VerifyUpdater(context, next) {
23
21
  const owner = get(context, ownerKey);
24
- const ownerAddress = typeof owner === 'string' ? [owner] : getRelatedAddresses(owner);
25
- const updaterKeys = typeof updaterKey === 'string' ? [updaterKey] : updaterKey;
26
-
27
22
  let assets = get(context, assetKey);
28
23
  assets = (Array.isArray(assets) ? assets : [assets]).filter(Boolean);
29
24
 
25
+ if (!owner) {
26
+ // If owner is not found, but assets is not empty, we should throw an error
27
+ if (assets.length) {
28
+ return next(new Error('INVALID_OWNER', 'Asset owner is empty', { persist: true }));
29
+ }
30
+
31
+ // Otherwise, we can continue
32
+ return next();
33
+ }
34
+
35
+ const ownerAddress = typeof owner === 'string' ? [owner] : getRelatedAddresses(owner);
36
+ const updaterKeys = typeof updaterKey === 'string' ? [updaterKey] : updaterKey;
37
+
30
38
  debug('verify', { ownerAddress, assets, updaterKeys });
31
39
 
32
40
  for (const asset of assets) {
33
41
  const updaters = updaterKeys.map((x) => asset[x]).filter(Boolean);
34
42
  if (!updaters.length || updaters.every((updater) => ownerAddress.includes(updater) === false)) {
35
- return next(new Error('INVALID_OWNER', `Asset ${asset.address} can not be updated by ${ownerAddress}`));
43
+ return next(
44
+ new Error('INVALID_OWNER', `Asset ${asset.address} can not be updated by ${ownerAddress}`, {
45
+ persist: true,
46
+ })
47
+ );
36
48
  }
37
49
  }
38
50
 
package/lib/runner.js CHANGED
@@ -1,8 +1,5 @@
1
1
  const EventEmitter = require('events');
2
2
 
3
- // eslint-disable-next-line global-require
4
- const debug = require('debug')(`${require('../package.json').name}:runner`);
5
-
6
3
  /* istanbul ignore next */
7
4
  function nextTick(fn) {
8
5
  // eslint-disable-next-line
@@ -19,38 +16,39 @@ class PipelineRunner extends EventEmitter {
19
16
  this.pipes = [];
20
17
  }
21
18
 
22
- use(pipe) {
23
- this.pipes.push(pipe);
19
+ use(pipe, opts = {}) {
20
+ this.pipes.push({ fn: pipe, opts });
24
21
  }
25
22
 
26
23
  run(context, done) {
27
- const call = async (pipe, err, next) => {
28
- const paramCount = pipe.length;
24
+ const call = async (fn, err, next, opts) => {
25
+ const paramCount = fn.length;
29
26
  const hasError = Boolean(err);
30
27
 
31
28
  try {
32
29
  if (hasError && paramCount === 3) {
33
- // error-handling pipe
34
- await pipe(err, context, next);
35
- debug('call error pipe', pipe.name || '<anonymous>');
30
+ await fn(err, context, next);
36
31
  return;
37
32
  }
38
33
  if (!hasError && paramCount < 3) {
39
- // tx-processing pipe
40
- await pipe(context, next);
41
- debug('call pipe ok', pipe.name || '<anonymous>');
34
+ await fn(context, next);
42
35
  return;
43
36
  }
44
37
 
45
38
  // Here we are skipping remaining
46
39
  } catch (e) {
47
40
  if (process.env.NODE_ENV !== 'test') {
48
- console.error('call pipe failed', pipe.name || '<anonymous>', e);
41
+ console.error('call pipe failed', fn.name || '<anonymous>', e);
49
42
  }
50
43
 
51
44
  // replace the error
52
45
  // eslint-disable-next-line no-param-reassign
53
46
  err = e;
47
+
48
+ if (opts && opts.persistError) {
49
+ err.props = err.props || {};
50
+ err.props.persist = true;
51
+ }
54
52
  }
55
53
 
56
54
  // continue
@@ -69,7 +67,8 @@ class PipelineRunner extends EventEmitter {
69
67
  }
70
68
 
71
69
  // call the pipeline fn
72
- await call(pipe, err, next);
70
+ const { fn, opts } = pipe;
71
+ await call(fn, err, next, opts);
73
72
  };
74
73
 
75
74
  next();
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.6.5",
6
+ "version": "1.6.10",
7
7
  "description": "Pipeline runner and common pipelines to process transactions",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -12,30 +12,33 @@
12
12
  "scripts": {
13
13
  "lint": "eslint tests lib",
14
14
  "lint:fix": "eslint --fix tests lib",
15
- "test": "node tools/jest.js",
16
- "coverage": "npm run test -- --coverage"
15
+ "start": "node tools/start-chain.js",
16
+ "test": "jest --forceExit --detectOpenHandles",
17
+ "test:ci": "jest --forceExit --detectOpenHandles --coverage",
18
+ "coverage": "start-server-and-test start http://127.0.0.1:4002 test:ci"
17
19
  },
18
20
  "keywords": [],
19
21
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
20
22
  "license": "MIT",
21
23
  "devDependencies": {
22
- "jest": "^26.6.3"
24
+ "jest": "^27.3.1",
25
+ "start-server-and-test": "^1.14.0"
23
26
  },
24
27
  "resolutions": {
25
28
  "bn.js": "5.1.3",
26
29
  "elliptic": "6.5.3"
27
30
  },
28
31
  "dependencies": {
29
- "@arcblock/did": "^1.6.5",
30
- "@arcblock/did-util": "^1.6.5",
31
- "@ocap/mcrypto": "^1.6.5",
32
- "@ocap/message": "^1.6.5",
33
- "@ocap/sdk": "^1.6.5",
34
- "@ocap/util": "^1.6.5",
35
- "@ocap/wallet": "^1.6.5",
36
- "debug": "^4.3.1",
32
+ "@arcblock/did": "1.6.10",
33
+ "@arcblock/did-util": "1.6.10",
34
+ "@ocap/mcrypto": "1.6.10",
35
+ "@ocap/message": "1.6.10",
36
+ "@ocap/state": "1.6.10",
37
+ "@ocap/util": "1.6.10",
38
+ "@ocap/wallet": "1.6.10",
39
+ "debug": "^4.3.3",
37
40
  "empty-value": "^1.0.1",
38
41
  "lodash": "^4.17.21"
39
42
  },
40
- "gitHead": "5779448f13824de38978df3c84c9da0c1e1ad989"
43
+ "gitHead": "ab272e8db3a15c6571cc7fae7cc3d3e0fdd4bdb1"
41
44
  }
package/lib/error.js DELETED
@@ -1,13 +0,0 @@
1
- class CustomError extends Error {
2
- constructor(code = 'GENERIC', ...params) {
3
- super(...params);
4
-
5
- if (Error.captureStackTrace) {
6
- Error.captureStackTrace(this, CustomError);
7
- }
8
-
9
- this.code = code;
10
- }
11
- }
12
-
13
- module.exports = CustomError;
@@ -1,11 +0,0 @@
1
- // eslint-disable-next-line global-require
2
- const debug = require('debug')(`${require('../../package.json').name}:pipe:update-delegation-state`);
3
-
4
- // const Error = require('../error');
5
-
6
- module.exports = function CreateUpdatePipe({ stateKey = 'delegationState', statesKey = 'delegationStates' }) {
7
- return function UpdateDelegationState(context, next) {
8
- debug('update delegation state', { stateKey, statesKey });
9
- next();
10
- };
11
- };
@@ -1,20 +0,0 @@
1
- /* eslint-disable no-restricted-syntax */
2
- const get = require('lodash/get');
3
- const { decodeBigInt } = require('@ocap/message');
4
- const { BN } = require('@ocap/util');
5
- const Error = require('../error');
6
-
7
- module.exports = function CreateVerifyBalancePipe({ state, value }) {
8
- return function VerifyBalance(context, next) {
9
- const account = get(context, state);
10
- const raw = get(context, value);
11
- const requirement = new BN(raw ? decodeBigInt(raw) : '0');
12
- const balance = new BN(account.balance);
13
-
14
- if (balance.lt(requirement)) {
15
- return next(new Error('INSUFFICIENT_FUND', 'Account balance not enough to complete the transaction'));
16
- }
17
-
18
- next();
19
- };
20
- };
@@ -1,19 +0,0 @@
1
- /* eslint-disable no-restricted-syntax */
2
- const get = require('lodash/get');
3
- const Error = require('../error');
4
-
5
- module.exports = function CreateVerifyItxSizePipe({ value }) {
6
- return function VerifyItxSize(context, next) {
7
- const maxListSize = get(context, 'config.transaction.maxListSize');
8
-
9
- const keys = Array.isArray(value) ? value : [value];
10
- for (const key of keys) {
11
- const tmp = get(context, key);
12
- if (tmp.length > maxListSize) {
13
- return next(new Error('INVALID_TX_SIZE', `Asset list can not exceeded ${maxListSize} items`));
14
- }
15
- }
16
-
17
- next();
18
- };
19
- };
@@ -1,19 +0,0 @@
1
- const get = require('lodash/get');
2
-
3
- const Error = require('../error');
4
-
5
- // Simple anti-replay on transaction hash with bloom-filter
6
- module.exports = function CreateVerifyReplayPipe({ filter, key = 'txHash' }) {
7
- return function VerifyReplay(context, next) {
8
- const hash = get(context, key);
9
- if (hash) {
10
- if (filter.has(hash)) {
11
- return next(new Error('INVALID_NONCE', 'Can not replay an existing transaction'));
12
- }
13
-
14
- filter.add(hash);
15
- }
16
-
17
- next();
18
- };
19
- };