trac-msb 0.2.17 → 0.2.19

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 (365) hide show
  1. package/README.md +62 -2
  2. package/SECURITY.md +9 -6
  3. package/cli/commandHandler.js +272 -0
  4. package/cli/handlers.js +210 -0
  5. package/cli/interactive.js +182 -0
  6. package/msb.mjs +7 -8
  7. package/package.json +33 -11
  8. package/rpc/create_server.js +44 -44
  9. package/rpc/handlers.js +43 -56
  10. package/rpc/routes/index.js +8 -8
  11. package/rpc/rpc_server.js +6 -2
  12. package/rpc/utils/helpers.js +46 -66
  13. package/scripts/pear-runner.js +170 -0
  14. package/src/config/args.js +60 -13
  15. package/src/config/config.js +163 -14
  16. package/src/config/env.js +57 -25
  17. package/src/core/network/Network.js +121 -56
  18. package/src/core/network/protocols/LegacyProtocol.js +21 -11
  19. package/src/core/network/protocols/NetworkMessages.js +38 -17
  20. package/src/core/network/protocols/ProtocolInterface.js +15 -3
  21. package/src/core/network/protocols/ProtocolSession.js +144 -17
  22. package/src/core/network/protocols/V1Protocol.js +37 -18
  23. package/src/core/network/protocols/connectionPolicies.js +174 -0
  24. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +26 -20
  25. package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +25 -14
  26. package/src/core/network/protocols/legacy/handlers/{GetRequestHandler.js → LegacyGetRequestHandler.js} +9 -9
  27. package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +23 -0
  28. package/src/core/network/protocols/{shared/handlers/RoleOperationHandler.js → legacy/handlers/LegacyRoleOperationHandler.js} +19 -12
  29. package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +29 -18
  30. package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +18 -12
  31. package/src/core/network/protocols/legacy/validators/base/BaseResponse.js +7 -10
  32. package/src/core/network/protocols/shared/validators/{PartialBootstrapDeployment.js → PartialBootstrapDeploymentValidator.js} +9 -4
  33. package/src/core/network/protocols/shared/validators/{base/PartialOperation.js → PartialOperationValidator.js} +53 -31
  34. package/src/core/network/protocols/shared/validators/{PartialRoleAccess.js → PartialRoleAccessValidator.js} +51 -17
  35. package/src/core/network/protocols/shared/validators/{PartialTransaction.js → PartialTransactionValidator.js} +21 -7
  36. package/src/core/network/protocols/shared/validators/{PartialTransfer.js → PartialTransferValidator.js} +32 -14
  37. package/src/core/network/protocols/v1/NetworkMessageRouter.js +91 -7
  38. package/src/core/network/protocols/v1/V1ProtocolError.js +27 -0
  39. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +66 -0
  40. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +272 -0
  41. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +88 -0
  42. package/src/core/network/protocols/v1/handlers/broadcastTransaction/BroadcastTransactionErrorMapper.js +84 -0
  43. package/src/core/network/protocols/v1/handlers/broadcastTransaction/BroadcastTransactionOperationStrategies.js +128 -0
  44. package/src/core/network/protocols/v1/handlers/broadcastTransaction/BroadcastTransactionPayloadUtils.js +26 -0
  45. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +212 -0
  46. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +28 -0
  47. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +260 -0
  48. package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +15 -0
  49. package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +17 -0
  50. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +210 -0
  51. package/src/core/network/services/ConnectionManager.js +149 -96
  52. package/src/core/network/services/MessageOrchestrator.js +197 -37
  53. package/src/core/network/services/PendingRequestService.js +175 -0
  54. package/src/core/network/services/TransactionCommitService.js +149 -0
  55. package/src/core/network/services/TransactionPoolService.js +129 -18
  56. package/src/core/network/services/TransactionRateLimiterService.js +57 -35
  57. package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
  58. package/src/core/network/services/ValidatorObserverService.js +400 -121
  59. package/src/core/state/State.js +259 -156
  60. package/src/core/state/utils/address.js +4 -9
  61. package/src/core/state/utils/balance.js +7 -7
  62. package/src/core/state/utils/roles.js +1 -1
  63. package/src/index.js +320 -278
  64. package/src/messages/network/v1/NetworkMessageBuilder.js +71 -100
  65. package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
  66. package/src/messages/network/v1/networkMessageFactory.js +1 -1
  67. package/src/messages/state/ApplyStateMessageBuilder.js +6 -6
  68. package/src/messages/state/applyStateMessageFactory.js +1 -1
  69. package/src/utils/Scheduler.js +0 -8
  70. package/src/utils/buffer.js +1 -1
  71. package/src/utils/check.js +12 -13
  72. package/src/utils/constants.js +78 -5
  73. package/src/utils/deepEqualApplyPayload.js +40 -0
  74. package/src/utils/errors.js +20 -0
  75. package/src/utils/fileUtils.js +12 -3
  76. package/src/utils/helpers.js +10 -1
  77. package/src/utils/logger.js +25 -0
  78. package/src/utils/migrationUtils.js +3 -3
  79. package/src/utils/normalizers.js +38 -0
  80. package/src/utils/protobuf/networkV1.generated.cjs +2460 -0
  81. package/src/utils/protobuf/operationHelpers.js +24 -3
  82. package/.dockerignore +0 -16
  83. package/.github/workflows/acceptance-tests.yml +0 -38
  84. package/.github/workflows/lint-pr-title.yml +0 -26
  85. package/.github/workflows/publish.yml +0 -33
  86. package/.github/workflows/unit-tests.yml +0 -34
  87. package/CODE_OF_CONDUCT.md +0 -128
  88. package/docker-compose.yml +0 -17
  89. package/dockerfile +0 -41
  90. package/docs/fee_distribution.md +0 -89
  91. package/docs/trac_network_http_api.openapi.yaml +0 -889
  92. package/migration/.gitkeep +0 -0
  93. package/proto/applyOperations.proto +0 -117
  94. package/proto/network.proto +0 -74
  95. package/rpc/rpc_services.js +0 -163
  96. package/scripts/generate-protobufs.js +0 -42
  97. package/src/core/network/identity/NetworkWalletFactory.js +0 -77
  98. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +0 -37
  99. package/src/utils/cli.js +0 -130
  100. package/src/utils/cliCommands.js +0 -280
  101. package/src/utils/protobuf/network.cjs +0 -840
  102. package/tests/acceptance/v1/account/account.test.mjs +0 -123
  103. package/tests/acceptance/v1/balance/balance.test.mjs +0 -54
  104. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +0 -116
  105. package/tests/acceptance/v1/confirmed-length/confirmed-length.test.mjs +0 -19
  106. package/tests/acceptance/v1/fee/fee.test.mjs +0 -11
  107. package/tests/acceptance/v1/health/health.test.mjs +0 -33
  108. package/tests/acceptance/v1/rpc.test.mjs +0 -95
  109. package/tests/acceptance/v1/tx/tx.test.mjs +0 -111
  110. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +0 -213
  111. package/tests/acceptance/v1/tx-hashes/tx-hashes.test.mjs +0 -72
  112. package/tests/acceptance/v1/tx-payloads-bulk/tx-payloads-bulk.test.mjs +0 -27
  113. package/tests/acceptance/v1/txv/txv.test.mjs +0 -11
  114. package/tests/acceptance/v1/unconfirmed-length/unconfirmed-length.test.mjs +0 -11
  115. package/tests/fixtures/apply.fixtures.js +0 -77
  116. package/tests/fixtures/assembleMessage.fixtures.js +0 -41
  117. package/tests/fixtures/check.fixtures.js +0 -428
  118. package/tests/fixtures/networkV1.fixtures.js +0 -85
  119. package/tests/fixtures/protobuf.fixtures.js +0 -388
  120. package/tests/helpers/StateNetworkFactory.js +0 -155
  121. package/tests/helpers/address.js +0 -6
  122. package/tests/helpers/autobaseTestHelpers.js +0 -419
  123. package/tests/helpers/config.js +0 -4
  124. package/tests/helpers/createTestSignature.js +0 -13
  125. package/tests/helpers/regexHelper.js +0 -3
  126. package/tests/helpers/setupApplyTests.js +0 -587
  127. package/tests/helpers/transactionPayloads.mjs +0 -92
  128. package/tests/helpers/wrapper.js +0 -28
  129. package/tests/unit/messages/messages.test.js +0 -12
  130. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +0 -276
  131. package/tests/unit/messages/network/NetworkMessageDirector.test.js +0 -201
  132. package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +0 -525
  133. package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +0 -237
  134. package/tests/unit/network/ConnectionManager.test.js +0 -191
  135. package/tests/unit/network/NetworkWalletFactory.test.js +0 -156
  136. package/tests/unit/network/networkModule.test.js +0 -10
  137. package/tests/unit/state/State.test.js +0 -80
  138. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +0 -42
  139. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +0 -276
  140. package/tests/unit/state/apply/addAdmin/adminEntryEncodingFailureScenario.js +0 -30
  141. package/tests/unit/state/apply/addAdmin/adminEntryExistsScenario.js +0 -78
  142. package/tests/unit/state/apply/addAdmin/nodeEntryInitializationFailureScenario.js +0 -30
  143. package/tests/unit/state/apply/addAdmin/nonBootstrapNodeScenario.js +0 -68
  144. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +0 -159
  145. package/tests/unit/state/apply/addIndexer/addIndexerHappyPathScenario.js +0 -39
  146. package/tests/unit/state/apply/addIndexer/addIndexerMultipleIndexersInTheNetworkScenario.js +0 -167
  147. package/tests/unit/state/apply/addIndexer/addIndexerPretenderAlreadyIndexerScenario.js +0 -21
  148. package/tests/unit/state/apply/addIndexer/addIndexerPretenderNotWriterScenario.js +0 -21
  149. package/tests/unit/state/apply/addIndexer/addIndexerRemoveAndReAddScenario.js +0 -186
  150. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +0 -443
  151. package/tests/unit/state/apply/addIndexer/addIndexerWriterKeyAlreadyRegisteredScenario.js +0 -32
  152. package/tests/unit/state/apply/addIndexer/state.apply.addIndexer.test.js +0 -297
  153. package/tests/unit/state/apply/addWriter/addWriterHappyPathScenario.js +0 -41
  154. package/tests/unit/state/apply/addWriter/addWriterInvalidValidatorSignatureScenario.js +0 -32
  155. package/tests/unit/state/apply/addWriter/addWriterNewWkScenario.js +0 -149
  156. package/tests/unit/state/apply/addWriter/addWriterRequesterAlreadyWriterScenario.js +0 -21
  157. package/tests/unit/state/apply/addWriter/addWriterRequesterBalanceInsufficientScenario.js +0 -21
  158. package/tests/unit/state/apply/addWriter/addWriterRequesterEntryDecodeFailureScenario.js +0 -19
  159. package/tests/unit/state/apply/addWriter/addWriterRequesterEntryMissingScenario.js +0 -19
  160. package/tests/unit/state/apply/addWriter/addWriterRequesterIndexerScenario.js +0 -21
  161. package/tests/unit/state/apply/addWriter/addWriterRequesterNotWhitelistedScenario.js +0 -21
  162. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +0 -766
  163. package/tests/unit/state/apply/addWriter/addWriterStakeBalanceUpdateFailureScenario.js +0 -50
  164. package/tests/unit/state/apply/addWriter/addWriterStakeInsufficientBalanceScenario.js +0 -29
  165. package/tests/unit/state/apply/addWriter/addWriterStakeInvalidBalanceScenario.js +0 -29
  166. package/tests/unit/state/apply/addWriter/addWriterStakeInvalidEntryScenario.js +0 -21
  167. package/tests/unit/state/apply/addWriter/addWriterStakeStakedBalanceFailureScenario.js +0 -37
  168. package/tests/unit/state/apply/addWriter/addWriterStakeSubtractFailureScenario.js +0 -42
  169. package/tests/unit/state/apply/addWriter/addWriterValidatorRewardScenario.js +0 -106
  170. package/tests/unit/state/apply/addWriter/addWriterWriterKeyMismatchScenario.js +0 -54
  171. package/tests/unit/state/apply/addWriter/addWriterWriterKeyOwnershipScenario.js +0 -54
  172. package/tests/unit/state/apply/addWriter/addWriterZeroWriterKeyScenario.js +0 -29
  173. package/tests/unit/state/apply/addWriter/state.apply.addWriter.test.js +0 -309
  174. package/tests/unit/state/apply/adminRecovery/adminRecoveryHappyPathScenario.js +0 -30
  175. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +0 -881
  176. package/tests/unit/state/apply/adminRecovery/state.apply.adminRecovery.test.js +0 -435
  177. package/tests/unit/state/apply/appendWhitelist/appendWhitelistBanAndReapplyScenario.js +0 -78
  178. package/tests/unit/state/apply/appendWhitelist/appendWhitelistExistingReaderHappyPathScenario.js +0 -98
  179. package/tests/unit/state/apply/appendWhitelist/appendWhitelistFeeAfterDisableScenario.js +0 -66
  180. package/tests/unit/state/apply/appendWhitelist/appendWhitelistHappyPathScenario.js +0 -55
  181. package/tests/unit/state/apply/appendWhitelist/appendWhitelistInsufficientAdminBalanceScenario.js +0 -103
  182. package/tests/unit/state/apply/appendWhitelist/appendWhitelistNodeAlreadyWhitelistedScenario.js +0 -60
  183. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +0 -189
  184. package/tests/unit/state/apply/appendWhitelist/state.apply.appendWhitelist.test.js +0 -220
  185. package/tests/unit/state/apply/balanceInitialization/balanceInitializationHappyPathScenario.js +0 -82
  186. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +0 -111
  187. package/tests/unit/state/apply/balanceInitialization/invalidAmountScenario.js +0 -45
  188. package/tests/unit/state/apply/balanceInitialization/nodeEntryBalanceUpdateFailureScenario.js +0 -82
  189. package/tests/unit/state/apply/balanceInitialization/state.apply.balanceInitialization.test.js +0 -189
  190. package/tests/unit/state/apply/banValidator/banValidatorBanAndReWhitelistScenario.js +0 -156
  191. package/tests/unit/state/apply/banValidator/banValidatorHappyPathScenario.js +0 -36
  192. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +0 -531
  193. package/tests/unit/state/apply/banValidator/banValidatorSequentialBansScenario.js +0 -74
  194. package/tests/unit/state/apply/banValidator/banValidatorTargetDecodeFailureScenario.js +0 -19
  195. package/tests/unit/state/apply/banValidator/banValidatorTargetIndexerScenario.js +0 -32
  196. package/tests/unit/state/apply/banValidator/banValidatorTargetNodeEntryMissingScenario.js +0 -19
  197. package/tests/unit/state/apply/banValidator/banValidatorTargetRoleUpdateFailureScenario.js +0 -19
  198. package/tests/unit/state/apply/banValidator/banValidatorWhitelistedNonWriterScenario.js +0 -38
  199. package/tests/unit/state/apply/banValidator/banValidatorWhitelistedZeroBalanceScenario.js +0 -91
  200. package/tests/unit/state/apply/banValidator/banValidatorWithdrawFailureScenario.js +0 -19
  201. package/tests/unit/state/apply/banValidator/state.apply.banValidator.test.js +0 -266
  202. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentDuplicateRegistrationScenario.js +0 -143
  203. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentHappyPathScenario.js +0 -26
  204. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentIncompleteOperationScenario.js +0 -94
  205. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentInvalidDeploymentEntryScenario.js +0 -37
  206. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentMultipleBootstrapScenario.js +0 -86
  207. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +0 -347
  208. package/tests/unit/state/apply/bootstrapDeployment/invalidValidatorNodeEntryScenario.js +0 -57
  209. package/tests/unit/state/apply/bootstrapDeployment/state.apply.bootstrapDeployment.test.js +0 -429
  210. package/tests/unit/state/apply/common/access-control/adminConsistencyMismatchScenario.js +0 -120
  211. package/tests/unit/state/apply/common/access-control/adminEntryDecodeFailureScenario.js +0 -130
  212. package/tests/unit/state/apply/common/access-control/adminEntryExistsScenario.js +0 -93
  213. package/tests/unit/state/apply/common/access-control/adminEntryMissingScenario.js +0 -108
  214. package/tests/unit/state/apply/common/access-control/adminOnlyGuardScenario.js +0 -126
  215. package/tests/unit/state/apply/common/access-control/adminPublicKeyDecodeFailureScenario.js +0 -121
  216. package/tests/unit/state/apply/common/access-control/roleAccessOperationValidationScenario.js +0 -50
  217. package/tests/unit/state/apply/common/adminControlOperationValidationScenario.js +0 -56
  218. package/tests/unit/state/apply/common/balances/adminEntryUpdateFailureScenario.js +0 -52
  219. package/tests/unit/state/apply/common/balances/base/requesterBalanceScenarioBase.js +0 -198
  220. package/tests/unit/state/apply/common/balances/feeDecodeFailureScenario.js +0 -52
  221. package/tests/unit/state/apply/common/balances/requesterBalanceDecodeFailureScenario.js +0 -15
  222. package/tests/unit/state/apply/common/balances/requesterBalanceFeeApplicationFailureScenario.js +0 -11
  223. package/tests/unit/state/apply/common/balances/requesterBalanceInsufficientScenario.js +0 -15
  224. package/tests/unit/state/apply/common/balances/requesterBalanceUpdateFailureScenario.js +0 -11
  225. package/tests/unit/state/apply/common/balances/validatorEntryRewardFailureScenario.js +0 -11
  226. package/tests/unit/state/apply/common/balances/validatorEntryUpdateFailureScenario.js +0 -11
  227. package/tests/unit/state/apply/common/balances/validatorNodeEntryDecodeFailureScenario.js +0 -40
  228. package/tests/unit/state/apply/common/base/OperationValidationScenarioBase.js +0 -114
  229. package/tests/unit/state/apply/common/commonScenarioHelper.js +0 -103
  230. package/tests/unit/state/apply/common/indexer/indexerNodeEntryDecodeFailureScenario.js +0 -36
  231. package/tests/unit/state/apply/common/indexer/indexerNodeEntryMissingScenario.js +0 -36
  232. package/tests/unit/state/apply/common/indexer/indexerRoleUpdateFailureScenario.js +0 -29
  233. package/tests/unit/state/apply/common/indexer/indexerSequenceStateInvalidScenario.js +0 -66
  234. package/tests/unit/state/apply/common/invalidMessageComponentValidationScenario.js +0 -84
  235. package/tests/unit/state/apply/common/nodeEntryInitializationFailureScenario.js +0 -47
  236. package/tests/unit/state/apply/common/operationAlreadyAppliedScenario.js +0 -85
  237. package/tests/unit/state/apply/common/payload-structure/addressWithInvalidPublicKeyScenario.js +0 -52
  238. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +0 -54
  239. package/tests/unit/state/apply/common/payload-structure/invalidAddressValidationScenario.js +0 -73
  240. package/tests/unit/state/apply/common/payload-structure/invalidHashValidationScenario.js +0 -71
  241. package/tests/unit/state/apply/common/payload-structure/invalidPayloadValidationScenario.js +0 -31
  242. package/tests/unit/state/apply/common/payload-structure/invalidSignatureValidationScenario.js +0 -142
  243. package/tests/unit/state/apply/common/payload-structure/partialOperationValidationScenario.js +0 -87
  244. package/tests/unit/state/apply/common/requester/requesterNodeEntryBufferMissingScenario.js +0 -71
  245. package/tests/unit/state/apply/common/requester/requesterNodeEntryDecodeFailureScenario.js +0 -73
  246. package/tests/unit/state/apply/common/requester/requesterNodeEntryMissingScenario.js +0 -36
  247. package/tests/unit/state/apply/common/requesterAddressValidationScenario.js +0 -44
  248. package/tests/unit/state/apply/common/requesterPublicKeyValidationScenario.js +0 -25
  249. package/tests/unit/state/apply/common/transactionValidityMismatchScenario.js +0 -98
  250. package/tests/unit/state/apply/common/validatorConsistency/base/validatorConsistencyScenarioBase.js +0 -202
  251. package/tests/unit/state/apply/common/validatorConsistency/validatorEntryDecodeFailureScenario.js +0 -17
  252. package/tests/unit/state/apply/common/validatorConsistency/validatorEntryMissingScenario.js +0 -44
  253. package/tests/unit/state/apply/common/validatorConsistency/validatorInactiveScenario.js +0 -19
  254. package/tests/unit/state/apply/common/validatorConsistency/validatorWriterKeyMismatchScenario.js +0 -18
  255. package/tests/unit/state/apply/common/validatorEntryValidation/base/validatorEntryValidationScenarioBase.js +0 -315
  256. package/tests/unit/state/apply/common/validatorEntryValidation/validatorEntryInvalidBalanceScenario.js +0 -18
  257. package/tests/unit/state/apply/common/writerKeyExistsValidationScenario.js +0 -43
  258. package/tests/unit/state/apply/disableInitialization/disableInitializationAlreadyDisabledScenario.js +0 -53
  259. package/tests/unit/state/apply/disableInitialization/disableInitializationHappyPathScenario.js +0 -24
  260. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +0 -204
  261. package/tests/unit/state/apply/disableInitialization/state.apply.disableInitialization.test.js +0 -161
  262. package/tests/unit/state/apply/missing-tests.md +0 -18
  263. package/tests/unit/state/apply/removeIndexer/removeIndexerHappyPathScenario.js +0 -58
  264. package/tests/unit/state/apply/removeIndexer/removeIndexerReAddAndRemoveAgainScenario.js +0 -98
  265. package/tests/unit/state/apply/removeIndexer/removeIndexerRemoveMultipleIndexersScenario.js +0 -167
  266. package/tests/unit/state/apply/removeIndexer/removeIndexerScenarioHelpers.js +0 -429
  267. package/tests/unit/state/apply/removeIndexer/removeIndexerTargetNotIndexerScenario.js +0 -22
  268. package/tests/unit/state/apply/removeIndexer/removeIndexerWriterKeyMissingScenario.js +0 -20
  269. package/tests/unit/state/apply/removeIndexer/state.apply.removeIndexer.test.js +0 -291
  270. package/tests/unit/state/apply/removeWriter/removeWriterAndAddWriterAgainScenario.js +0 -87
  271. package/tests/unit/state/apply/removeWriter/removeWriterHappyPathScenario.js +0 -38
  272. package/tests/unit/state/apply/removeWriter/removeWriterInvalidValidatorSignatureScenario.js +0 -32
  273. package/tests/unit/state/apply/removeWriter/removeWriterRequesterBalanceInsufficientScenario.js +0 -21
  274. package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryDecodeFailureScenario.js +0 -19
  275. package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryMissingScenario.js +0 -19
  276. package/tests/unit/state/apply/removeWriter/removeWriterRequesterIndexerScenario.js +0 -21
  277. package/tests/unit/state/apply/removeWriter/removeWriterRequesterNotWriterScenario.js +0 -21
  278. package/tests/unit/state/apply/removeWriter/removeWriterRequesterRoleUpdateFailureScenario.js +0 -19
  279. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +0 -348
  280. package/tests/unit/state/apply/removeWriter/removeWriterThroughWriterValidatorScenario.js +0 -113
  281. package/tests/unit/state/apply/removeWriter/removeWriterUnstakeFailureScenario.js +0 -33
  282. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyMismatchScenario.js +0 -21
  283. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyOwnershipScenario.js +0 -26
  284. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyRegistryMissingScenario.js +0 -22
  285. package/tests/unit/state/apply/removeWriter/state.apply.removeWriter.test.js +0 -307
  286. package/tests/unit/state/apply/state.apply.test.js +0 -24
  287. package/tests/unit/state/apply/transfer/state.apply.transfer.test.js +0 -819
  288. package/tests/unit/state/apply/transfer/transferContractSchemaValidationScenario.js +0 -22
  289. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +0 -146
  290. package/tests/unit/state/apply/transfer/transferDoubleSpendSameBatchScenario.js +0 -63
  291. package/tests/unit/state/apply/transfer/transferDoubleSpendSingleValidatorScenario.js +0 -67
  292. package/tests/unit/state/apply/transfer/transferExistingRecipientAmountScenario.js +0 -31
  293. package/tests/unit/state/apply/transfer/transferExistingRecipientZeroAmountScenario.js +0 -31
  294. package/tests/unit/state/apply/transfer/transferHandlerGuardScenarios.js +0 -22
  295. package/tests/unit/state/apply/transfer/transferHappyPathScenario.js +0 -8
  296. package/tests/unit/state/apply/transfer/transferInvalidIncomingDataScenario.js +0 -66
  297. package/tests/unit/state/apply/transfer/transferNewRecipientAmountScenario.js +0 -31
  298. package/tests/unit/state/apply/transfer/transferNewRecipientZeroAmountScenario.js +0 -31
  299. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +0 -1170
  300. package/tests/unit/state/apply/transfer/transferSelfTransferAmountScenario.js +0 -38
  301. package/tests/unit/state/apply/transfer/transferSelfTransferZeroAmountScenario.js +0 -38
  302. package/tests/unit/state/apply/transfer/transferValidatorRecipientAmountScenario.js +0 -38
  303. package/tests/unit/state/apply/transfer/transferValidatorRecipientZeroAmountScenario.js +0 -38
  304. package/tests/unit/state/apply/txOperation/state.apply.txOperation.test.js +0 -318
  305. package/tests/unit/state/apply/txOperation/txOperationBootstrapNotRegisteredScenario.js +0 -70
  306. package/tests/unit/state/apply/txOperation/txOperationDifferentValidatorCreatorHappyPathScenario.js +0 -23
  307. package/tests/unit/state/apply/txOperation/txOperationInvalidDeploymentEntryScenario.js +0 -48
  308. package/tests/unit/state/apply/txOperation/txOperationInvalidFeeAmountScenario.js +0 -39
  309. package/tests/unit/state/apply/txOperation/txOperationInvalidSubnetCreatorAddressScenario.js +0 -46
  310. package/tests/unit/state/apply/txOperation/txOperationRequesterCreatorHappyPathScenario.js +0 -21
  311. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +0 -432
  312. package/tests/unit/state/apply/txOperation/txOperationStandardHappyPathScenario.js +0 -21
  313. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddCreatorBalanceFailureScenario.js +0 -26
  314. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBalanceFailureScenario.js +0 -25
  315. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBonusFailureScenario.js +0 -27
  316. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeCreatorEntryScenario.js +0 -18
  317. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeRequesterEntryScenario.js +0 -17
  318. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeValidatorEntryScenario.js +0 -31
  319. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardBypassScenario.js +0 -49
  320. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardScenarioFactory.js +0 -93
  321. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInsufficientRequesterBalanceScenario.js +0 -28
  322. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidCreatorBalanceScenario.js +0 -29
  323. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterBalanceScenario.js +0 -28
  324. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterEntryScenario.js +0 -17
  325. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidValidatorBalanceScenario.js +0 -33
  326. package/tests/unit/state/apply/txOperation/txOperationTransferFeeMissingCreatorEntryScenario.js +0 -18
  327. package/tests/unit/state/apply/txOperation/txOperationTransferFeeSubtractFailureScenario.js +0 -25
  328. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateCreatorBalanceFailureScenario.js +0 -26
  329. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateFailureScenario.js +0 -25
  330. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBalanceFailureScenario.js +0 -26
  331. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBonusFailureScenario.js +0 -27
  332. package/tests/unit/state/apply/txOperation/txOperationValidatorCreatorHappyPathScenario.js +0 -21
  333. package/tests/unit/state/stateModule.test.js +0 -23
  334. package/tests/unit/state/stateTestUtils.js +0 -32
  335. package/tests/unit/state/utils/address.test.js +0 -63
  336. package/tests/unit/state/utils/adminEntry.test.js +0 -52
  337. package/tests/unit/state/utils/balance.test.js +0 -320
  338. package/tests/unit/state/utils/lengthEntry.test.js +0 -54
  339. package/tests/unit/state/utils/nodeEntry.test.js +0 -298
  340. package/tests/unit/state/utils/roles.test.js +0 -44
  341. package/tests/unit/unit.test.js +0 -14
  342. package/tests/unit/utils/amountSerialization/amountSerialization.test.js +0 -237
  343. package/tests/unit/utils/buffer/buffer.test.js +0 -251
  344. package/tests/unit/utils/check/adminControlOperation.test.js +0 -156
  345. package/tests/unit/utils/check/balanceInitializationOperation.test.js +0 -54
  346. package/tests/unit/utils/check/bootstrapDeploymentOperation.test.js +0 -104
  347. package/tests/unit/utils/check/check.test.js +0 -17
  348. package/tests/unit/utils/check/common.test.js +0 -419
  349. package/tests/unit/utils/check/coreAdminOperation.test.js +0 -95
  350. package/tests/unit/utils/check/roleAccessOperation.test.js +0 -255
  351. package/tests/unit/utils/check/transactionOperation.test.js +0 -107
  352. package/tests/unit/utils/check/transferOperation.test.js +0 -102
  353. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +0 -95
  354. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +0 -149
  355. package/tests/unit/utils/helpers/helpers.test.js +0 -11
  356. package/tests/unit/utils/helpers/isHexString.test.js +0 -12
  357. package/tests/unit/utils/helpers/normalizeHex.test.js +0 -82
  358. package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +0 -100
  359. package/tests/unit/utils/normalizers/normalizers.test.js +0 -469
  360. package/tests/unit/utils/protobuf/applyOperations.test.js +0 -91
  361. package/tests/unit/utils/protobuf/operationHelpers.test.js +0 -305
  362. package/tests/unit/utils/protobuf/protobuf.test.js +0 -12
  363. package/tests/unit/utils/type/type.test.js +0 -25
  364. package/tests/unit/utils/utils.test.js +0 -20
  365. package/whitelist/.gitkeep +0 -0
