trac-msb 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/.dockerignore +16 -0
  2. package/.github/workflows/acceptance-tests.yml +7 -0
  3. package/.github/workflows/publish.yml +40 -0
  4. package/.github/workflows/{CI.yml → unit-tests.yml} +1 -1
  5. package/README.md +175 -50
  6. package/docker-compose.yml +16 -0
  7. package/dockerfile +41 -0
  8. package/docs/fee_distribution.md +89 -0
  9. package/msb.mjs +12 -14
  10. package/package.json +8 -4
  11. package/rpc/constants.mjs +4 -1
  12. package/rpc/handlers.mjs +109 -66
  13. package/rpc/routes/v1.mjs +3 -1
  14. package/rpc/rpc_services.js +126 -0
  15. package/rpc/utils/confirmedParameter.mjs +17 -0
  16. package/rpc/utils/url.mjs +38 -0
  17. package/src/core/network/Network.js +27 -10
  18. package/src/core/network/identity/NetworkWalletFactory.js +78 -0
  19. package/src/core/network/services/ConnectionManager.js +2 -2
  20. package/src/core/network/services/ValidatorObserverService.js +7 -4
  21. package/src/core/state/State.js +28 -22
  22. package/src/index.js +197 -385
  23. package/src/utils/cliCommands.js +280 -0
  24. package/src/utils/constants.js +3 -1
  25. package/tests/acceptance/v1/account/account.test.mjs +123 -0
  26. package/tests/acceptance/v1/balance/balance.test.mjs +55 -0
  27. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +111 -0
  28. package/tests/acceptance/v1/confirmed-length/confirmed-length.test.mjs +19 -0
  29. package/tests/acceptance/v1/fee/fee.test.mjs +11 -0
  30. package/tests/acceptance/v1/rpc.test.mjs +62 -291
  31. package/tests/acceptance/v1/tx/tx.test.mjs +98 -0
  32. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +195 -0
  33. package/tests/acceptance/v1/tx-hashes/tx-hashes.test.mjs +72 -0
  34. package/tests/acceptance/v1/tx-payloads-bulk/tx-payloads-bulk.test.mjs +27 -0
  35. package/tests/acceptance/v1/txv/txv.test.mjs +11 -0
  36. package/tests/acceptance/v1/unconfirmed-length/unconfirmed-length.test.mjs +11 -0
  37. package/tests/helpers/StateNetworkFactory.js +157 -0
  38. package/tests/helpers/autobaseTestHelpers.js +369 -0
  39. package/tests/helpers/createTestSignature.js +12 -0
  40. package/tests/helpers/transactionPayloads.mjs +78 -0
  41. package/tests/unit/network/NetworkWalletFactory.test.js +156 -0
  42. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +38 -0
  43. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +273 -0
  44. package/tests/unit/state/apply/addAdmin/adminEntryEncodingFailureScenario.js +30 -0
  45. package/tests/unit/state/apply/addAdmin/adminEntryExistsScenario.js +78 -0
  46. package/tests/unit/state/apply/addAdmin/nodeEntryInitializationFailureScenario.js +30 -0
  47. package/tests/unit/state/apply/addAdmin/nonBootstrapNodeScenario.js +68 -0
  48. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +155 -0
  49. package/tests/unit/state/apply/addIndexer/addIndexerHappyPathScenario.js +39 -0
  50. package/tests/unit/state/apply/addIndexer/addIndexerMultipleIndexersInTheNetworkScenario.js +167 -0
  51. package/tests/unit/state/apply/addIndexer/addIndexerPretenderAlreadyIndexerScenario.js +21 -0
  52. package/tests/unit/state/apply/addIndexer/addIndexerPretenderNotWriterScenario.js +21 -0
  53. package/tests/unit/state/apply/addIndexer/addIndexerRemoveAndReAddScenario.js +186 -0
  54. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +445 -0
  55. package/tests/unit/state/apply/addIndexer/addIndexerWriterKeyAlreadyRegisteredScenario.js +32 -0
  56. package/tests/unit/state/apply/addIndexer/state.apply.addIndexer.test.js +297 -0
  57. package/tests/unit/state/apply/addWriter/addWriterHappyPathScenario.js +41 -0
  58. package/tests/unit/state/apply/addWriter/addWriterInvalidValidatorSignatureScenario.js +32 -0
  59. package/tests/unit/state/apply/addWriter/addWriterNewWkScenario.js +149 -0
  60. package/tests/unit/state/apply/addWriter/addWriterRequesterAlreadyWriterScenario.js +21 -0
  61. package/tests/unit/state/apply/addWriter/addWriterRequesterBalanceInsufficientScenario.js +21 -0
  62. package/tests/unit/state/apply/addWriter/addWriterRequesterEntryDecodeFailureScenario.js +19 -0
  63. package/tests/unit/state/apply/addWriter/addWriterRequesterEntryMissingScenario.js +19 -0
  64. package/tests/unit/state/apply/addWriter/addWriterRequesterIndexerScenario.js +21 -0
  65. package/tests/unit/state/apply/addWriter/addWriterRequesterNotWhitelistedScenario.js +21 -0
  66. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +757 -0
  67. package/tests/unit/state/apply/addWriter/addWriterStakeBalanceUpdateFailureScenario.js +50 -0
  68. package/tests/unit/state/apply/addWriter/addWriterStakeInsufficientBalanceScenario.js +29 -0
  69. package/tests/unit/state/apply/addWriter/addWriterStakeInvalidBalanceScenario.js +29 -0
  70. package/tests/unit/state/apply/addWriter/addWriterStakeInvalidEntryScenario.js +21 -0
  71. package/tests/unit/state/apply/addWriter/addWriterStakeStakedBalanceFailureScenario.js +37 -0
  72. package/tests/unit/state/apply/addWriter/addWriterStakeSubtractFailureScenario.js +42 -0
  73. package/tests/unit/state/apply/addWriter/addWriterValidatorRewardScenario.js +105 -0
  74. package/tests/unit/state/apply/addWriter/addWriterWriterKeyMismatchScenario.js +54 -0
  75. package/tests/unit/state/apply/addWriter/addWriterWriterKeyOwnershipScenario.js +54 -0
  76. package/tests/unit/state/apply/addWriter/addWriterZeroWriterKeyScenario.js +29 -0
  77. package/tests/unit/state/apply/addWriter/state.apply.addWriter.test.js +309 -0
  78. package/tests/unit/state/apply/adminRecovery/adminRecoveryHappyPathScenario.js +30 -0
  79. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +866 -0
  80. package/tests/unit/state/apply/adminRecovery/state.apply.adminRecovery.test.js +439 -0
  81. package/tests/unit/state/apply/appendWhitelist/appendWhitelistBanAndReapplyScenario.js +78 -0
  82. package/tests/unit/state/apply/appendWhitelist/appendWhitelistExistingReaderHappyPathScenario.js +98 -0
  83. package/tests/unit/state/apply/appendWhitelist/appendWhitelistFeeAfterDisableScenario.js +66 -0
  84. package/tests/unit/state/apply/appendWhitelist/appendWhitelistHappyPathScenario.js +55 -0
  85. package/tests/unit/state/apply/appendWhitelist/appendWhitelistInsufficientAdminBalanceScenario.js +103 -0
  86. package/tests/unit/state/apply/appendWhitelist/appendWhitelistNodeAlreadyWhitelistedScenario.js +60 -0
  87. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +191 -0
  88. package/tests/unit/state/apply/appendWhitelist/state.apply.appendWhitelist.test.js +220 -0
  89. package/tests/unit/state/apply/balanceInitialization/balanceInitializationHappyPathScenario.js +82 -0
  90. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +106 -0
  91. package/tests/unit/state/apply/balanceInitialization/invalidAmountScenario.js +45 -0
  92. package/tests/unit/state/apply/balanceInitialization/nodeEntryBalanceUpdateFailureScenario.js +81 -0
  93. package/tests/unit/state/apply/balanceInitialization/state.apply.balanceInitialization.test.js +189 -0
  94. package/tests/unit/state/apply/banValidator/banValidatorBanAndReWhitelistScenario.js +155 -0
  95. package/tests/unit/state/apply/banValidator/banValidatorHappyPathScenario.js +36 -0
  96. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +534 -0
  97. package/tests/unit/state/apply/banValidator/banValidatorSequentialBansScenario.js +74 -0
  98. package/tests/unit/state/apply/banValidator/banValidatorTargetDecodeFailureScenario.js +19 -0
  99. package/tests/unit/state/apply/banValidator/banValidatorTargetIndexerScenario.js +32 -0
  100. package/tests/unit/state/apply/banValidator/banValidatorTargetNodeEntryMissingScenario.js +19 -0
  101. package/tests/unit/state/apply/banValidator/banValidatorTargetRoleUpdateFailureScenario.js +19 -0
  102. package/tests/unit/state/apply/banValidator/banValidatorWhitelistedNonWriterScenario.js +38 -0
  103. package/tests/unit/state/apply/banValidator/banValidatorWhitelistedZeroBalanceScenario.js +91 -0
  104. package/tests/unit/state/apply/banValidator/banValidatorWithdrawFailureScenario.js +19 -0
  105. package/tests/unit/state/apply/banValidator/state.apply.banValidator.test.js +266 -0
  106. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentDuplicateRegistrationScenario.js +142 -0
  107. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentHappyPathScenario.js +26 -0
  108. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentIncompleteOperationScenario.js +94 -0
  109. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentInvalidDeploymentEntryScenario.js +37 -0
  110. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentMultipleBootstrapScenario.js +86 -0
  111. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +344 -0
  112. package/tests/unit/state/apply/bootstrapDeployment/invalidValidatorNodeEntryScenario.js +57 -0
  113. package/tests/unit/state/apply/bootstrapDeployment/state.apply.bootstrapDeployment.test.js +429 -0
  114. package/tests/unit/state/apply/common/access-control/adminConsistencyMismatchScenario.js +119 -0
  115. package/tests/unit/state/apply/common/access-control/adminEntryDecodeFailureScenario.js +130 -0
  116. package/tests/unit/state/apply/common/access-control/adminEntryExistsScenario.js +93 -0
  117. package/tests/unit/state/apply/common/access-control/adminEntryMissingScenario.js +108 -0
  118. package/tests/unit/state/apply/common/access-control/adminOnlyGuardScenario.js +126 -0
  119. package/tests/unit/state/apply/common/access-control/adminPublicKeyDecodeFailureScenario.js +120 -0
  120. package/tests/unit/state/apply/common/access-control/roleAccessOperationValidationScenario.js +50 -0
  121. package/tests/unit/state/apply/common/adminControlOperationValidationScenario.js +56 -0
  122. package/tests/unit/state/apply/common/balances/adminEntryUpdateFailureScenario.js +52 -0
  123. package/tests/unit/state/apply/common/balances/base/requesterBalanceScenarioBase.js +197 -0
  124. package/tests/unit/state/apply/common/balances/feeDecodeFailureScenario.js +52 -0
  125. package/tests/unit/state/apply/common/balances/requesterBalanceDecodeFailureScenario.js +15 -0
  126. package/tests/unit/state/apply/common/balances/requesterBalanceFeeApplicationFailureScenario.js +11 -0
  127. package/tests/unit/state/apply/common/balances/requesterBalanceInsufficientScenario.js +15 -0
  128. package/tests/unit/state/apply/common/balances/requesterBalanceUpdateFailureScenario.js +11 -0
  129. package/tests/unit/state/apply/common/balances/validatorEntryRewardFailureScenario.js +11 -0
  130. package/tests/unit/state/apply/common/balances/validatorEntryUpdateFailureScenario.js +11 -0
  131. package/tests/unit/state/apply/common/balances/validatorNodeEntryDecodeFailureScenario.js +40 -0
  132. package/tests/unit/state/apply/common/base/OperationValidationScenarioBase.js +114 -0
  133. package/tests/unit/state/apply/common/commonScenarioHelper.js +103 -0
  134. package/tests/unit/state/apply/common/indexer/indexerNodeEntryDecodeFailureScenario.js +36 -0
  135. package/tests/unit/state/apply/common/indexer/indexerNodeEntryMissingScenario.js +36 -0
  136. package/tests/unit/state/apply/common/indexer/indexerRoleUpdateFailureScenario.js +29 -0
  137. package/tests/unit/state/apply/common/indexer/indexerSequenceStateInvalidScenario.js +66 -0
  138. package/tests/unit/state/apply/common/invalidMessageComponentValidationScenario.js +84 -0
  139. package/tests/unit/state/apply/common/nodeEntryInitializationFailureScenario.js +47 -0
  140. package/tests/unit/state/apply/common/operationAlreadyAppliedScenario.js +85 -0
  141. package/tests/unit/state/apply/common/payload-structure/addressWithInvalidPublicKeyScenario.js +52 -0
  142. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +49 -0
  143. package/tests/unit/state/apply/common/payload-structure/invalidAddressValidationScenario.js +73 -0
  144. package/tests/unit/state/apply/common/payload-structure/invalidHashValidationScenario.js +71 -0
  145. package/tests/unit/state/apply/common/payload-structure/invalidPayloadValidationScenario.js +31 -0
  146. package/tests/unit/state/apply/common/payload-structure/invalidSignatureValidationScenario.js +142 -0
  147. package/tests/unit/state/apply/common/payload-structure/partialOperationValidationScenario.js +87 -0
  148. package/tests/unit/state/apply/common/requester/requesterNodeEntryBufferMissingScenario.js +70 -0
  149. package/tests/unit/state/apply/common/requester/requesterNodeEntryDecodeFailureScenario.js +72 -0
  150. package/tests/unit/state/apply/common/requester/requesterNodeEntryMissingScenario.js +36 -0
  151. package/tests/unit/state/apply/common/requesterAddressValidationScenario.js +44 -0
  152. package/tests/unit/state/apply/common/requesterPublicKeyValidationScenario.js +25 -0
  153. package/tests/unit/state/apply/common/transactionValidityMismatchScenario.js +98 -0
  154. package/tests/unit/state/apply/common/validatorConsistency/base/validatorConsistencyScenarioBase.js +201 -0
  155. package/tests/unit/state/apply/common/validatorConsistency/validatorEntryDecodeFailureScenario.js +17 -0
  156. package/tests/unit/state/apply/common/validatorConsistency/validatorEntryMissingScenario.js +44 -0
  157. package/tests/unit/state/apply/common/validatorConsistency/validatorInactiveScenario.js +19 -0
  158. package/tests/unit/state/apply/common/validatorConsistency/validatorWriterKeyMismatchScenario.js +18 -0
  159. package/tests/unit/state/apply/common/validatorEntryValidation/base/validatorEntryValidationScenarioBase.js +314 -0
  160. package/tests/unit/state/apply/common/validatorEntryValidation/validatorEntryInvalidBalanceScenario.js +18 -0
  161. package/tests/unit/state/apply/common/writerKeyExistsValidationScenario.js +43 -0
  162. package/tests/unit/state/apply/disableInitialization/disableInitializationAlreadyDisabledScenario.js +53 -0
  163. package/tests/unit/state/apply/disableInitialization/disableInitializationHappyPathScenario.js +24 -0
  164. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +197 -0
  165. package/tests/unit/state/apply/disableInitialization/state.apply.disableInitialization.test.js +161 -0
  166. package/tests/unit/state/apply/missing-tests.md +18 -0
  167. package/tests/unit/state/apply/removeIndexer/removeIndexerHappyPathScenario.js +58 -0
  168. package/tests/unit/state/apply/removeIndexer/removeIndexerReAddAndRemoveAgainScenario.js +98 -0
  169. package/tests/unit/state/apply/removeIndexer/removeIndexerRemoveMultipleIndexersScenario.js +167 -0
  170. package/tests/unit/state/apply/removeIndexer/removeIndexerScenarioHelpers.js +428 -0
  171. package/tests/unit/state/apply/removeIndexer/removeIndexerTargetNotIndexerScenario.js +22 -0
  172. package/tests/unit/state/apply/removeIndexer/removeIndexerWriterKeyMissingScenario.js +20 -0
  173. package/tests/unit/state/apply/removeIndexer/state.apply.removeIndexer.test.js +291 -0
  174. package/tests/unit/state/apply/removeWriter/removeWriterAndAddWriterAgainScenario.js +87 -0
  175. package/tests/unit/state/apply/removeWriter/removeWriterHappyPathScenario.js +38 -0
  176. package/tests/unit/state/apply/removeWriter/removeWriterInvalidValidatorSignatureScenario.js +32 -0
  177. package/tests/unit/state/apply/removeWriter/removeWriterRequesterBalanceInsufficientScenario.js +21 -0
  178. package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryDecodeFailureScenario.js +19 -0
  179. package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryMissingScenario.js +19 -0
  180. package/tests/unit/state/apply/removeWriter/removeWriterRequesterIndexerScenario.js +21 -0
  181. package/tests/unit/state/apply/removeWriter/removeWriterRequesterNotWriterScenario.js +21 -0
  182. package/tests/unit/state/apply/removeWriter/removeWriterRequesterRoleUpdateFailureScenario.js +19 -0
  183. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +344 -0
  184. package/tests/unit/state/apply/removeWriter/removeWriterThroughWriterValidatorScenario.js +113 -0
  185. package/tests/unit/state/apply/removeWriter/removeWriterUnstakeFailureScenario.js +33 -0
  186. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyMismatchScenario.js +21 -0
  187. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyOwnershipScenario.js +26 -0
  188. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyRegistryMissingScenario.js +22 -0
  189. package/tests/unit/state/apply/removeWriter/state.apply.removeWriter.test.js +307 -0
  190. package/tests/unit/state/apply/state.apply.test.js +24 -0
  191. package/tests/unit/state/apply/transfer/state.apply.transfer.test.js +819 -0
  192. package/tests/unit/state/apply/transfer/transferContractSchemaValidationScenario.js +22 -0
  193. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +137 -0
  194. package/tests/unit/state/apply/transfer/transferDoubleSpendSameBatchScenario.js +63 -0
  195. package/tests/unit/state/apply/transfer/transferDoubleSpendSingleValidatorScenario.js +67 -0
  196. package/tests/unit/state/apply/transfer/transferExistingRecipientAmountScenario.js +31 -0
  197. package/tests/unit/state/apply/transfer/transferExistingRecipientZeroAmountScenario.js +31 -0
  198. package/tests/unit/state/apply/transfer/transferHandlerGuardScenarios.js +22 -0
  199. package/tests/unit/state/apply/transfer/transferHappyPathScenario.js +8 -0
  200. package/tests/unit/state/apply/transfer/transferInvalidIncomingDataScenario.js +66 -0
  201. package/tests/unit/state/apply/transfer/transferNewRecipientAmountScenario.js +31 -0
  202. package/tests/unit/state/apply/transfer/transferNewRecipientZeroAmountScenario.js +31 -0
  203. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +1167 -0
  204. package/tests/unit/state/apply/transfer/transferSelfTransferAmountScenario.js +38 -0
  205. package/tests/unit/state/apply/transfer/transferSelfTransferZeroAmountScenario.js +38 -0
  206. package/tests/unit/state/apply/transfer/transferValidatorRecipientAmountScenario.js +38 -0
  207. package/tests/unit/state/apply/transfer/transferValidatorRecipientZeroAmountScenario.js +38 -0
  208. package/tests/unit/state/apply/txOperation/state.apply.txOperation.test.js +318 -0
  209. package/tests/unit/state/apply/txOperation/txOperationBootstrapNotRegisteredScenario.js +70 -0
  210. package/tests/unit/state/apply/txOperation/txOperationDifferentValidatorCreatorHappyPathScenario.js +23 -0
  211. package/tests/unit/state/apply/txOperation/txOperationInvalidDeploymentEntryScenario.js +48 -0
  212. package/tests/unit/state/apply/txOperation/txOperationInvalidFeeAmountScenario.js +39 -0
  213. package/tests/unit/state/apply/txOperation/txOperationInvalidSubnetCreatorAddressScenario.js +46 -0
  214. package/tests/unit/state/apply/txOperation/txOperationRequesterCreatorHappyPathScenario.js +21 -0
  215. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +429 -0
  216. package/tests/unit/state/apply/txOperation/txOperationStandardHappyPathScenario.js +21 -0
  217. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddCreatorBalanceFailureScenario.js +26 -0
  218. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBalanceFailureScenario.js +25 -0
  219. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBonusFailureScenario.js +27 -0
  220. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeCreatorEntryScenario.js +18 -0
  221. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeRequesterEntryScenario.js +17 -0
  222. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeValidatorEntryScenario.js +31 -0
  223. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardBypassScenario.js +49 -0
  224. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardScenarioFactory.js +92 -0
  225. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInsufficientRequesterBalanceScenario.js +28 -0
  226. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidCreatorBalanceScenario.js +29 -0
  227. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterBalanceScenario.js +28 -0
  228. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterEntryScenario.js +17 -0
  229. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidValidatorBalanceScenario.js +33 -0
  230. package/tests/unit/state/apply/txOperation/txOperationTransferFeeMissingCreatorEntryScenario.js +18 -0
  231. package/tests/unit/state/apply/txOperation/txOperationTransferFeeSubtractFailureScenario.js +25 -0
  232. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateCreatorBalanceFailureScenario.js +26 -0
  233. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateFailureScenario.js +25 -0
  234. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBalanceFailureScenario.js +26 -0
  235. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBonusFailureScenario.js +27 -0
  236. package/tests/unit/state/apply/txOperation/txOperationValidatorCreatorHappyPathScenario.js +21 -0
  237. package/tests/unit/state/stateModule.test.js +1 -0
  238. package/tests/unit/state/stateTestUtils.js +5 -1
  239. package/.env +0 -3
