trac-msb 0.2.18 → 0.2.20

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 (381) 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 +25 -8
  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 +138 -10
  16. package/src/config/env.js +36 -18
  17. package/src/core/network/Network.js +130 -34
  18. package/src/core/network/protocols/ProtocolInterface.js +5 -5
  19. package/src/core/network/protocols/connectionPolicies.js +86 -0
  20. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +1 -1
  21. package/src/core/network/protocols/legacy/handlers/BaseStateOperationHandler.js +2 -2
  22. package/src/core/network/protocols/legacy/handlers/LegacyGetRequestHandler.js +3 -3
  23. package/src/core/network/protocols/legacy/handlers/LegacyRoleOperationHandler.js +1 -1
  24. package/src/core/network/protocols/legacy/handlers/LegacySubnetworkOperationHandler.js +1 -1
  25. package/src/core/network/protocols/legacy/handlers/LegacyTransferOperationHandler.js +1 -1
  26. package/src/core/network/protocols/legacy/validators/base/BaseResponse.js +7 -10
  27. package/src/core/network/protocols/shared/validators/PartialBootstrapDeploymentValidator.js +2 -2
  28. package/src/core/network/protocols/shared/validators/PartialOperationValidator.js +23 -23
  29. package/src/core/network/protocols/shared/validators/PartialRoleAccessValidator.js +14 -14
  30. package/src/core/network/protocols/shared/validators/PartialTransactionValidator.js +5 -5
  31. package/src/core/network/protocols/shared/validators/PartialTransferValidator.js +12 -11
  32. package/src/core/network/protocols/v1/V1ProtocolError.js +5 -69
  33. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +3 -2
  34. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +130 -247
  35. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +6 -5
  36. package/src/core/network/protocols/v1/handlers/broadcastTransaction/BroadcastTransactionErrorMapper.js +84 -0
  37. package/src/core/network/protocols/v1/handlers/broadcastTransaction/BroadcastTransactionOperationStrategies.js +128 -0
  38. package/src/core/network/protocols/v1/handlers/broadcastTransaction/BroadcastTransactionPayloadUtils.js +26 -0
  39. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +32 -31
  40. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +5 -3
  41. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +34 -50
  42. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +1 -1
  43. package/src/core/network/services/ConnectionManager.js +11 -10
  44. package/src/core/network/services/MessageOrchestrator.js +53 -17
  45. package/src/core/network/services/PendingRequestService.js +22 -19
  46. package/src/core/network/services/TransactionCommitService.js +36 -36
  47. package/src/core/network/services/TransactionPoolService.js +39 -39
  48. package/src/core/network/services/TransactionRateLimiterService.js +8 -4
  49. package/src/core/network/services/ValidatorObserverService.js +397 -110
  50. package/src/core/state/State.js +190 -138
  51. package/src/core/state/utils/address.js +4 -9
  52. package/src/core/state/utils/balance.js +7 -7
  53. package/src/core/state/utils/roles.js +1 -1
  54. package/src/index.js +316 -275
  55. package/src/messages/network/v1/NetworkMessageBuilder.js +12 -21
  56. package/src/messages/network/v1/networkMessageFactory.js +1 -1
  57. package/src/messages/state/ApplyStateMessageBuilder.js +6 -6
  58. package/src/messages/state/applyStateMessageFactory.js +1 -1
  59. package/src/utils/buffer.js +1 -1
  60. package/src/utils/check.js +12 -13
  61. package/src/utils/constants.js +7 -0
  62. package/src/utils/errors.js +20 -0
  63. package/src/utils/fileUtils.js +12 -3
  64. package/src/utils/helpers.js +2 -2
  65. package/src/utils/migrationUtils.js +3 -3
  66. package/.dockerignore +0 -16
  67. package/CODE_OF_CONDUCT.md +0 -128
  68. package/docker-compose.yml +0 -17
  69. package/dockerfile +0 -41
  70. package/docs/fee_distribution.md +0 -89
  71. package/docs/trac_network_http_api.openapi.yaml +0 -889
  72. package/migration/.gitkeep +0 -0
  73. package/proto/applyOperations.proto +0 -117
  74. package/proto/network/v1/enums/message_type.proto +0 -16
  75. package/proto/network/v1/enums/result_code.proto +0 -84
  76. package/proto/network/v1/messages/broadcast_transaction_request.proto +0 -9
  77. package/proto/network/v1/messages/broadcast_transaction_response.proto +0 -13
  78. package/proto/network/v1/messages/liveness_request.proto +0 -8
  79. package/proto/network/v1/messages/liveness_response.proto +0 -11
  80. package/proto/network/v1/network_message.proto +0 -22
  81. package/rpc/rpc_services.js +0 -181
  82. package/scripts/generate-protobufs.js +0 -67
  83. package/src/core/network/identity/NetworkWalletFactory.js +0 -77
  84. package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +0 -27
  85. package/src/utils/cli.js +0 -130
  86. package/src/utils/cliCommands.js +0 -280
  87. package/tests/acceptance/v1/account/account.test.mjs +0 -129
  88. package/tests/acceptance/v1/balance/balance.test.mjs +0 -54
  89. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +0 -116
  90. package/tests/acceptance/v1/confirmed-length/confirmed-length.test.mjs +0 -19
  91. package/tests/acceptance/v1/fee/fee.test.mjs +0 -11
  92. package/tests/acceptance/v1/health/health.test.mjs +0 -33
  93. package/tests/acceptance/v1/rpc.test.mjs +0 -95
  94. package/tests/acceptance/v1/tx/tx.test.mjs +0 -133
  95. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +0 -241
  96. package/tests/acceptance/v1/tx-hashes/tx-hashes.test.mjs +0 -72
  97. package/tests/acceptance/v1/tx-payloads-bulk/tx-payloads-bulk.test.mjs +0 -27
  98. package/tests/acceptance/v1/txv/txv.test.mjs +0 -11
  99. package/tests/acceptance/v1/unconfirmed-length/unconfirmed-length.test.mjs +0 -11
  100. package/tests/fixtures/apply.fixtures.js +0 -77
  101. package/tests/fixtures/assembleMessage.fixtures.js +0 -41
  102. package/tests/fixtures/check.fixtures.js +0 -428
  103. package/tests/fixtures/networkV1.fixtures.js +0 -59
  104. package/tests/fixtures/protobuf.fixtures.js +0 -388
  105. package/tests/helpers/StateNetworkFactory.js +0 -155
  106. package/tests/helpers/address.js +0 -6
  107. package/tests/helpers/autobaseTestHelpers.js +0 -419
  108. package/tests/helpers/config.js +0 -4
  109. package/tests/helpers/createTestSignature.js +0 -13
  110. package/tests/helpers/regexHelper.js +0 -3
  111. package/tests/helpers/setupApplyTests.js +0 -587
  112. package/tests/helpers/transactionPayloads.mjs +0 -92
  113. package/tests/helpers/wrapper.js +0 -28
  114. package/tests/unit/messages/messages.test.js +0 -12
  115. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +0 -436
  116. package/tests/unit/messages/network/NetworkMessageDirector.test.js +0 -347
  117. package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +0 -525
  118. package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +0 -237
  119. package/tests/unit/network/LegacyNetworkMessageRouter.test.js +0 -54
  120. package/tests/unit/network/NetworkWalletFactory.test.js +0 -156
  121. package/tests/unit/network/ProtocolSession.test.js +0 -127
  122. package/tests/unit/network/networkModule.test.js +0 -13
  123. package/tests/unit/network/services/ConnectionManager.test.js +0 -450
  124. package/tests/unit/network/services/MessageOrchestrator.test.js +0 -445
  125. package/tests/unit/network/services/PendingRequestService.test.js +0 -431
  126. package/tests/unit/network/services/TransactionCommitService.test.js +0 -246
  127. package/tests/unit/network/services/TransactionPoolService.test.js +0 -489
  128. package/tests/unit/network/services/TransactionRateLimiterService.test.js +0 -139
  129. package/tests/unit/network/services/ValidatorHealthCheckService.test.js +0 -115
  130. package/tests/unit/network/services/services.test.js +0 -17
  131. package/tests/unit/network/utils/v1TestUtils.js +0 -153
  132. package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +0 -151
  133. package/tests/unit/network/v1/V1BaseOperation.test.js +0 -356
  134. package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +0 -129
  135. package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +0 -53
  136. package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +0 -512
  137. package/tests/unit/network/v1/V1LivenessRequest.test.js +0 -32
  138. package/tests/unit/network/v1/V1LivenessResponse.test.js +0 -45
  139. package/tests/unit/network/v1/V1ResultCode.test.js +0 -84
  140. package/tests/unit/network/v1/V1ValidationSchema.test.js +0 -13
  141. package/tests/unit/network/v1/connectionPolicies.test.js +0 -49
  142. package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +0 -284
  143. package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +0 -794
  144. package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +0 -193
  145. package/tests/unit/network/v1/v1.handlers.test.js +0 -15
  146. package/tests/unit/network/v1/v1.test.js +0 -19
  147. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +0 -119
  148. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +0 -136
  149. package/tests/unit/network/v1/v1ValidationSchema/common.test.js +0 -308
  150. package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +0 -90
  151. package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +0 -133
  152. package/tests/unit/state/State.test.js +0 -80
  153. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +0 -42
  154. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +0 -276
  155. package/tests/unit/state/apply/addAdmin/adminEntryEncodingFailureScenario.js +0 -30
  156. package/tests/unit/state/apply/addAdmin/adminEntryExistsScenario.js +0 -78
  157. package/tests/unit/state/apply/addAdmin/nodeEntryInitializationFailureScenario.js +0 -30
  158. package/tests/unit/state/apply/addAdmin/nonBootstrapNodeScenario.js +0 -68
  159. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +0 -159
  160. package/tests/unit/state/apply/addIndexer/addIndexerHappyPathScenario.js +0 -39
  161. package/tests/unit/state/apply/addIndexer/addIndexerMultipleIndexersInTheNetworkScenario.js +0 -167
  162. package/tests/unit/state/apply/addIndexer/addIndexerPretenderAlreadyIndexerScenario.js +0 -21
  163. package/tests/unit/state/apply/addIndexer/addIndexerPretenderNotWriterScenario.js +0 -21
  164. package/tests/unit/state/apply/addIndexer/addIndexerRemoveAndReAddScenario.js +0 -186
  165. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +0 -443
  166. package/tests/unit/state/apply/addIndexer/addIndexerWriterKeyAlreadyRegisteredScenario.js +0 -32
  167. package/tests/unit/state/apply/addIndexer/state.apply.addIndexer.test.js +0 -297
  168. package/tests/unit/state/apply/addWriter/addWriterHappyPathScenario.js +0 -41
  169. package/tests/unit/state/apply/addWriter/addWriterInvalidValidatorSignatureScenario.js +0 -32
  170. package/tests/unit/state/apply/addWriter/addWriterNewWkScenario.js +0 -149
  171. package/tests/unit/state/apply/addWriter/addWriterRequesterAlreadyWriterScenario.js +0 -21
  172. package/tests/unit/state/apply/addWriter/addWriterRequesterBalanceInsufficientScenario.js +0 -21
  173. package/tests/unit/state/apply/addWriter/addWriterRequesterEntryDecodeFailureScenario.js +0 -19
  174. package/tests/unit/state/apply/addWriter/addWriterRequesterEntryMissingScenario.js +0 -19
  175. package/tests/unit/state/apply/addWriter/addWriterRequesterIndexerScenario.js +0 -21
  176. package/tests/unit/state/apply/addWriter/addWriterRequesterNotWhitelistedScenario.js +0 -21
  177. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +0 -766
  178. package/tests/unit/state/apply/addWriter/addWriterStakeBalanceUpdateFailureScenario.js +0 -50
  179. package/tests/unit/state/apply/addWriter/addWriterStakeInsufficientBalanceScenario.js +0 -29
  180. package/tests/unit/state/apply/addWriter/addWriterStakeInvalidBalanceScenario.js +0 -29
  181. package/tests/unit/state/apply/addWriter/addWriterStakeInvalidEntryScenario.js +0 -21
  182. package/tests/unit/state/apply/addWriter/addWriterStakeStakedBalanceFailureScenario.js +0 -37
  183. package/tests/unit/state/apply/addWriter/addWriterStakeSubtractFailureScenario.js +0 -42
  184. package/tests/unit/state/apply/addWriter/addWriterValidatorRewardScenario.js +0 -106
  185. package/tests/unit/state/apply/addWriter/addWriterWriterKeyMismatchScenario.js +0 -54
  186. package/tests/unit/state/apply/addWriter/addWriterWriterKeyOwnershipScenario.js +0 -54
  187. package/tests/unit/state/apply/addWriter/addWriterZeroWriterKeyScenario.js +0 -29
  188. package/tests/unit/state/apply/addWriter/state.apply.addWriter.test.js +0 -309
  189. package/tests/unit/state/apply/adminRecovery/adminRecoveryHappyPathScenario.js +0 -30
  190. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +0 -881
  191. package/tests/unit/state/apply/adminRecovery/state.apply.adminRecovery.test.js +0 -435
  192. package/tests/unit/state/apply/appendWhitelist/appendWhitelistBanAndReapplyScenario.js +0 -78
  193. package/tests/unit/state/apply/appendWhitelist/appendWhitelistExistingReaderHappyPathScenario.js +0 -98
  194. package/tests/unit/state/apply/appendWhitelist/appendWhitelistFeeAfterDisableScenario.js +0 -66
  195. package/tests/unit/state/apply/appendWhitelist/appendWhitelistHappyPathScenario.js +0 -55
  196. package/tests/unit/state/apply/appendWhitelist/appendWhitelistInsufficientAdminBalanceScenario.js +0 -103
  197. package/tests/unit/state/apply/appendWhitelist/appendWhitelistNodeAlreadyWhitelistedScenario.js +0 -60
  198. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +0 -189
  199. package/tests/unit/state/apply/appendWhitelist/state.apply.appendWhitelist.test.js +0 -220
  200. package/tests/unit/state/apply/balanceInitialization/balanceInitializationHappyPathScenario.js +0 -82
  201. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +0 -111
  202. package/tests/unit/state/apply/balanceInitialization/invalidAmountScenario.js +0 -45
  203. package/tests/unit/state/apply/balanceInitialization/nodeEntryBalanceUpdateFailureScenario.js +0 -82
  204. package/tests/unit/state/apply/balanceInitialization/state.apply.balanceInitialization.test.js +0 -189
  205. package/tests/unit/state/apply/banValidator/banValidatorBanAndReWhitelistScenario.js +0 -156
  206. package/tests/unit/state/apply/banValidator/banValidatorHappyPathScenario.js +0 -36
  207. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +0 -531
  208. package/tests/unit/state/apply/banValidator/banValidatorSequentialBansScenario.js +0 -74
  209. package/tests/unit/state/apply/banValidator/banValidatorTargetDecodeFailureScenario.js +0 -19
  210. package/tests/unit/state/apply/banValidator/banValidatorTargetIndexerScenario.js +0 -32
  211. package/tests/unit/state/apply/banValidator/banValidatorTargetNodeEntryMissingScenario.js +0 -19
  212. package/tests/unit/state/apply/banValidator/banValidatorTargetRoleUpdateFailureScenario.js +0 -19
  213. package/tests/unit/state/apply/banValidator/banValidatorWhitelistedNonWriterScenario.js +0 -38
  214. package/tests/unit/state/apply/banValidator/banValidatorWhitelistedZeroBalanceScenario.js +0 -91
  215. package/tests/unit/state/apply/banValidator/banValidatorWithdrawFailureScenario.js +0 -19
  216. package/tests/unit/state/apply/banValidator/state.apply.banValidator.test.js +0 -266
  217. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentDuplicateRegistrationScenario.js +0 -143
  218. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentHappyPathScenario.js +0 -26
  219. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentIncompleteOperationScenario.js +0 -94
  220. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentInvalidDeploymentEntryScenario.js +0 -37
  221. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentMultipleBootstrapScenario.js +0 -86
  222. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +0 -347
  223. package/tests/unit/state/apply/bootstrapDeployment/invalidValidatorNodeEntryScenario.js +0 -57
  224. package/tests/unit/state/apply/bootstrapDeployment/state.apply.bootstrapDeployment.test.js +0 -429
  225. package/tests/unit/state/apply/common/access-control/adminConsistencyMismatchScenario.js +0 -120
  226. package/tests/unit/state/apply/common/access-control/adminEntryDecodeFailureScenario.js +0 -130
  227. package/tests/unit/state/apply/common/access-control/adminEntryExistsScenario.js +0 -93
  228. package/tests/unit/state/apply/common/access-control/adminEntryMissingScenario.js +0 -108
  229. package/tests/unit/state/apply/common/access-control/adminOnlyGuardScenario.js +0 -126
  230. package/tests/unit/state/apply/common/access-control/adminPublicKeyDecodeFailureScenario.js +0 -121
  231. package/tests/unit/state/apply/common/access-control/roleAccessOperationValidationScenario.js +0 -50
  232. package/tests/unit/state/apply/common/adminControlOperationValidationScenario.js +0 -56
  233. package/tests/unit/state/apply/common/balances/adminEntryUpdateFailureScenario.js +0 -52
  234. package/tests/unit/state/apply/common/balances/base/requesterBalanceScenarioBase.js +0 -198
  235. package/tests/unit/state/apply/common/balances/feeDecodeFailureScenario.js +0 -52
  236. package/tests/unit/state/apply/common/balances/requesterBalanceDecodeFailureScenario.js +0 -15
  237. package/tests/unit/state/apply/common/balances/requesterBalanceFeeApplicationFailureScenario.js +0 -11
  238. package/tests/unit/state/apply/common/balances/requesterBalanceInsufficientScenario.js +0 -15
  239. package/tests/unit/state/apply/common/balances/requesterBalanceUpdateFailureScenario.js +0 -11
  240. package/tests/unit/state/apply/common/balances/validatorEntryRewardFailureScenario.js +0 -11
  241. package/tests/unit/state/apply/common/balances/validatorEntryUpdateFailureScenario.js +0 -11
  242. package/tests/unit/state/apply/common/balances/validatorNodeEntryDecodeFailureScenario.js +0 -40
  243. package/tests/unit/state/apply/common/base/OperationValidationScenarioBase.js +0 -114
  244. package/tests/unit/state/apply/common/commonScenarioHelper.js +0 -103
  245. package/tests/unit/state/apply/common/indexer/indexerNodeEntryDecodeFailureScenario.js +0 -36
  246. package/tests/unit/state/apply/common/indexer/indexerNodeEntryMissingScenario.js +0 -36
  247. package/tests/unit/state/apply/common/indexer/indexerRoleUpdateFailureScenario.js +0 -29
  248. package/tests/unit/state/apply/common/indexer/indexerSequenceStateInvalidScenario.js +0 -66
  249. package/tests/unit/state/apply/common/invalidMessageComponentValidationScenario.js +0 -84
  250. package/tests/unit/state/apply/common/nodeEntryInitializationFailureScenario.js +0 -47
  251. package/tests/unit/state/apply/common/operationAlreadyAppliedScenario.js +0 -85
  252. package/tests/unit/state/apply/common/payload-structure/addressWithInvalidPublicKeyScenario.js +0 -52
  253. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +0 -54
  254. package/tests/unit/state/apply/common/payload-structure/invalidAddressValidationScenario.js +0 -73
  255. package/tests/unit/state/apply/common/payload-structure/invalidHashValidationScenario.js +0 -71
  256. package/tests/unit/state/apply/common/payload-structure/invalidPayloadValidationScenario.js +0 -31
  257. package/tests/unit/state/apply/common/payload-structure/invalidSignatureValidationScenario.js +0 -142
  258. package/tests/unit/state/apply/common/payload-structure/partialOperationValidationScenario.js +0 -87
  259. package/tests/unit/state/apply/common/requester/requesterNodeEntryBufferMissingScenario.js +0 -71
  260. package/tests/unit/state/apply/common/requester/requesterNodeEntryDecodeFailureScenario.js +0 -73
  261. package/tests/unit/state/apply/common/requester/requesterNodeEntryMissingScenario.js +0 -36
  262. package/tests/unit/state/apply/common/requesterAddressValidationScenario.js +0 -44
  263. package/tests/unit/state/apply/common/requesterPublicKeyValidationScenario.js +0 -25
  264. package/tests/unit/state/apply/common/transactionValidityMismatchScenario.js +0 -98
  265. package/tests/unit/state/apply/common/validatorConsistency/base/validatorConsistencyScenarioBase.js +0 -202
  266. package/tests/unit/state/apply/common/validatorConsistency/validatorEntryDecodeFailureScenario.js +0 -17
  267. package/tests/unit/state/apply/common/validatorConsistency/validatorEntryMissingScenario.js +0 -44
  268. package/tests/unit/state/apply/common/validatorConsistency/validatorInactiveScenario.js +0 -19
  269. package/tests/unit/state/apply/common/validatorConsistency/validatorWriterKeyMismatchScenario.js +0 -18
  270. package/tests/unit/state/apply/common/validatorEntryValidation/base/validatorEntryValidationScenarioBase.js +0 -315
  271. package/tests/unit/state/apply/common/validatorEntryValidation/validatorEntryInvalidBalanceScenario.js +0 -18
  272. package/tests/unit/state/apply/common/writerKeyExistsValidationScenario.js +0 -43
  273. package/tests/unit/state/apply/disableInitialization/disableInitializationAlreadyDisabledScenario.js +0 -53
  274. package/tests/unit/state/apply/disableInitialization/disableInitializationHappyPathScenario.js +0 -24
  275. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +0 -204
  276. package/tests/unit/state/apply/disableInitialization/state.apply.disableInitialization.test.js +0 -161
  277. package/tests/unit/state/apply/missing-tests.md +0 -18
  278. package/tests/unit/state/apply/removeIndexer/removeIndexerHappyPathScenario.js +0 -58
  279. package/tests/unit/state/apply/removeIndexer/removeIndexerReAddAndRemoveAgainScenario.js +0 -98
  280. package/tests/unit/state/apply/removeIndexer/removeIndexerRemoveMultipleIndexersScenario.js +0 -167
  281. package/tests/unit/state/apply/removeIndexer/removeIndexerScenarioHelpers.js +0 -429
  282. package/tests/unit/state/apply/removeIndexer/removeIndexerTargetNotIndexerScenario.js +0 -22
  283. package/tests/unit/state/apply/removeIndexer/removeIndexerWriterKeyMissingScenario.js +0 -20
  284. package/tests/unit/state/apply/removeIndexer/state.apply.removeIndexer.test.js +0 -291
  285. package/tests/unit/state/apply/removeWriter/removeWriterAndAddWriterAgainScenario.js +0 -87
  286. package/tests/unit/state/apply/removeWriter/removeWriterHappyPathScenario.js +0 -38
  287. package/tests/unit/state/apply/removeWriter/removeWriterInvalidValidatorSignatureScenario.js +0 -32
  288. package/tests/unit/state/apply/removeWriter/removeWriterRequesterBalanceInsufficientScenario.js +0 -21
  289. package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryDecodeFailureScenario.js +0 -19
  290. package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryMissingScenario.js +0 -19
  291. package/tests/unit/state/apply/removeWriter/removeWriterRequesterIndexerScenario.js +0 -21
  292. package/tests/unit/state/apply/removeWriter/removeWriterRequesterNotWriterScenario.js +0 -21
  293. package/tests/unit/state/apply/removeWriter/removeWriterRequesterRoleUpdateFailureScenario.js +0 -19
  294. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +0 -348
  295. package/tests/unit/state/apply/removeWriter/removeWriterThroughWriterValidatorScenario.js +0 -113
  296. package/tests/unit/state/apply/removeWriter/removeWriterUnstakeFailureScenario.js +0 -33
  297. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyMismatchScenario.js +0 -21
  298. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyOwnershipScenario.js +0 -26
  299. package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyRegistryMissingScenario.js +0 -22
  300. package/tests/unit/state/apply/removeWriter/state.apply.removeWriter.test.js +0 -307
  301. package/tests/unit/state/apply/state.apply.test.js +0 -24
  302. package/tests/unit/state/apply/transfer/state.apply.transfer.test.js +0 -819
  303. package/tests/unit/state/apply/transfer/transferContractSchemaValidationScenario.js +0 -22
  304. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +0 -146
  305. package/tests/unit/state/apply/transfer/transferDoubleSpendSameBatchScenario.js +0 -63
  306. package/tests/unit/state/apply/transfer/transferDoubleSpendSingleValidatorScenario.js +0 -67
  307. package/tests/unit/state/apply/transfer/transferExistingRecipientAmountScenario.js +0 -31
  308. package/tests/unit/state/apply/transfer/transferExistingRecipientZeroAmountScenario.js +0 -31
  309. package/tests/unit/state/apply/transfer/transferHandlerGuardScenarios.js +0 -22
  310. package/tests/unit/state/apply/transfer/transferHappyPathScenario.js +0 -8
  311. package/tests/unit/state/apply/transfer/transferInvalidIncomingDataScenario.js +0 -66
  312. package/tests/unit/state/apply/transfer/transferNewRecipientAmountScenario.js +0 -31
  313. package/tests/unit/state/apply/transfer/transferNewRecipientZeroAmountScenario.js +0 -31
  314. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +0 -1170
  315. package/tests/unit/state/apply/transfer/transferSelfTransferAmountScenario.js +0 -38
  316. package/tests/unit/state/apply/transfer/transferSelfTransferZeroAmountScenario.js +0 -38
  317. package/tests/unit/state/apply/transfer/transferValidatorRecipientAmountScenario.js +0 -38
  318. package/tests/unit/state/apply/transfer/transferValidatorRecipientZeroAmountScenario.js +0 -38
  319. package/tests/unit/state/apply/txOperation/state.apply.txOperation.test.js +0 -318
  320. package/tests/unit/state/apply/txOperation/txOperationBootstrapNotRegisteredScenario.js +0 -70
  321. package/tests/unit/state/apply/txOperation/txOperationDifferentValidatorCreatorHappyPathScenario.js +0 -23
  322. package/tests/unit/state/apply/txOperation/txOperationInvalidDeploymentEntryScenario.js +0 -48
  323. package/tests/unit/state/apply/txOperation/txOperationInvalidFeeAmountScenario.js +0 -39
  324. package/tests/unit/state/apply/txOperation/txOperationInvalidSubnetCreatorAddressScenario.js +0 -46
  325. package/tests/unit/state/apply/txOperation/txOperationRequesterCreatorHappyPathScenario.js +0 -21
  326. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +0 -432
  327. package/tests/unit/state/apply/txOperation/txOperationStandardHappyPathScenario.js +0 -21
  328. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddCreatorBalanceFailureScenario.js +0 -26
  329. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBalanceFailureScenario.js +0 -25
  330. package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBonusFailureScenario.js +0 -27
  331. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeCreatorEntryScenario.js +0 -18
  332. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeRequesterEntryScenario.js +0 -17
  333. package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeValidatorEntryScenario.js +0 -31
  334. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardBypassScenario.js +0 -49
  335. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardScenarioFactory.js +0 -93
  336. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInsufficientRequesterBalanceScenario.js +0 -28
  337. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidCreatorBalanceScenario.js +0 -29
  338. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterBalanceScenario.js +0 -28
  339. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterEntryScenario.js +0 -17
  340. package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidValidatorBalanceScenario.js +0 -33
  341. package/tests/unit/state/apply/txOperation/txOperationTransferFeeMissingCreatorEntryScenario.js +0 -18
  342. package/tests/unit/state/apply/txOperation/txOperationTransferFeeSubtractFailureScenario.js +0 -25
  343. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateCreatorBalanceFailureScenario.js +0 -26
  344. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateFailureScenario.js +0 -25
  345. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBalanceFailureScenario.js +0 -26
  346. package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBonusFailureScenario.js +0 -27
  347. package/tests/unit/state/apply/txOperation/txOperationValidatorCreatorHappyPathScenario.js +0 -21
  348. package/tests/unit/state/stateModule.test.js +0 -23
  349. package/tests/unit/state/stateTestUtils.js +0 -32
  350. package/tests/unit/state/utils/address.test.js +0 -63
  351. package/tests/unit/state/utils/adminEntry.test.js +0 -52
  352. package/tests/unit/state/utils/balance.test.js +0 -320
  353. package/tests/unit/state/utils/lengthEntry.test.js +0 -54
  354. package/tests/unit/state/utils/nodeEntry.test.js +0 -298
  355. package/tests/unit/state/utils/roles.test.js +0 -44
  356. package/tests/unit/unit.test.js +0 -14
  357. package/tests/unit/utils/amountSerialization/amountSerialization.test.js +0 -237
  358. package/tests/unit/utils/buffer/buffer.test.js +0 -251
  359. package/tests/unit/utils/check/adminControlOperation.test.js +0 -156
  360. package/tests/unit/utils/check/balanceInitializationOperation.test.js +0 -54
  361. package/tests/unit/utils/check/bootstrapDeploymentOperation.test.js +0 -104
  362. package/tests/unit/utils/check/check.test.js +0 -17
  363. package/tests/unit/utils/check/common.test.js +0 -419
  364. package/tests/unit/utils/check/coreAdminOperation.test.js +0 -95
  365. package/tests/unit/utils/check/roleAccessOperation.test.js +0 -255
  366. package/tests/unit/utils/check/transactionOperation.test.js +0 -107
  367. package/tests/unit/utils/check/transferOperation.test.js +0 -102
  368. package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +0 -102
  369. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +0 -95
  370. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +0 -149
  371. package/tests/unit/utils/helpers/helpers.test.js +0 -11
  372. package/tests/unit/utils/helpers/isHexString.test.js +0 -12
  373. package/tests/unit/utils/helpers/normalizeHex.test.js +0 -82
  374. package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +0 -100
  375. package/tests/unit/utils/normalizers/normalizers.test.js +0 -469
  376. package/tests/unit/utils/protobuf/applyOperations.test.js +0 -91
  377. package/tests/unit/utils/protobuf/operationHelpers.test.js +0 -303
  378. package/tests/unit/utils/protobuf/protobuf.test.js +0 -12
  379. package/tests/unit/utils/type/type.test.js +0 -25
  380. package/tests/unit/utils/utils.test.js +0 -21
  381. package/whitelist/.gitkeep +0 -0