@@ -1,186 +1,465 @@
1
- import PeerWallet from "trac-wallet";
2
- import b4a from "b4a";
3
- import { bufferToAddress } from '../../state/utils/address.js';
4
- import { sleep } from '../../../utils/helpers.js';
5
1
  import Scheduler from "../../../utils/Scheduler.js";
6
- import Network from "../Network.js";
7
-
8
- const DELAY_INTERVAL = 50
9
- const VALIDATOR_CANDIDATES_PER_CYCLE = 10
10
- const POLL_INTERVAL = (VALIDATOR_CANDIDATES_PER_CYCLE + 1) * DELAY_INTERVAL // This is to avoid more than one instance of the worker running at the same time
11
-
12
- // -- Debug Mode --
13
- // TODO: Implement a better debug system in the future. This is just temporary.
14
- const DEBUG = false;
15
- const debugLog = (...args) => {
16
- if (DEBUG) {
17
- console.log('DEBUG [ValidatorObserverService] ==> ', ...args);
18
- }
19
- };
2
+ import { Logger } from "../../../utils/logger.js";
3
+ import b4a from "b4a";
4
+ import { bufferToAddress } from "../../../core/state/utils/address.js";
5
+ import tracCryptoApi from "trac-crypto-api";
6
+ import { WRITER_BYTE_LENGTH, CONNECTION_STATUS } from "../../../utils/constants.js";
7
+
8
+ // Internal constants
9
+ const VALIDATOR_CANDIDATES_PER_CYCLE = 10;
10
+ const MAX_KEY_DECODE_CACHE_SIZE = 10_000;
20
11
 