@@ -0,0 +1,428 @@
1
+ import b4a from 'b4a';
2
+ import nodeEntryUtils from '../../../../../src/core/state/utils/nodeEntry.js';
3
+ import { BALANCE_FEE, toBalance } from '../../../../../src/core/state/utils/balance.js';
4
+ import addressUtils from '../../../../../src/core/state/utils/address.js';
5
+ import nodeRoleUtils from '../../../../../src/core/state/utils/roles.js';
6
+ import { EntryType } from '../../../../../src/utils/constants.js';
7
+ import { safeDecodeApplyOperation } from '../../../../../src/utils/protobuf/operationHelpers.js';
8
+ import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
9
+ import {
10
+ setupAddIndexerScenario,
11
+ selectIndexerCandidatePeer,
12
+ buildAddIndexerPayload,
13
+ buildRemoveIndexerPayload as buildRemoveIndexerPayloadFromAddHelper,
14
+ buildRemoveIndexerPayloadWithTxValidity as buildRemoveIndexerPayloadWithTxValidityFromAddHelper,
15
+ assertAddIndexerSuccessState
16
+ } from '../addIndexer/addIndexerScenarioHelpers.js';
17
+
18
+ export async function setupRemoveIndexerScenario(t, options = {}) {
19
+ const context = await setupAddIndexerScenario(t, options);
20
+ const adminPeer = context.adminBootstrap;
21
+ const indexerPeer = selectIndexerCandidatePeer(context);
22
+
23
+ const writerEntryBefore = await adminPeer.base.view.get(indexerPeer.wallet.address);
24
+ if (!writerEntryBefore?.value) {
25
+ throw new Error('setupRemoveIndexerScenario requires a writer entry before promotion.');
26
+ }
27
+ const adminEntryBefore = await adminPeer.base.view.get(adminPeer.wallet.address);
28
+ if (!adminEntryBefore?.value) {
29
+ throw new Error('setupRemoveIndexerScenario requires an admin entry before promotion.');
30
+ }
31
+
32
+ const addPayload = await buildAddIndexerPayload(context, { writerPeer: indexerPeer, adminPeer });
33
+ await adminPeer.base.append(addPayload);
34
+ await adminPeer.base.update();
35
+ await eventFlush();
36
+
37
+ await assertAddIndexerSuccessState(t, context, {
38
+ writerPeer: indexerPeer,
39
+ adminPeer,
40
+ writerEntryBefore: { value: b4a.from(writerEntryBefore.value) },
41
+ adminEntryBefore: { value: b4a.from(adminEntryBefore.value) },
42
+ payload: addPayload
43
+ });
44
+
45
+ const indexerEntryBeforeRemoval = await adminPeer.base.view.get(indexerPeer.wallet.address);
46
+ const adminEntryBeforeRemoval = await adminPeer.base.view.get(adminPeer.wallet.address);
47
+ const writersLengthBeforeRemoval = await readWritersLength(adminPeer.base);
48
+
49
+ context.removeIndexerScenario = {
50
+ ...(context.removeIndexerScenario ?? {}),
51
+ indexerPeer,
52
+ indexerEntryBeforeRemoval: indexerEntryBeforeRemoval
53
+ ? { value: b4a.from(indexerEntryBeforeRemoval.value) }
54
+ : null,
55
+ adminEntryBeforeRemoval: adminEntryBeforeRemoval
56
+ ? { value: b4a.from(adminEntryBeforeRemoval.value) }
57
+ : null,
58
+ writersLengthBeforeRemoval
59
+ };
60
+
61
+ return context;
62
+ }
63
+
64
+ export async function buildRemoveIndexerPayload(context, options = {}) {
65
+ return buildRemoveIndexerPayloadFromAddHelper(context, options);
66
+ }
67
+
68
+ export async function buildRemoveIndexerPayloadWithTxValidity(context, mutatedTxValidity, options = {}) {
69
+ return buildRemoveIndexerPayloadWithTxValidityFromAddHelper(context, mutatedTxValidity, options);
70
+ }
71
+
72
+ export async function applyWithRemoveIndexerRoleUpdateFailure(context, invalidPayload) {
73
+ const adminPeer = context.adminBootstrap ?? context.bootstrap ?? context.peers?.[0] ?? null;
74
+ if (!adminPeer?.base) {
75
+ throw new Error('RemoveIndexer role mutation failure scenario requires a writable node.');
76
+ }
77
+
78
+ const originalSetter = nodeEntryUtils.setRoleAndWriterKey;
79
+ let shouldFail = true;
80
+ nodeEntryUtils.setRoleAndWriterKey = function patchedSetRoleAndWriterKey(...args) {
81
+ if (shouldFail) {
82
+ shouldFail = false;
83
+ return null;
84
+ }
85
+ return originalSetter.apply(this, args);
86
+ };
87
+
88
+ try {
89
+ await adminPeer.base.append(invalidPayload);
90
+ await adminPeer.base.update();
91
+ await eventFlush();
92
+ } finally {
93
+ nodeEntryUtils.setRoleAndWriterKey = originalSetter;
94
+ }
95
+ }
96
+
97
+ export async function applyWithoutIndexerMembership(context, invalidPayload) {
98
+ const adminPeer = context.adminBootstrap ?? context.bootstrap ?? context.peers?.[0] ?? null;
99
+ const indexerPeer = context.removeIndexerScenario?.indexerPeer ?? selectIndexerCandidatePeer(context);
100
+ if (!adminPeer?.base || !indexerPeer?.wallet?.address) {
101
+ throw new Error('RemoveIndexer missing indexer membership scenario requires a writable admin and indexer peer.');
102
+ }
103
+
104
+ const targetAddress = indexerPeer.wallet.address;
105
+ const targetAddressBuffer = addressUtils.addressToBuffer(targetAddress);
106
+ if (!targetAddressBuffer) {
107
+ throw new Error('RemoveIndexer missing indexer membership scenario requires a decodable indexer address.');
108
+ }
109
+
110
+ const replacementWriterKey = b4a.alloc(32, 0xaa);
111
+ const originalApply = adminPeer.base._handlers.apply;
112
+
113
+ adminPeer.base._handlers.apply = async function patchedApply(nodes, view, baseCtx) {
114
+ let mutatedOnce = false;
115
+ const originalBatch = view.batch;
116
+ view.batch = function patchedBatch(...args) {
117
+ const batch = originalBatch.apply(this, args);
118
+ if (!batch?.get) return batch;
119
+
120
+ const originalGet = batch.get.bind(batch);
121
+ batch.get = async key => {
122
+ if (mutatedOnce) return originalGet(key);
123
+
124
+ const isTarget =
125
+ (typeof key === 'string' && key === targetAddress) ||
126
+ (b4a.isBuffer(key) && b4a.equals(key, targetAddressBuffer));
127
+ if (!isTarget) return originalGet(key);
128
+
129
+ const entry = await originalGet(key);
130
+ if (!entry?.value) {
131
+ throw new Error('RemoveIndexer missing indexer membership scenario requires an existing indexer entry.');
132
+ }
133
+
134
+ const mutated = nodeEntryUtils.setRoleAndWriterKey(
135
+ b4a.from(entry.value),
136
+ nodeRoleUtils.NodeRole.INDEXER,
137
+ replacementWriterKey
138
+ );
139
+ if (!mutated) {
140
+ throw new Error('Failed to mutate indexer writing key.');
141
+ }
142
+
143
+ mutatedOnce = true;
144
+ return { ...entry, value: mutated };
145
+ };
146
+
147
+ return batch;
148
+ };
149
+
150
+ try {
151
+ return await originalApply.call(this, nodes, view, baseCtx);
152
+ } finally {
153
+ view.batch = originalBatch;
154
+ }
155
+ };
156
+
157
+ try {
158
+ await adminPeer.base.append(invalidPayload);
159
+ await adminPeer.base.update();
160
+ await eventFlush();
161
+ } finally {
162
+ adminPeer.base._handlers.apply = originalApply;
163
+ }
164
+ }
165
+
166
+ export async function assertRemoveIndexerSuccessState(
167
+ t,
168
+ context,
169
+ {
170
+ indexerPeer = context.removeIndexerScenario?.indexerPeer ?? selectIndexerCandidatePeer(context),
171
+ adminPeer = context.adminBootstrap,
172
+ indexerEntryBefore = context.removeIndexerScenario?.indexerEntryBeforeRemoval,
173
+ adminEntryBefore = context.removeIndexerScenario?.adminEntryBeforeRemoval,
174
+ payload,
175
+ writersLengthBefore = context.removeIndexerScenario?.writersLengthBeforeRemoval,
176
+ skipSync = false
177
+ } = {}
178
+ ) {
179
+ if (!indexerEntryBefore?.value) {
180
+ throw new Error('assertRemoveIndexerSuccessState requires the indexer entry before removal.');
181
+ }
182
+ if (!adminEntryBefore?.value) {
183
+ throw new Error('assertRemoveIndexerSuccessState requires the admin entry before removal.');
184
+ }
185
+ if (!payload) {
186
+ throw new Error('assertRemoveIndexerSuccessState requires the removeIndexer payload.');
187
+ }
188
+ if (typeof writersLengthBefore !== 'number') {
189
+ throw new Error('assertRemoveIndexerSuccessState requires writersLengthBefore.');
190
+ }
191
+
192
+ const decodedBefore = nodeEntryUtils.decode(indexerEntryBefore.value);
193
+ t.ok(decodedBefore, 'indexer entry before removeIndexer decodes');
194
+ if (!decodedBefore) return;
195
+ t.is(decodedBefore.isWhitelisted, true, 'indexer is whitelisted before removal');
196
+ t.is(decodedBefore.isWriter, true, 'indexer retains writer role before removal');
197
+ t.is(decodedBefore.isIndexer, true, 'indexer role set before removal');
198
+
199
+ const indexerEntryAfter = await adminPeer.base.view.get(indexerPeer.wallet.address);
200
+ t.ok(indexerEntryAfter, 'indexer entry exists after removeIndexer');
201
+ const decodedAfter = nodeEntryUtils.decode(indexerEntryAfter?.value);
202
+ t.ok(decodedAfter, 'indexer entry decodes after removeIndexer');
203
+ if (!decodedAfter) return;
204
+ t.is(decodedAfter.isWhitelisted, true, 'indexer remains whitelisted after removal');
205
+ t.is(decodedAfter.isWriter, true, 'node downgraded to writer after removeIndexer');
206
+ t.is(decodedAfter.isIndexer, false, 'indexer flag cleared after removeIndexer');
207
+ t.ok(b4a.equals(decodedAfter.wk, decodedBefore.wk), 'writer key preserved after removeIndexer');
208
+ t.ok(
209
+ b4a.equals(decodedAfter.balance, decodedBefore.balance),
210
+ 'node balance preserved after removeIndexer'
211
+ );
212
+ t.ok(
213
+ b4a.equals(decodedAfter.stakedBalance, decodedBefore.stakedBalance),
214
+ 'node staked balance preserved after removeIndexer'
215
+ );
216
+ t.ok(b4a.equals(decodedAfter.license, decodedBefore.license), 'license preserved after removeIndexer');
217
+
218
+ await assertWriterRegistry(t, adminPeer.base, decodedBefore.wk, indexerPeer.wallet.address);
219
+ await assertWriterIndexUpdates(t, adminPeer.base, writersLengthBefore, indexerPeer.wallet.address);
220
+ await assertAdminFeeDeducted(t, adminPeer.base, adminEntryBefore.value, adminPeer.wallet.address);
221
+ await assertRemoveIndexerPayloadMetadata(
222
+ t,
223
+ adminPeer.base,
224
+ payload,
225
+ adminPeer.wallet.address,
226
+ indexerPeer.wallet.address
227
+ );
228
+ assertIndexerMembershipRemoved(t, adminPeer.base, decodedBefore.wk);
229
+
230
+ if (!skipSync) {
231
+ await context.sync();
232
+ await assertDemotedNodeState(t, indexerPeer.base, indexerPeer.wallet.address, decodedBefore.wk);
233
+ }
234
+ }
235
+
236
+ export async function assertRemoveIndexerFailureState(
237
+ t,
238
+ context,
239
+ {
240
+ indexerPeer = context.removeIndexerScenario?.indexerPeer ?? selectIndexerCandidatePeer(context),
241
+ adminPeer = context.adminBootstrap,
242
+ writersLengthBefore = context.removeIndexerScenario?.writersLengthBeforeRemoval,
243
+ skipSync = false
244
+ } = {}
245
+ ) {
246
+ const base = adminPeer.base;
247
+ const writersLength = typeof writersLengthBefore === 'number'
248
+ ? writersLengthBefore
249
+ : await readWritersLength(base);
250
+
251
+ await assertIndexerStillRegistered(t, base, indexerPeer, writersLength);
252
+
253
+ if (!skipSync) {
254
+ await context.sync();
255
+ await assertIndexerStillRegistered(t, indexerPeer.base, indexerPeer, writersLength);
256
+ }
257
+ }
258
+
259
+ export async function assertRemoveIndexerGuardFailureState(
260
+ t,
261
+ context,
262
+ {
263
+ indexerPeer = context.removeIndexerScenario?.indexerPeer ?? selectIndexerCandidatePeer(context),
264
+ adminPeer = context.adminBootstrap,
265
+ skipSync = false
266
+ } = {}
267
+ ) {
268
+ await assertMinimalIndexerState(t, adminPeer.base, indexerPeer);
269
+
270
+ if (!skipSync) {
271
+ await context.sync();
272
+ await assertMinimalIndexerState(t, indexerPeer.base, indexerPeer);
273
+ }
274
+ }
275
+
276
+ async function assertWriterRegistry(t, base, writingKey, expectedAddress) {
277
+ const registryKey = EntryType.WRITER_ADDRESS + writingKey.toString('hex');
278
+ const entry = await base.view.get(registryKey);
279
+ t.ok(entry, 'writer registry entry exists after removeIndexer');
280
+ const addressBuffer = addressUtils.addressToBuffer(expectedAddress);
281
+ t.ok(addressBuffer, 'indexer address encodes to buffer');
282
+ if (!entry?.value || !addressBuffer) return;
283
+ t.ok(
284
+ b4a.equals(entry.value, addressBuffer),
285
+ 'writer registry maps writing key to downgraded writer address'
286
+ );
287
+ }
288
+
289
+ async function assertWriterIndexUpdates(t, base, lengthBefore, expectedAddress) {
290
+ const lengthAfter = await readWritersLength(base);
291
+ t.is(lengthAfter, lengthBefore + 1, 'writers length increments after removeIndexer');
292
+
293
+ const indexEntry = await base.view.get(EntryType.WRITERS_INDEX + lengthBefore);
294
+ t.ok(indexEntry, 'writers index entry stored after removeIndexer');
295
+ const addressBuffer = addressUtils.addressToBuffer(expectedAddress);
296
+ if (!indexEntry?.value || !addressBuffer) return;
297
+ t.ok(
298
+ b4a.equals(indexEntry.value, addressBuffer),
299
+ 'writers index entry stores downgraded writer address'
300
+ );
301
+ }
302
+
303
+ async function assertAdminFeeDeducted(t, base, adminEntryBeforeValue, adminAddress) {
304
+ const decodedAdminBefore = nodeEntryUtils.decode(adminEntryBeforeValue);
305
+ t.ok(decodedAdminBefore, 'admin entry before removeIndexer decodes');
306
+ if (!decodedAdminBefore) return;
307
+
308
+ const adminBalanceBefore = toBalance(decodedAdminBefore.balance);
309
+ t.ok(adminBalanceBefore, 'admin balance before removeIndexer decodes');
310
+ if (!adminBalanceBefore) return;
311
+
312
+ const adminEntryAfter = await base.view.get(adminAddress);
313
+ t.ok(adminEntryAfter, 'admin entry exists after removeIndexer');
314
+ const decodedAdminAfter = nodeEntryUtils.decode(adminEntryAfter?.value);
315
+ t.ok(decodedAdminAfter, 'admin entry decodes after removeIndexer');
316
+ if (!decodedAdminAfter) return;
317
+
318
+ const adminBalanceAfter = toBalance(decodedAdminAfter.balance);
319
+ t.ok(adminBalanceAfter, 'admin balance after removeIndexer decodes');
320
+ if (!adminBalanceAfter) return;
321
+
322
+ const expectedBalance = adminBalanceBefore.sub(BALANCE_FEE);
323
+ t.ok(expectedBalance, 'admin balance after fee computation succeeds');
324
+ if (!expectedBalance) return;
325
+
326
+ t.ok(
327
+ b4a.equals(decodedAdminAfter.balance, expectedBalance.value),
328
+ 'admin balance reduced by removeIndexer fee'
329
+ );
330
+ t.ok(
331
+ b4a.equals(decodedAdminAfter.stakedBalance, decodedAdminBefore.stakedBalance),
332
+ 'admin staked balance remains unchanged after removeIndexer'
333
+ );
334
+ }
335
+
336
+ async function assertRemoveIndexerPayloadMetadata(t, base, payload, expectedAdmin, expectedTarget) {
337
+ const decodedOperation = safeDecodeApplyOperation(payload);
338
+ t.ok(decodedOperation, 'removeIndexer payload decodes');
339
+ if (!decodedOperation) return;
340
+
341
+ const requesterAddressBuffer = decodedOperation.address;
342
+ t.ok(requesterAddressBuffer, 'removeIndexer payload contains requester address');
343
+ const requesterAddress = addressUtils.bufferToAddress(requesterAddressBuffer);
344
+ t.ok(requesterAddress, 'removeIndexer requester address decodes');
345
+ if (requesterAddress) {
346
+ t.is(requesterAddress, expectedAdmin, 'removeIndexer payload signed by admin');
347
+ }
348
+
349
+ const targetAddressBuffer = decodedOperation?.aco?.ia;
350
+ t.ok(targetAddressBuffer, 'removeIndexer payload contains target indexer address');
351
+ const targetAddress = addressUtils.bufferToAddress(targetAddressBuffer);
352
+ t.ok(targetAddress, 'removeIndexer target address decodes');
353
+ if (targetAddress) {
354
+ t.is(targetAddress, expectedTarget, 'removeIndexer payload nominates expected indexer');
355
+ }
356
+
357
+ const txHashBuffer = decodedOperation?.aco?.tx;
358
+ t.ok(txHashBuffer, 'removeIndexer tx hash extracted');
359
+ if (txHashBuffer) {
360
+ const txEntry = await base.view.get(txHashBuffer.toString('hex'));
361
+ t.ok(txEntry, 'removeIndexer transaction recorded for replay protection');
362
+ }
363
+ }
364
+
365
+ function assertIndexerMembershipRemoved(t, base, writingKey) {
366
+ const hasMembership = indexerMembershipIncludes(base, writingKey);
367
+ t.is(hasMembership, false, 'indexer writer key removed from validator set');
368
+ }
369
+
370
+ async function assertDemotedNodeState(t, base, address, writingKey) {
371
+ const entry = await base.view.get(address);
372
+ t.ok(entry, 'downgraded entry exists after removeIndexer');
373
+ const decoded = nodeEntryUtils.decode(entry?.value);
374
+ t.ok(decoded, 'downgraded entry decodes after removeIndexer');
375
+ if (!decoded) return;
376
+ t.is(decoded.isIndexer, false, 'node remains non-indexer after removeIndexer sync');
377
+ t.is(decoded.isWriter, true, 'node remains writer after removeIndexer sync');
378
+ t.is(decoded.isWhitelisted, true, 'node remains whitelisted after removeIndexer sync');
379
+ t.ok(b4a.equals(decoded.wk, writingKey), 'writer key preserved after removeIndexer sync');
380
+ assertIndexerMembershipRemoved(t, base, writingKey);
381
+ }
382
+
383
+ async function assertIndexerStillRegistered(t, base, indexerPeer, writersLengthBefore) {
384
+ const entry = await base.view.get(indexerPeer.wallet.address);
385
+ t.ok(entry, 'indexer entry persists after failed removeIndexer');
386
+ const decoded = nodeEntryUtils.decode(entry?.value);
387
+ t.ok(decoded, 'indexer entry decodes after failed removeIndexer');
388
+ if (!decoded) return;
389
+
390
+ t.is(decoded.isIndexer, true, 'indexer role preserved after failed removeIndexer');
391
+ t.is(decoded.isWriter, true, 'writer role preserved after failed removeIndexer');
392
+ t.is(decoded.isWhitelisted, true, 'whitelist flag preserved after failed removeIndexer');
393
+ assertIndexerMembershipPresent(t, base, decoded.wk);
394
+
395
+ if (typeof writersLengthBefore === 'number') {
396
+ const writersLengthAfter = await readWritersLength(base);
397
+ t.is(
398
+ writersLengthAfter,
399
+ writersLengthBefore,
400
+ 'writers length unchanged after failed removeIndexer'
401
+ );
402
+ }
403
+ }
404
+
405
+ function assertIndexerMembershipPresent(t, base, writingKey) {
406
+ const hasMembership = indexerMembershipIncludes(base, writingKey);
407
+ t.ok(hasMembership, 'indexer writer key remains in validator set');
408
+ }
409
+
410
+ async function assertMinimalIndexerState(t, base, indexerPeer) {
411
+ const entry = await base.view.get(indexerPeer.wallet.address);
412
+ t.ok(entry, 'indexer entry persists after failed removeIndexer');
413
+ const decoded = nodeEntryUtils.decode(entry?.value);
414
+ t.ok(decoded, 'indexer entry decodes after failed removeIndexer');
415
+ if (!decoded) return;
416
+ }
417
+
418
+ async function readWritersLength(base) {
419
+ const entry = await base.view.get(EntryType.WRITERS_LENGTH);
420
+ if (!entry?.value) return 0;
421
+ return entry.value.readUInt32BE();
422
+ }
423
+
424
+ function indexerMembershipIncludes(base, writingKey) {
425
+ const entries = base?.system?.indexers;
426
+ if (!entries) return false;
427
+ return Object.values(entries).some(entry => entry?.key && b4a.equals(entry.key, writingKey));
428
+ }
@@ -0,0 +1,22 @@
1
+ import { test } from 'brittle';
2
+ import nodeRoleUtils from '../../../../../src/core/state/utils/roles.js';
3
+ import OperationValidationScenarioBase from '../common/base/OperationValidationScenarioBase.js';
4
+ import { applyWithPretenderRoleMutation } from '../addIndexer/addIndexerScenarioHelpers.js';
5
+ import {
6
+ setupRemoveIndexerScenario,
7
+ buildRemoveIndexerPayload,
8
+ assertRemoveIndexerFailureState
9
+ } from './removeIndexerScenarioHelpers.js';
10
+
11
+ export default function removeIndexerTargetNotIndexerScenario() {
12
+ new OperationValidationScenarioBase({
13
+ title: 'State.apply removeIndexer rejects payloads when target is not an indexer',
14
+ setupScenario: setupRemoveIndexerScenario,
15
+ buildValidPayload: context => buildRemoveIndexerPayload(context),
16
+ mutatePayload: (_t, payload) => payload,
17
+ applyInvalidPayload: (context, invalidPayload) =>
18
+ applyWithPretenderRoleMutation(context, invalidPayload, nodeRoleUtils.NodeRole.WRITER),
19
+ assertStateUnchanged: (t, context) => assertRemoveIndexerFailureState(t, context, { skipSync: true }),
20
+ expectedLogs: ['Node must be an indexer.']
21
+ }).performScenario(test);
22
+ }
@@ -0,0 +1,20 @@
1
+ import OperationValidationScenarioBase from '../common/base/OperationValidationScenarioBase.js';
2
+ import {
3
+ setupRemoveIndexerScenario,
4
+ buildRemoveIndexerPayload,
5
+ assertRemoveIndexerGuardFailureState,
6
+ applyWithoutIndexerMembership
7
+ } from './removeIndexerScenarioHelpers.js';
8
+
9
+ export default function removeIndexerWriterKeyMissingScenario() {
10
+ new OperationValidationScenarioBase({
11
+ title: 'State.apply removeIndexer rejects payloads when writer key is missing from indexer list',
12
+ setupScenario: setupRemoveIndexerScenario,
13
+ buildValidPayload: context => buildRemoveIndexerPayload(context),
14
+ mutatePayload: (_t, payload) => payload,
15
+ applyInvalidPayload: (context, invalidPayload) =>
16
+ applyWithoutIndexerMembership(context, invalidPayload),
17
+ assertStateUnchanged: (t, context) => assertRemoveIndexerGuardFailureState(t, context, { skipSync: true }),
18
+ expectedLogs: ['Writer key does not exist in indexer list.']
19
+ }).performScenario();
20
+ }