@@ -1,178 +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
- import { Logger } from '../../../utils/logger.js';
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";
8
7
 
9
- const DELAY_INTERVAL = 50
10
- const VALIDATOR_CANDIDATES_PER_CYCLE = 10
11
- 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
8
+ // Internal constants
9
+ const VALIDATOR_CANDIDATES_PER_CYCLE = 10;
10
+ const MAX_KEY_DECODE_CACHE_SIZE = 10_000;
12
11
 
13
12
  class ValidatorObserverService {
14
- #config;
15
- #state;
16
13
  #network;
17
- #scheduler;
14
+ #state;
18
15
  #address;
19
- #isInterrupted
16
+ #config;
17
+ #scheduler;
20
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;
21
25
 
22
26
  /**
23
- * @param {Network} network
24
- * @param {State} state
25
- * @param {string} address
26
- * @param {Config} config
27
- **/
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
+ */
28
32
  constructor(network, state, address, config) {
29
- this.#config = config
30
33
  this.#network = network;
31
34
  this.#state = state;
32
35
  this.#address = address;
33
- this.#isInterrupted = false;
36
+ this.#config = config;
34
37
  this.#logger = new Logger(config);
35
- this.initTimestamp = Date.now();
36
- this.reachedMax = false;
37
- this.end = 0;
38
- this.begin = 0;
38
+ this.#hasBootstrapped = false;
39
+ this.#bootstrapStartedAt = 0;
40
+ this.#adminCache = { entry: null, lastUpdated: 0 };
41
+ this.#writersCache = { list: [], lastUpdated: 0 };
42
+ this.#isInterrupted = false;
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,
117
+ }
39
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");
40
140
 
41
- get state() {
42
- return this.#state;
141
+ const isAdmin = adminEntry && this.#address === adminEntry.address;
142
+ if (isAdmin) return;
143
+
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
+ }
43
149
  }