21
12
  class ValidatorObserverService {
22
- #config;
23
- #state;
24
13
  #network;
25
- #scheduler;
14
+ #state;
26
15
  #address;
27
- #isInterrupted
16
+ #config;
17
+ #scheduler;
18
+ #logger;
19
+ #hasBootstrapped; // Whether initial connection phase is complete
20
+ #bootstrapStartedAt; // Timestamp to enforce bootstrap timeout
21
+ #adminCache; // Cached admin entry with TTL
22
+ #writersCache; // Cached writer list with dynamic TTL
23
+ #isInterrupted; // Used to stop execution mid-cycle
24
+ #keyDecodeCache;
28
25
 
29
26
  /**
30
- * @param {Network} network
31
- * @param {State} state
32
- * @param {string} address
33
- * @param {Config} config
34
- **/
27
+ * @param {Object} network - The network layer instance (e.g., Hyperswarm wrapper).
28
+ * @param {Object} state - The state manager instance interacting with Autobase.
29
+ * @param {string} address - The physical address of the current node.
30
+ * @param {Object} config - The application configuration object.
31
+ */
35
32
  constructor(network, state, address, config) {
36
- this.#config = config
37
33
  this.#network = network;
38
34
  this.#state = state;
39
35
  this.#address = address;
36
+ this.#config = config;
37
+ this.#logger = new Logger(config);
38
+ this.#hasBootstrapped = false;
39
+ this.#bootstrapStartedAt = 0;
40
+ this.#adminCache = { entry: null, lastUpdated: 0 };
41
+ this.#writersCache = { list: [], lastUpdated: 0 };
40
42
  this.#isInterrupted = false;
41
- if (DEBUG) {
42
- this.initTimestamp = Date.now();
43
- this.reachedMax = false;
44
- this.end = 0;
45
- this.begin = 0;
43
+ this.#keyDecodeCache = new Map();
44
+ }
45
+
46
+ /**
47
+ * Returns configurable poll interval.
48
+ * @returns {number} The poll interval in milliseconds.
49
+ */
50
+ #getPollInterval() {
51
+ return this.#config.pollInterval;
52
+ }
53
+
54
+ /**
55
+ * Selects a subset of valid validator candidates to connect to.
56
+ *
57
+ * Rules:
58
+ * - Assumes writers list is already pre-filtered and deduplicated from scan phase.
59
+ * - Excludes self.
60
+ * - Excludes already connected or pending nodes.
61
+ * - Applies admin connection restriction based on the threshold of ALIVE writers.
62
+ * - Randomizes selection (Fisher-Yates) to avoid bias.
63
+ * @param {Array<Object>} writers - The deduplicated list of active writers.
64
+ * @param {Object} adminEntry - The current admin node entry.
65
+ * @returns {{candidates: Array<Object>, slots: number}} The selected candidates and the number of available connection slots.
66
+ */
67
+ #selectCandidates(writers, adminEntry) {
68
+ const manager = this.#network.validatorConnectionManager;
69
+ const adminThreshold = this.#config.maxWritersForAdminIndexerConnection;
70
+
71
+ // Evaluates the admin blockage policy based on fully connected/alive writers
72
+ const aliveWritersCount = this.#countAliveWriters(writers, adminEntry);
73
+ const adminBlocked = adminEntry && aliveWritersCount >= adminThreshold;
74
+
75
+ // Target calculation: ensures the observer deducts its own slot from the network size
76
+ // only if it belongs to the valid writers list.
77
+ const isSelfInWriters = writers.some(w => w.address === this.#address);
78
+ const networkSize = isSelfInWriters ? writers.length - 1 : writers.length;
79
+ const target = Math.min(networkSize, this.#config.maxValidators);
80
+
81
+ const isSelfAdmin = this.#address === adminEntry?.address;
82
+ const totalConnected = manager.connectedValidators().length;
83
+
84
+ const available = writers.filter((w) => {
85
+ if (w.address === this.#address) return false;
86
+
87
+ const isConnected = manager.connected(w.publicKey);
88
+
89
+ // Checks if we are ALREADY trying to connect to this specific node
90
+ const isPending = this.#network.isConnectionPending(w.publicKeyHex);
91
+
92
+ // Skips nodes we are already interacting with
93
+ if (isConnected || isPending) return false;
94
+
95
+ // The admin is actively filtered out from the pool if the threshold policy is triggered
96
+ if (adminBlocked && !isSelfAdmin && w.address === adminEntry?.address) return false;
97
+
98
+ return true;
99
+ });
100
+
101
+ const result = [...available];
102
+
103
+ // Fisher-Yates shuffle
104
+ for (let i = 0; i < result.length; i++) {
105
+ const j = i + Math.floor(Math.random() * (result.length - i));
106
+ [result[i], result[j]] = [result[j], result[i]];
107
+ }
108
+
109
+ // Open slots based ONLY on who is ALREADY connected,
110
+ // Ignoring pending/non-active nodes to avoid blocking the queue.
111
+ const slots = Math.max(0, target - totalConnected);
112
+ const limit = Math.min(slots, VALIDATOR_CANDIDATES_PER_CYCLE, result.length);
113
+
114
+ return {
115
+ candidates: result.slice(0, limit),
116
+ slots: slots,
46
117
  }
47
118
  }
119
+
120
+ /**
121
+ * Enforces admin-specific connection rules.
122
+ *
123
+ * If the count of fully connected writers exceeds the threshold:
124
+ * - The admin node is excluded from the validator pool (handled in selection phase).
125
+ * - Non-admin nodes actively disconnect from the admin validator (if currently connected).
126
+ *
127
+ * NOTE:
128
+ * The admin is not forcibly disconnected from all roles.
129
+ * It can still maintain connections for tx submission and observation.
130
+ * @param {Array<Object>} writers - The deduplicated list of active writers.
131
+ * @param {Object} adminEntry - The current admin node entry.
132
+ */
133
+ #enforceAdminPolicy(writers, adminEntry) {
134
+ // Evaluates against alive network peers rather than dormant database entries
135
+ const aliveWritersCount = this.#countAliveWriters(writers, adminEntry);
136
+
137
+ if (aliveWritersCount < this.#config.maxWritersForAdminIndexerConnection) return;
138
+
139
+ this.#logger.debug("Validator threshold reached → enforcing admin policy");
140
+
141
+ const isAdmin = adminEntry && this.#address === adminEntry.address;
142
+ if (isAdmin) return;
48
143
 
49
- get state() {
50
- return this.#state;
144
+ // Non-admin nodes: ensure the admin is not kept as a validator connection
145
+ const adminWriter = writers.find((w) => w.address === adminEntry?.address);
146
+ if (adminWriter?.publicKey) {
147
+ this.#network.validatorConnectionManager.remove(adminWriter.publicKey);
148
+ }
51
149
  }
52
150
 
53
- // Original comment for this:
54
- // TODO: AFTER WHILE LOOP SIGNAL TO THE PROCESS THAT VALIDATOR OBSERVER STOPPED OPERATING.
55
- // OS CALLS, ACCUMULATORS, MAYBE THIS IS POSSIBLE TO CHECK I/O QUEUE IF IT COINTAIN IT. FOR NOW WE ARE USING SLEEP.
151
+ /**
152
+ * Removes active connections to writers that are no longer valid (e.g., removed/soft-deleted).
153
+ * @param {Uint8Array} publicKey - The public key of the stale connection to be removed.
154
+ */
155
+ #enforceNoStaleConnections(publicKey) {
156
+ const manager = this.#network.validatorConnectionManager;
157
+
158
+ if (manager.connected(publicKey)) {
159
+ manager.remove(publicKey);
160
+ this.#logger.debug(`Removed stale validator connection: ${b4a.toString(publicKey, "hex")}`);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Determines whether the observer should run.
166
+ * Controlled by a config flag + runtime interruption.
167
+ * @returns {boolean} True if the observer is enabled and not interrupted.
168
+ */
169
+ #shouldRun() {
170
+ return this.#config.enableValidatorObserver && !this.#isInterrupted;
171
+ }
172
+
173
+ /**
174
+ * Counts the number of physically active and connected writers in the network.
175
+ * This method bridges the gap between the Autobase state (which may include
176
+ * offline/dormant writers due to its append-only/soft-delete nature) and the
177
+ * actual physical network layer. It ensures the admin threshold policy is
178
+ * evaluated against truly active nodes rather than historical state.
179
+ *
180
+ * @param {Array<Object>} writers - The deduplicated list of writers from the Autobase.
181
+ * @param {Object} adminEntry - The current admin node entry.
182
+ * @returns {number} The count of alive, fully connected writers (including self).
183
+ */
184
+ #countAliveWriters(writers, adminEntry) {
185
+ let aliveCount = 0;
186
+ const manager = this.#network.validatorConnectionManager;
187
+
188
+ for (const w of writers) {
189
+ // Ignores the admin in the threshold calculation
190
+ if (w.address === adminEntry?.address) continue;
191
+
192
+ // Always counts the current node as alive if it's in the valid writers list
193
+ if (w.address === this.#address) {
194
+ aliveCount++;
195
+ continue;
196
+ }
197
+
198
+ // Counts only fully connected nodes (ignores pending or offline peers)
199
+ if (manager.connected(w.publicKey)) {
200
+ aliveCount++;
201
+ }
202
+ }
203
+
204
+ return aliveCount;
205
+ }
206
+
207
+ /**
208
+ * Starts the validator observer loop.
209
+ * Initializes the scheduler and bootstrap state.
210
+ */
56
211
  async start() {
57
212
  if (!this.#shouldRun()) {
58
- console.info('ValidatorObserverService can not start. Disabled by configuration.');
213
+ this.#logger.info('ValidatorObserverService can not start. Disabled by configuration.');
59
214
  return;
60
215
  }
216
+
61
217
  if (this.#scheduler && this.#scheduler.isRunning) {
62
- console.info('ValidatorObserverService is already started');
218
+ this.#logger.info('ValidatorObserverService is already started');
63
219
  return;
64
220
  }
65
221
 
222
+ const interval = this.#getPollInterval();
223
+ this.#scheduler = new Scheduler((next) => this.#worker(next), interval);
224
+ this.#bootstrapStartedAt = Date.now();
225
+ this.#hasBootstrapped = false;
66
226
  this.#isInterrupted = false;
67
- this.#scheduler = new Scheduler(next => this.#worker(next), POLL_INTERVAL);
68
227
  this.#scheduler.start();
228
+ this.#logger.debug("ValidatorObserverService started");
69
229
  }
70
230
 
231
+ /**
232
+ * Stops the observer gracefully.
233
+ * Prevents new work and waits for the current cycle to finish if requested.
234
+ * @param {boolean} [waitForCurrent=true] - Whether to wait for the current worker cycle to finish.
235
+ */
71
236
  async stopValidatorObserver(waitForCurrent = true) {
72
237
  if (!this.#scheduler) return;
238
+
73
239
  this.#isInterrupted = true;
240
+
74
241
  await this.#scheduler.stop(waitForCurrent);
75
242
  this.#scheduler = null;
76
- console.info('ValidatorObserverService: closing gracefully...');
243
+ this.#logger.debug("ValidatorObserverService stopped");
77
244
  }
78
245
 
246
+ /**
247
+ * Main worker loop (executed periodically).
248
+ *
249
+ * Flow:
250
+ * 1. Fetch admin + writers (cached).
251
+ * 2. Enforce admin connection policy.
252
+ * 3. Skip if the connection pool is full.
253
+ * 4. Select candidates.
254
+ * 5. Attempt connections (with pacing).
255
+ * @param {Function} next - Callback to schedule the next execution.
256
+ */
79
257
  async #worker(next) {
80
- if (!this.#network.validatorConnectionManager.maxConnectionsReached()) {
81
- if (DEBUG) this.begin = Date.now();
82
- const length = await this.#lengthEntry()
83
-
84
- const promises = [];
85
- for (let i = 0; i < VALIDATOR_CANDIDATES_PER_CYCLE; i++) {
86
- promises.push(this.#findValidator(this.#address, length + 1));
87
- await sleep(DELAY_INTERVAL); // Low key dangerous as the network progresses
88
- }
89
- await Promise.all(promises);
258
+ const interval = this.#getPollInterval();
259
+ if (!this.#shouldRun()) return next(interval);
90
260
 
91
- if (DEBUG) this.end = Date.now();
92
- debugLog('Worker cycle completed in (ms):', this.end - this.begin, '| Validator Connections:', this.#network.validatorConnectionManager.connectionCount(), " | Pending: ", this.#network.pendingConnectionsCount());
93
- }
94
- else if (DEBUG) {
95
- if (!this.reachedMax) {
96
- this.reachedMax = true;
97
- debugLog('Max validator connections reached. Skipping this cycle.');
98
- const now = Date.now();
99
- const elapsed = now - this.initTimestamp;
100
- debugLog('>>> Time elapsed since start (ms):', elapsed);
261
+ try {
262
+ const adminEntry = await this.#getAdminEntryCached();
263
+ const writers = await this.#getWritersCached(adminEntry);
264
+
265
+ if (writers.length === 0) return next(interval);
266
+
267
+ this.#enforceAdminPolicy(writers, adminEntry);
268
+
269
+ const manager = this.#network.validatorConnectionManager;
270
+ // CEILING: do not exceed the max connections limit
271
+ if (manager.maxConnectionsReached()) return next(interval);
272
+
273
+ const { candidates, slots} = this.#selectCandidates(writers, adminEntry);
274
+ if (candidates.length > 0) {
275
+ await this.#processCandidates(candidates, slots);
101
276
  }
277
+
278
+ } catch (err) {
279
+ this.#logger.error(`ValidatorObserver worker error: ${err.message}`);
102
280
  }
103
- next(POLL_INTERVAL);
281
+
282
+ next(interval);
104
283
  }
105
284
 
106
- async #findValidator(address, validatorListLength) {
107
- if (!this.#shouldRun()) return;
108
- const maxAttempts = 50; // TODO: make configurable
109
- let attempts = 0;
110
- let isValidatorValid = false;
111
- let validatorAddressBuffer = b4a.alloc(0);
285
+ /**
286
+ * Returns the cached admin entry with TTL.
287
+ * @returns {Promise<Object>} The admin entry object.
288
+ */
289
+ async #getAdminEntryCached() {
290
+ const now = Date.now();
291
+ const ttl = this.#config.adminCacheTTL;
292
+ if (this.#adminCache.entry && now - this.#adminCache.lastUpdated < ttl) return this.#adminCache.entry;
112
293
 
113
- while (attempts < maxAttempts && !isValidatorValid) {
114
- const rndIndex = Math.floor(Math.random() * validatorListLength);
115
- validatorAddressBuffer = await this.state.getWriterIndex(rndIndex);
116
- isValidatorValid = await this.#isValidatorValid(address, validatorAddressBuffer, validatorListLength);
117
- attempts++;
118
- }
294
+ const entry = await this.#state.getAdminEntry();
295
+ this.#adminCache = { entry, lastUpdated: now };
296
+ return entry;
297
+ }
298
+
299
+ /**
300
+ * Returns the cached writer list with dynamic TTL:
301
+ * - Short TTL during bootstrap (aggressive discovery).
302
+ * - Long TTL after bootstrap (stability).
303
+ *
304
+ * Bootstrap completes when:
305
+ * - Connection pool is full OR
306
+ * - Timeout is reached.
307
+ * @param {Object} adminEntry - The current admin node entry.
308
+ * @returns {Promise<Array<Object>>} The deduplicated list of writers.
309
+ */
310
+ async #getWritersCached(adminEntry) {
311
+ const now = Date.now();
312
+ const cachedList = this.#writersCache.list;
313
+ const lastUpdated = this.#writersCache.lastUpdated;
314
+ const manager = this.#network.validatorConnectionManager;
119
315
 
120
- if (attempts >= maxAttempts) {
121
- debugLog('Max attempts reached without finding a valid validator.');
316
+ if (!this.#hasBootstrapped && manager.maxConnectionsReached()) {
317
+ this.#hasBootstrapped = true;
318
+ this.#logger.debug("Bootstrap complete (pool full)");
122
319
  }
123
- else {
124
- debugLog(`Found valid validator to connect after ${attempts} attempts.`);
320
+
321
+ if (!this.#hasBootstrapped && now - this.#bootstrapStartedAt > this.#config.bootstrapTimeout) {
322
+ this.#hasBootstrapped = true;
323
+ this.#logger.debug("Bootstrap complete (timeout)");
125
324
  }
325
+
326
+ const short = this.#config.writersShortCacheTTL;
327
+ const long = this.#config.writersLongCacheTTL;
328
+
329
+ const dynamicTTL = this.#hasBootstrapped ? long : short;
330
+ if (cachedList.length > 0 && now - lastUpdated < dynamicTTL) return cachedList;
331
+
332
+ const list = await this.#scanAutobaseWriters(adminEntry);
333
+ this.#writersCache = { list, lastUpdated: now };
126
334
 
127
- if (!isValidatorValid) return;
335
+ return list;
336
+ }
128
337
 
129
- const validatorAddress = bufferToAddress(validatorAddressBuffer, this.#config.addressPrefix);
130
- const validatorPubKeyBuffer = PeerWallet.decodeBech32m(validatorAddress);
131
- const validatorPubKeyHex = validatorPubKeyBuffer.toString('hex');
132
- const adminEntry = await this.state.getAdminEntry();
338
+ /**
339
+ * Sequentially processes selected candidates.
340
+ * Applies delay between connection attempts to avoid network bursts.
341
+ * @param {Array<Object>} candidates - The list of selected validator candidates.
342
+ * @param {number} slots - The number of available connection slots.
343
+ */
344
+ async #processCandidates(candidates, slots) {
345
+ const delay = this.#config.validatorConnectionAttemptDelay;
133
346
 
134
- if (validatorAddress !== adminEntry?.address || validatorListLength < this.#config.maxWritersForAdminIndexerConnection) {
135
- this.#network.tryConnect(validatorPubKeyHex, 'validator');
136
- }
137
- };
347
+ let attempts = 0;
138
348
 
139
- async #isValidatorValid(forbiddenAddress, validatorAddressBuffer, validatorListLength) {
140
- if (validatorAddressBuffer === null || b4a.byteLength(validatorAddressBuffer) !== this.#config.addressLength) return false;
349
+ for (const candidate of candidates) {
350
+ if (!this.#shouldRun()) break;
351
+ if (attempts >= slots) break;
141
352
 
142
- const validatorAddress = bufferToAddress(validatorAddressBuffer, this.#config.addressPrefix);
143
- if (validatorAddress === forbiddenAddress) return false;
353
+ const isConnected = this.#network.validatorConnectionManager.connected(candidate.publicKey);
354
+ const isPending = this.#network.isConnectionPending(candidate.publicKeyHex);
144
355
 
145
- const validatorPubKeyBuffer = PeerWallet.decodeBech32m(validatorAddress);
146
- const validatorEntry = await this.state.getNodeEntry(validatorAddress);
147
- const adminEntry = await this.state.getAdminEntry();
356
+ if (isConnected || isPending) continue;
148
357
 
149
- if (this.#network.isConnectionPending(validatorPubKeyBuffer.toString('hex'))) {
150
- return false;
151
- }
358
+ const result = await this.#tryConnect(candidate);
359
+ if (!result || result === CONNECTION_STATUS.IGNORED) continue;
360
+ this.#logger.debug(`Validator connection to ${candidate.publicKeyHex} resulted in: ${result}`);
152
361
 
153
- if (validatorAddress === adminEntry?.address && validatorListLength >= this.#config.maxWritersForAdminIndexerConnection) {
154
- if (this.#network.validatorConnectionManager.exists(validatorPubKeyBuffer)) {
155
- this.#network.validatorConnectionManager.remove(validatorPubKeyBuffer)
362
+ attempts++;
363
+
364
+ if (attempts < slots) {
365
+ await new Promise((resolve) => setTimeout(resolve, delay));
156
366
  }
157
367
  }
368
+ }
158
369
 
159
- // Connection validation rules:
160
- // - Cannot connect if already connected to a validator
161
- // - Validator must exist and be a writer
162
- // - Cannot connect to indexers, except for admin-indexer
163
- // - Admin-indexer connection is allowed only when writers length is below maxWritersForAdminIndexerConnection
164
- if (this.#network.validatorConnectionManager.connected(validatorPubKeyBuffer) ||
165
- this.#network.validatorConnectionManager.maxConnectionsReached() ||
166
- validatorEntry === null ||
167
- !validatorEntry.isWriter ||
168
- (validatorEntry.isIndexer && (validatorAddress !== adminEntry?.address || validatorListLength >= this.#config.maxWritersForAdminIndexerConnection))
169
- ) {
170
- return false;
370
+ /**
371
+ * Scans Autobase writer entries and builds the active validator list.
372
+ *
373
+ * Rules:
374
+ * - Skips invalid/malformed entries.
375
+ * - Skips non-admin indexers.
376
+ * - Deduplicates nodes by mapping their latest state to a unique address.
377
+ * - Removes stale connections for nodes flagged with 'isRemoved'.
378
+ * - Includes only valid and active writer nodes.
379
+ * @param {Object} adminEntry - The current admin node entry.
380
+ * @returns {Promise<Array<Object>>} A promise resolving to the deduplicated array of active writers.
381
+ */
382
+ async #scanAutobaseWriters(adminEntry) {
383
+ // Prevents memory leak in case of a very long-running node
384
+ if (this.#keyDecodeCache.size > MAX_KEY_DECODE_CACHE_SIZE) {
385
+ this.#keyDecodeCache.clear();
171
386
  }
172
387
 
173
- return true;
174
- }
388
+ // Map is used for deduplication, ensuring that each node physical address
389
+ // occupies only a single slot despite the append-only nature of Autobase
390
+ const activeMap = new Map();
175
391
 
176
- #shouldRun() {
177
- return this.#config.enableValidatorObserver && !this.#isInterrupted
392
+ try {
393
+ for await (const { key, value } of this.#state.base.system.list()) {
394
+ if (!this.#shouldRun()) break;
395
+
396
+ if (!key || b4a.byteLength(key) !== WRITER_BYTE_LENGTH) continue;
397
+ if (!value) continue;
398
+
399
+ const writerKeyHex = b4a.toString(key, "hex");
400
+ let decodedIdentity = this.#keyDecodeCache.get(writerKeyHex);
401
+
402
+ if (decodedIdentity === undefined) {
403
+ const addressBuffer = await this.#state.getRegisteredWriterKey(writerKeyHex);
404
+
405
+ if (!addressBuffer || b4a.byteLength(addressBuffer) !== this.#config.addressLength) {
406
+ this.#keyDecodeCache.set(writerKeyHex, null);
407
+ continue;
408
+ }
409
+
410
+ const addr = bufferToAddress(addressBuffer, this.#config.addressPrefix);
411
+ const publicKey = tracCryptoApi.address.decode(addr);
412
+
413
+ if (!publicKey) {
414
+ this.#keyDecodeCache.set(writerKeyHex, null);
415
+ continue;
416
+ }
417
+
418
+ const publicKeyHex = b4a.toString(publicKey, "hex");
419
+ decodedIdentity = { addr, publicKey, publicKeyHex };
420
+ this.#keyDecodeCache.set(writerKeyHex, decodedIdentity);
421
+ }
422
+
423
+ if (decodedIdentity === null) continue;
424
+
425
+ const { addr, publicKey, publicKeyHex } = decodedIdentity;
426
+
427
+ if (value.isIndexer && addr !== adminEntry?.address) continue;
428
+
429
+ // Handles soft-delete: drops stale connections and removes the node from the pool
430
+ if (value.isRemoved) {
431
+ this.#enforceNoStaleConnections(publicKey);
432
+ activeMap.delete(addr);
433
+ continue;
434
+ }
435
+
436
+ // Registers or overwrites to maintain a single source of truth per machine
437
+ activeMap.set(addr, { key, address: addr, publicKey, publicKeyHex });
438
+ }
439
+ } catch (err) {
440
+ this.#logger.error(`Autobase writer scan error: ${err.message}`);
441
+ }
442
+
443
+ return Array.from(activeMap.values());
178
444
  }
179
445
 
180
- async #lengthEntry() {
181
- const lengthEntry = await this.state.getWriterLength();
182
- return Number.isInteger(lengthEntry) && lengthEntry > 0 ? lengthEntry : 0;
446
+ /**
447
+ * Attempts to establish a connection with a validator candidate.
448
+ *
449
+ * Assumes the candidate was pre-filtered and validated during scan/selection.
450
+ *
451
+ * Behavior:
452
+ * - Delegates connection attempt to the network layer.
453
+ * @param {Object} candidate - The target validator candidate object.
454
+ * @returns {Promise<string>} The connection status string returned by the network layer.
455
+ */
456
+ async #tryConnect(candidate) {
457
+ try {
458
+ return await this.#network.tryConnect(candidate.publicKeyHex, "validator");
459
+ } catch (err) {
460
+ this.#logger.error(`Validator connection attempt failed: ${err.message}`);
461
+ }
183
462
  }
184
463
  }
185
464
 
186
- export default ValidatorObserverService;
465
+ export default ValidatorObserverService;