44
150
 
45
- // Original comment for this:
46
- // TODO: AFTER WHILE LOOP SIGNAL TO THE PROCESS THAT VALIDATOR OBSERVER STOPPED OPERATING.
47
- // 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
+ */
48
211
  async start() {
49
212
  if (!this.#shouldRun()) {
50
213
  this.#logger.info('ValidatorObserverService can not start. Disabled by configuration.');
51
214
  return;
52
215
  }
216
+
53
217
  if (this.#scheduler && this.#scheduler.isRunning) {
54
218
  this.#logger.info('ValidatorObserverService is already started');
55
219
  return;
56
220
  }
57
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;
58
226
  this.#isInterrupted = false;
59
- this.#scheduler = new Scheduler(next => this.#worker(next), POLL_INTERVAL);
60
227
  this.#scheduler.start();
228
+ this.#logger.debug("ValidatorObserverService started");
61
229
  }
62
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
+ */
63
236
  async stopValidatorObserver(waitForCurrent = true) {
64
237
  if (!this.#scheduler) return;
238
+
65
239
  this.#isInterrupted = true;
240
+
66
241
  await this.#scheduler.stop(waitForCurrent);
67
242
  this.#scheduler = null;
68
- this.#logger.info('ValidatorObserverService: closing gracefully...');
243
+ this.#logger.debug("ValidatorObserverService stopped");
69
244
  }
70
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
+ */
71
257
  async #worker(next) {
72
- if (!this.#network.validatorConnectionManager.maxConnectionsReached()) {
73
- this.begin = Date.now();
74
- const length = await this.#lengthEntry()
75
-
76
- const promises = [];
77
- for (let i = 0; i < VALIDATOR_CANDIDATES_PER_CYCLE; i++) {
78
- promises.push(this.#findValidator(this.#address, length + 1));
79
- await sleep(DELAY_INTERVAL); // Low key dangerous as the network progresses
80
- }
81
- await Promise.all(promises);
258
+ const interval = this.#getPollInterval();
259
+ if (!this.#shouldRun()) return next(interval);
82
260
 
83
- this.end = Date.now();
84
- this.#logger.debug(`Worker cycle completed in (ms): ${this.end - this.begin} | Validator Connections: ${this.#network.validatorConnectionManager.connectionCount()} | Pending: ${this.#network.pendingConnectionsCount()}`);
85
- }
86
- else {
87
- if (!this.reachedMax) {
88
- this.reachedMax = true;
89
- this.#logger.debug('Max validator connections reached. Skipping this cycle.');
90
- const now = Date.now();
91
- const elapsed = now - this.initTimestamp;
92
- this.#logger.debug(`>>> 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);
93
276
  }
277
+
278
+ } catch (err) {
279
+ this.#logger.error(`ValidatorObserver worker error: ${err.message}`);
94
280
  }
95
- next(POLL_INTERVAL);
281
+
282
+ next(interval);
96
283
  }
97
284
 
98
- async #findValidator(address, validatorListLength) {
99
- if (!this.#shouldRun()) return;
100
- const maxAttempts = 50; // TODO: make configurable
101
- let attempts = 0;
102
- let isValidatorValid = false;
103
- 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;
104
293
 
105
- while (attempts < maxAttempts && !isValidatorValid) {
106
- const rndIndex = Math.floor(Math.random() * validatorListLength);
107
- validatorAddressBuffer = await this.state.getWriterIndex(rndIndex);
108
- isValidatorValid = await this.#isValidatorValid(address, validatorAddressBuffer, validatorListLength);
109
- attempts++;
110
- }
294
+ const entry = await this.#state.getAdminEntry();
295
+ this.#adminCache = { entry, lastUpdated: now };
296
+ return entry;
297
+ }
111
298
 
112
- if (attempts >= maxAttempts) {
113
- this.#logger.debug('Max attempts reached without finding a valid validator.');
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;
315
+
316
+ if (!this.#hasBootstrapped && manager.maxConnectionsReached()) {
317
+ this.#hasBootstrapped = true;
318
+ this.#logger.debug("Bootstrap complete (pool full)");
114
319
  }
115
- else {
116
- this.#logger.debug(`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)");
117
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 };
118
334
 
119
- if (!isValidatorValid) return;
335
+ return list;
336
+ }
120
337
 
121
- const validatorAddress = bufferToAddress(validatorAddressBuffer, this.#config.addressPrefix);
122
- const validatorPubKeyBuffer = PeerWallet.decodeBech32m(validatorAddress);
123
- const validatorPubKeyHex = validatorPubKeyBuffer.toString('hex');
124
- 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;
125
346
 
126
- if (validatorAddress !== adminEntry?.address || validatorListLength < this.#config.maxWritersForAdminIndexerConnection) {
127
- this.#network.tryConnect(validatorPubKeyHex, 'validator');
128
- }
129
- };
347
+ let attempts = 0;
130
348
 
131
- async #isValidatorValid(forbiddenAddress, validatorAddressBuffer, validatorListLength) {
132
- 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;
133
352
 
134
- const validatorAddress = bufferToAddress(validatorAddressBuffer, this.#config.addressPrefix);
135
- if (validatorAddress === forbiddenAddress) return false;
353
+ const isConnected = this.#network.validatorConnectionManager.connected(candidate.publicKey);
354
+ const isPending = this.#network.isConnectionPending(candidate.publicKeyHex);
136
355
 
137
- const validatorPubKeyBuffer = PeerWallet.decodeBech32m(validatorAddress);
138
- const validatorEntry = await this.state.getNodeEntry(validatorAddress);
139
- const adminEntry = await this.state.getAdminEntry();
356
+ if (isConnected || isPending) continue;
140
357
 
141
- if (this.#network.isConnectionPending(validatorPubKeyBuffer.toString('hex'))) {
142
- return false;
143
- }
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}`);
144
361
 
145
- if (validatorAddress === adminEntry?.address && validatorListLength >= this.#config.maxWritersForAdminIndexerConnection) {
146
- if (this.#network.validatorConnectionManager.exists(validatorPubKeyBuffer)) {
147
- this.#network.validatorConnectionManager.remove(validatorPubKeyBuffer)
362
+ attempts++;
363
+
364
+ if (attempts < slots) {
365
+ await new Promise((resolve) => setTimeout(resolve, delay));
148
366
  }
149
367
  }
368
+ }
150
369
 
151
- // Connection validation rules:
152
- // - Cannot connect if already connected to a validator
153
- // - Validator must exist and be a writer
154
- // - Cannot connect to indexers, except for admin-indexer
155
- // - Admin-indexer connection is allowed only when writers length is below maxWritersForAdminIndexerConnection
156
- if (this.#network.validatorConnectionManager.connected(validatorPubKeyBuffer) ||
157
- this.#network.validatorConnectionManager.maxConnectionsReached() ||
158
- validatorEntry === null ||
159
- !validatorEntry.isWriter ||
160
- (validatorEntry.isIndexer && (validatorAddress !== adminEntry?.address || validatorListLength >= this.#config.maxWritersForAdminIndexerConnection))
161
- ) {
162
- 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();
163
386
  }
164
387
 
165
- return true;
166
- }
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();
167
391
 
168
- #shouldRun() {
169
- 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());
170
444
  }
171
445
 
172
- async #lengthEntry() {
173
- const lengthEntry = await this.state.getWriterLength();
174
- 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
+ }
175
462
  }
176
463
  }
177
464
 
178
- export default ValidatorObserverService;
465
+ export default ValidatorObserverService;