trac-msb 0.2.13 → 0.2.15

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 (121) hide show
  1. package/.github/workflows/acceptance-tests.yml +38 -0
  2. package/.github/workflows/lint-pr-title.yml +26 -0
  3. package/.github/workflows/publish.yml +33 -0
  4. package/.github/workflows/unit-tests.yml +34 -0
  5. package/package.json +7 -12
  6. package/proto/network.proto +74 -0
  7. package/rpc/rpc_services.js +4 -22
  8. package/scripts/generate-protobufs.js +12 -37
  9. package/src/config/config.js +9 -26
  10. package/src/config/env.js +17 -27
  11. package/src/core/network/Network.js +36 -73
  12. package/src/core/network/protocols/LegacyProtocol.js +11 -21
  13. package/src/core/network/protocols/NetworkMessages.js +17 -38
  14. package/src/core/network/protocols/ProtocolInterface.js +2 -14
  15. package/src/core/network/protocols/ProtocolSession.js +17 -144
  16. package/src/core/network/protocols/V1Protocol.js +18 -37
  17. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +19 -25
  18. package/src/core/network/protocols/legacy/handlers/{LegacyGetRequestHandler.js → GetRequestHandler.js} +6 -6
  19. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
  20. package/src/core/network/protocols/{legacy/handlers/LegacyRoleOperationHandler.js → shared/handlers/RoleOperationHandler.js} +11 -18
  21. package/src/core/network/protocols/{legacy/handlers/LegacySubnetworkOperationHandler.js → shared/handlers/SubnetworkOperationHandler.js} +17 -28
  22. package/src/core/network/protocols/{legacy/handlers/LegacyTransferOperationHandler.js → shared/handlers/TransferOperationHandler.js} +11 -17
  23. package/src/core/network/protocols/{legacy/handlers/BaseStateOperationHandler.js → shared/handlers/base/BaseOperationHandler.js} +12 -23
  24. package/src/core/network/protocols/shared/validators/{PartialBootstrapDeploymentValidator.js → PartialBootstrapDeployment.js} +4 -9
  25. package/src/core/network/protocols/shared/validators/{PartialRoleAccessValidator.js → PartialRoleAccess.js} +17 -51
  26. package/src/core/network/protocols/shared/validators/{PartialTransactionValidator.js → PartialTransaction.js} +7 -21
  27. package/src/core/network/protocols/shared/validators/{PartialTransferValidator.js → PartialTransfer.js} +9 -26
  28. package/src/core/network/protocols/shared/validators/{PartialOperationValidator.js → base/PartialOperation.js} +25 -47
  29. package/src/core/network/protocols/v1/NetworkMessageRouter.js +7 -91
  30. package/src/core/network/services/ConnectionManager.js +94 -146
  31. package/src/core/network/services/MessageOrchestrator.js +27 -151
  32. package/src/core/network/services/TransactionPoolService.js +18 -129
  33. package/src/core/network/services/TransactionRateLimiterService.js +34 -52
  34. package/src/core/network/services/ValidatorObserverService.js +26 -18
  35. package/src/core/state/State.js +19 -70
  36. package/src/index.js +8 -6
  37. package/src/messages/network/v1/NetworkMessageBuilder.js +79 -59
  38. package/src/messages/network/v1/NetworkMessageDirector.js +50 -16
  39. package/src/utils/Scheduler.js +8 -0
  40. package/src/utils/constants.js +5 -71
  41. package/src/utils/helpers.js +1 -10
  42. package/src/utils/normalizers.js +0 -38
  43. package/src/utils/protobuf/network.cjs +840 -0
  44. package/src/utils/protobuf/operationHelpers.js +3 -24
  45. package/tests/acceptance/v1/account/account.test.mjs +2 -8
  46. package/tests/acceptance/v1/tx/tx.test.mjs +1 -23
  47. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +6 -34
  48. package/tests/fixtures/assembleMessage.fixtures.js +8 -7
  49. package/tests/fixtures/networkV1.fixtures.js +28 -2
  50. package/tests/helpers/autobaseTestHelpers.js +5 -2
  51. package/tests/helpers/createTestSignature.js +3 -2
  52. package/tests/helpers/transactionPayloads.mjs +2 -2
  53. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +79 -239
  54. package/tests/unit/messages/network/NetworkMessageDirector.test.js +77 -223
  55. package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +5 -1
  56. package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +5 -1
  57. package/tests/unit/network/ConnectionManager.test.js +191 -0
  58. package/tests/unit/network/networkModule.test.js +1 -4
  59. package/tests/unit/unit.test.js +2 -2
  60. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +2 -2
  61. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +2 -2
  62. package/tests/unit/utils/protobuf/operationHelpers.test.js +4 -2
  63. package/tests/unit/utils/utils.test.js +0 -1
  64. package/proto/network/v1/enums/message_type.proto +0 -16
  65. package/proto/network/v1/enums/result_code.proto +0 -84
  66. package/proto/network/v1/messages/broadcast_transaction_request.proto +0 -9
  67. package/proto/network/v1/messages/broadcast_transaction_response.proto +0 -13
  68. package/proto/network/v1/messages/liveness_request.proto +0 -8
  69. package/proto/network/v1/messages/liveness_response.proto +0 -11
  70. package/proto/network/v1/network_message.proto +0 -22
  71. package/src/core/network/protocols/connectionPolicies.js +0 -88
  72. package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +0 -23
  73. package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +0 -27
  74. package/src/core/network/protocols/v1/V1ProtocolError.js +0 -91
  75. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +0 -65
  76. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +0 -389
  77. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +0 -87
  78. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +0 -211
  79. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +0 -26
  80. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +0 -276
  81. package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +0 -15
  82. package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +0 -17
  83. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +0 -210
  84. package/src/core/network/services/PendingRequestService.js +0 -172
  85. package/src/core/network/services/TransactionCommitService.js +0 -149
  86. package/src/core/network/services/ValidatorHealthCheckService.js +0 -127
  87. package/src/utils/deepEqualApplyPayload.js +0 -40
  88. package/src/utils/logger.js +0 -25
  89. package/src/utils/protobuf/networkV1.generated.cjs +0 -2460
  90. package/tests/unit/network/LegacyNetworkMessageRouter.test.js +0 -54
  91. package/tests/unit/network/ProtocolSession.test.js +0 -127
  92. package/tests/unit/network/services/ConnectionManager.test.js +0 -450
  93. package/tests/unit/network/services/MessageOrchestrator.test.js +0 -445
  94. package/tests/unit/network/services/PendingRequestService.test.js +0 -431
  95. package/tests/unit/network/services/TransactionCommitService.test.js +0 -246
  96. package/tests/unit/network/services/TransactionPoolService.test.js +0 -489
  97. package/tests/unit/network/services/TransactionRateLimiterService.test.js +0 -139
  98. package/tests/unit/network/services/ValidatorHealthCheckService.test.js +0 -115
  99. package/tests/unit/network/services/services.test.js +0 -17
  100. package/tests/unit/network/utils/v1TestUtils.js +0 -153
  101. package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +0 -151
  102. package/tests/unit/network/v1/V1BaseOperation.test.js +0 -356
  103. package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +0 -129
  104. package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +0 -53
  105. package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +0 -512
  106. package/tests/unit/network/v1/V1LivenessRequest.test.js +0 -32
  107. package/tests/unit/network/v1/V1LivenessResponse.test.js +0 -45
  108. package/tests/unit/network/v1/V1ResultCode.test.js +0 -84
  109. package/tests/unit/network/v1/V1ValidationSchema.test.js +0 -13
  110. package/tests/unit/network/v1/connectionPolicies.test.js +0 -49
  111. package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +0 -284
  112. package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +0 -794
  113. package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +0 -193
  114. package/tests/unit/network/v1/v1.handlers.test.js +0 -15
  115. package/tests/unit/network/v1/v1.test.js +0 -19
  116. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +0 -119
  117. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +0 -136
  118. package/tests/unit/network/v1/v1ValidationSchema/common.test.js +0 -308
  119. package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +0 -90
  120. package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +0 -133
  121. package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +0 -102
@@ -1,40 +1,37 @@
1
1
  // PoolService.js
2
2
  import { BATCH_SIZE } from '../../../utils/constants.js';
3
3
  import Scheduler from '../../../utils/Scheduler.js';
4
- import Denque from "denque";
5
- import b4a from "b4a";
6
4
 
7
5
  class TransactionPoolService {
8
6
  #state;
9
7
  #address;
10
8
  #config;
11
- #txPool = new Denque();
12
- #transactionCommitService
9
+ #tx_pool = [];
13
10
  #scheduler = null;
14
- #queuedTxHashes
15
11
 
16
12
  /**
17
13
  * @param {State} state
18
14
  * @param {string} address
19
- * @param {TransactionCommitService} transactionCommitService
20
15
  * @param {Config} config
21
16
  **/
22
- constructor(state, address, transactionCommitService, config) {
17
+ constructor(state, address, config) {
23
18
  this.#state = state;
24
19
  this.#address = address;
25
- this.#transactionCommitService = transactionCommitService;
26
- this.#queuedTxHashes = new Set(); // to improve lookup performance when checking for duplicate transactions
27
20
  this.#config = config;
28
21
  }
29
22
 
30
- get txPool() {
31
- return this.#txPool;
23
+ get tx_pool() {
24
+ return this.#tx_pool;
32
25
  }
33
26
 
34
27
  get state() {
35
28
  return this.#state;
36
29
  }
37
30
 
31
+ get address() {
32
+ return this.#address;
33
+ }
34
+
38
35
  async start() {
39
36
  if (!this.#config.enableWallet) {
40
37
  console.info('TransactionPoolService can not start. Wallet is not enabled');
@@ -52,7 +49,7 @@ class TransactionPoolService {
52
49
  async #worker(next) {
53
50
  try {
54
51
  await this.#processTransactions();
55
- if (this.#txPool.size() > 0) {
52
+ if (this.#tx_pool.length > 0) {
56
53
  next(0);
57
54
  } else {
58
55
  next(this.#config.processIntervalMs);
@@ -68,143 +65,35 @@ class TransactionPoolService {
68
65
 
69
66
  async #processTransactions() {
70
67
  const canValidate = await this.#checkValidationPermissions();
71
- if (!canValidate || this.#txPool.size() === 0) return;
72
-
73
- const batchItems = this.#prepareBatch();
74
- const encodedBatch = batchItems.map(item => item.encodedTx);
75
- const batchTxHashes = batchItems.map(item => item.txHash);
76
- try {
77
- const receipts = await this.#state.appendWithProofOfPublication(encodedBatch, batchTxHashes);
78
-
79
- const receiptsByHash = new Map();
80
- for (const receipt of receipts) {
81
- if (receipt.txHash) receiptsByHash.set(receipt.txHash, receipt);
82
- }
83
-
84
- for (const item of batchItems) {
85
- const receipt = receiptsByHash.get(item.txHash);
86
-
87
- if (!receipt) {
88
- this.#transactionCommitService.rejectPendingCommit(
89
- item.txHash,
90
- new TransactionPoolMissingCommitReceiptError(item.txHash)
91
- );
92
- continue;
93
- }
94
-
95
- if (!receipt.proof) {
96
- this.#transactionCommitService.rejectPendingCommit(
97
- item.txHash,
98
- new TransactionPoolProofUnavailableError(
99
- item.txHash,
100
- receipt.blockNumber,
101
- receipt.proofError,
102
- receipt.timestamp
103
- )
104
- );
105
- continue;
106
- }
107
-
108
- this.#transactionCommitService.resolvePendingCommit(item.txHash, receipt);
109
- }
110
- } catch (error) {
111
- for (const item of batchItems) {
112
- this.#transactionCommitService.rejectPendingCommit(item.txHash, error);
113
- }
114
- console.error(
115
- `TransactionPoolService: failed to process batch (size=${batchItems.length}): ${error?.message ?? 'unknown error'}`
116
- );
68
+ if (canValidate && this.#tx_pool.length > 0) {
69
+ const batch = this.#prepareBatch();
70
+ await this.#state.append(batch);
117
71
  }
118
72
  }
119
73
 
120
74
  async #checkValidationPermissions() {
121
75
  const isAdminAllowedToValidate = await this.state.isAdminAllowedToValidate();
122
- const isNodeAllowedToValidate = await this.state.allowedToValidate(this.#address);
76
+ const isNodeAllowedToValidate = await this.state.allowedToValidate(this.address);
123
77
  return isNodeAllowedToValidate || isAdminAllowedToValidate;
124
78
  }
125
79
 
126
80
  #prepareBatch() {
127
- const batch = [];
128
- const batchSize = Math.min(this.#txPool.size(), BATCH_SIZE);
129
-
130
- for (let i = 0; i < batchSize; i++) {
131
- const tx = this.#txPool.shift();
132
- this.#queuedTxHashes.delete(tx.txHash);
133
- batch.push(tx);
134
- }
81
+ const length = Math.min(this.tx_pool.length, BATCH_SIZE);
82
+ const batch = this.tx_pool.slice(0, length);
83
+ this.tx_pool.splice(0, length);
135
84
  return batch;
136
85
  }
137
86
 
138
- addTransaction(txHash, encodedTx) {
139
- this.validateEnqueue();
140
- if (!txHash || !encodedTx || typeof txHash !== 'string' || !b4a.isBuffer(encodedTx)) {
141
- throw new TransactionPoolInvalidIncomingDataError()
142
- }
143
- if (this.hasTransaction(txHash)) {
144
- throw new TransactionPoolAlreadyQueuedError(txHash);
145
- }
146
- this.#queuedTxHashes.add(txHash);
147
- const txData = { txHash, encodedTx };
148
- this.txPool.push(txData);
87
+ addTransaction(tx) {
88
+ this.tx_pool.push(tx);
149
89
  }
150
90
 
151
91
  async stopPool(waitForCurrent = true) {
152
92
  if (!this.#scheduler) return;
153
93
  await this.#scheduler.stop(waitForCurrent);
154
94
  this.#scheduler = null;
155
- this.#queuedTxHashes.clear();
156
- this.#txPool.clear();
157
95
  console.info('TransactionPoolService: closing gracefully...');
158
96
  }
159
-
160
- validateEnqueue() {
161
- if (this.#txPool.size() >= this.#config.txPoolSize) {
162
- throw new TransactionPoolFullError(this.#config.txPoolSize);
163
- }
164
- }
165
-
166
- hasTransaction(txHash) {
167
- return this.#queuedTxHashes.has(txHash);
168
- }
169
- }
170
-
171
- export class TransactionPoolProofUnavailableError extends Error {
172
- constructor(txHash, blockNumber, reason = 'unknown', timestamp = 0) {
173
- const timestampValue = timestamp instanceof Date ? timestamp.getTime() : timestamp;
174
- const safeTimestamp = Number.isSafeInteger(timestampValue) ? timestampValue : 0;
175
- super(`Proof unavailable for txHash ${txHash} at block ${blockNumber} at ${safeTimestamp}. Reason: ${reason}`);
176
- this.txHash = txHash;
177
- this.blockNumber = blockNumber;
178
- this.timestamp = safeTimestamp;
179
- this.reason = reason;
180
- }
181
- }
182
-
183
- export class TransactionPoolMissingCommitReceiptError extends Error {
184
- constructor(txHash) {
185
- super(`Missing commit receipt for txHash ${txHash}`);
186
- this.txHash = txHash;
187
- }
188
- }
189
-
190
- export class TransactionPoolInvalidIncomingDataError extends Error {
191
- constructor(message = 'Invalid transaction pool incoming data') {
192
- super(message);
193
- }
194
- }
195
-
196
- export class TransactionPoolFullError extends Error {
197
- constructor(maxSize) {
198
- super(`Transaction pool is full. Maximum size of ${maxSize} reached.`);
199
- this.maxSize = maxSize
200
- }
201
-
202
- }
203
-
204
- export class TransactionPoolAlreadyQueuedError extends Error {
205
- constructor(txHash) {
206
- super(`Transaction with hash ${txHash} is already queued in the transaction pool.`);
207
- }
208
97
  }
209
98
 
210
99
  export default TransactionPoolService;
@@ -1,6 +1,4 @@
1
1
  import b4a from 'b4a';
2
- import {V1RateLimitedError} from "../protocols/v1/V1ProtocolError.js";
3
- import {publicKeyToAddress} from "../../../utils/helpers.js";
4
2
 
5
3
  class TransactionRateLimiterService {
6
4
  #lastCleanup;
@@ -16,23 +14,19 @@ class TransactionRateLimiterService {
16
14
  }
17
15
 
18
16
  /*
19
- Checks if the peer has exceeded the rate limit for the current 1-second window.
17
+ Checks if the peer has exceeded the rate limit.
20
18
  A peer is considered to have exceeded the rate limit if:
21
- - The request belongs to the same 1-second window as previous requests (tracked per peer)
22
- - The number of transactions already seen in this window is >= rateLimitMaxTransactionsPerSecond
23
-
24
- Important:
25
- - This method assumes the caller increments transactionCount AFTER calling this method.
26
- (So exactly rateLimitMaxTransactionsPerSecond are allowed; the next one is blocked.)
19
+ - The time since the last activity is greater than or equal to 1000 ms (1 second)
20
+ - The number of transactions in the current session is greater than or equal to rateLimitMaxTransactionsPerSecond
21
+ If the rate limit is exceeded, the peer is disconnected.
27
22
  */
28
- #hasExceededRateLimit(peer, currentTime) {
23
+ #hasExceededRateLimit(peer) {
29
24
  const peerData = this.#connectionsStatistics.get(peer);
30
- const currentSecond = Math.floor((currentTime - peerData.sessionStartTime) / 1000);
31
- const lastResetSecond = Math.floor((peerData.lastCounterReset - peerData.sessionStartTime) / 1000);
32
-
33
- if (currentSecond > lastResetSecond) {
25
+ const currentSecond = Math.floor((peerData.lastActivityTime - peerData.sessionStartTime) / 1000);
26
+
27
+ if (currentSecond > Math.floor((peerData.lastCounterReset - peerData.sessionStartTime) / 1000)) {
34
28
  peerData.transactionCount = 0;
35
- peerData.lastCounterReset = currentTime;
29
+ peerData.lastCounterReset = peerData.lastActivityTime;
36
30
  this.#connectionsStatistics.set(peer, peerData);
37
31
  }
38
32
 
@@ -40,18 +34,23 @@ class TransactionRateLimiterService {
40
34
  }
41
35
 
42
36
  /*
43
- Handles rate limiting for a peer connection (legacy protocol).
44
- If the peer has exceeded the rate limit, it disconnects the peer and returns true.
45
- Otherwise, it updates the connection info with the current timestamp and returns false.
37
+ Handles the rate limiting for a peer connection.
38
+ If the peer has exceeded the rate limit, it disconnects the peer.
39
+ Otherwise, it updates the connection info with the current timestamp.
46
40
  */
47
- legacyHandleRateLimit(connection) {
41
+ handleRateLimit(connection) {
48
42
  const peer = b4a.toString(connection.remotePublicKey, 'hex');
49
43
  const currentTime = Date.now();
50
44
 
51
45
  this.#cleanUpOldConnections(currentTime);
52
46
  this.#initializePeerConnectionInfoEntry(peer, currentTime);
53
47
 
54
- if (this.#hasExceededRateLimit(peer, currentTime)) {
48
+ if (this.#isConnectionExpired(peer)) {
49
+ this.#connectionsStatistics.delete(peer);
50
+ return false;
51
+ }
52
+
53
+ if (this.#hasExceededRateLimit(peer)) {
55
54
  console.warn(`Rate limit exceeded for peer ${peer}. Disconnecting...`);
56
55
  this.#swarm.leavePeer(connection.remotePublicKey);
57
56
  connection.end();
@@ -62,31 +61,14 @@ class TransactionRateLimiterService {
62
61
  return false;
63
62
  }
64
63
 
65
- /*
66
- Handles rate limiting for a peer connection (v1 protocol).
67
- If the peer has exceeded the rate limit, it throws RateLimitedError.
68
- Otherwise, it updates the connection info with the current timestamp.
69
- */
70
- v1HandleRateLimit(connection) {
71
- const peer = b4a.toString(connection.remotePublicKey, 'hex');
72
- const currentTime = Date.now();
73
-
74
- this.#cleanUpOldConnections(currentTime);
75
- this.#initializePeerConnectionInfoEntry(peer, currentTime);
76
-
77
- if (this.#hasExceededRateLimit(peer, currentTime)) {
78
- throw new V1RateLimitedError(`Rate limit exceeded for peer ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`);
79
- }
80
- this.#updatePeerConnectionInfo(peer, currentTime);
81
- }
82
-
83
64
  #shouldCleanupConnections(currentTime) {
84
65
  return currentTime - this.#lastCleanup >= this.#config.rateLimitCleanupIntervalMs;
85
66
  }
86
67
 
87
68
  /**
88
- Cleans up per-peer statistics that have been inactive for more than rateLimitCleanupIntervalMs.
89
- Runs at most once every rateLimitCleanupIntervalMs.
69
+ Cleans up old connections that have timed out.
70
+ Condition for cleanup based on #shouldCleanupConnections:
71
+ - If the last cleanup was more than rateLimitCleanupIntervalMs ago
90
72
  */
91
73
  #cleanUpOldConnections(currentTime) {
92
74
  if (!this.#shouldCleanupConnections(currentTime)) {
@@ -94,7 +76,8 @@ class TransactionRateLimiterService {
94
76
  }
95
77
 
96
78
  for (const [peer, _] of this.#connectionsStatistics.entries()) {
97
- if (this.#isConnectionExpired(peer, currentTime)) {
79
+ if (this.#isConnectionExpired(peer)) {
80
+ //console.log(`Connection for peer ${peer} has expired. Removing...`);
98
81
  this.#connectionsStatistics.delete(peer);
99
82
  }
100
83
  }
@@ -104,19 +87,19 @@ class TransactionRateLimiterService {
104
87
 
105
88
  /*
106
89
  Initializes the connection statistics for a peer.
107
- Stored as a HashMap with the following structure:
108
- peerPublicKeyHex: {
109
- sessionStartTime: timestamp, // When we first saw this peer (start of local tracking session)
110
- lastActivityTime: timestamp, // Timestamp of peer's most recent activity
111
- lastCounterReset: timestamp, // Timestamp when the per-second counter was last reset
112
- transactionCount: number // Transactions seen in the current 1-second window
90
+ Connection is a HashMap with the following structure:
91
+ peerPublicKey: {
92
+ sessionStartTime: timestamp, // When the external peer started their session
93
+ lastActivityTime: timestamp, // Timestamp of peer's most recent activity (default: 0)
94
+ transactionCount: number // Number of transactions in the current session (default: 0)
113
95
  }
96
+
114
97
  */
115
98
  #initializePeerConnectionInfoEntry(peer, timestamp) {
116
99
  if (!this.#connectionsStatistics.has(peer)) {
117
100
  this.#connectionsStatistics.set(peer, {
118
101
  sessionStartTime: timestamp,
119
- lastActivityTime: timestamp,
102
+ lastActivityTime: 0,
120
103
  lastCounterReset: timestamp,
121
104
  transactionCount: 0
122
105
  });
@@ -135,12 +118,11 @@ class TransactionRateLimiterService {
135
118
  }
136
119
 
137
120
  /*
138
- Checks if the stored statistics for a peer have expired due to inactivity.
139
- Note: this is NOT a network-level connection timeout; it's only used to evict old Map entries.
121
+ Checks if the connection for a peer has expired.
140
122
  */
141
- #isConnectionExpired(peer, currentTime) {
123
+ #isConnectionExpired(peer) {
142
124
  const peerData = this.#connectionsStatistics.get(peer);
143
- return currentTime - peerData.lastActivityTime >= this.#config.rateLimitConnectionTimeoutMs;
125
+ return peerData.lastActivityTime - peerData.sessionStartTime >= this.#config.rateLimitConnectionTimeoutMs;
144
126
  }
145
127
  }
146
128
 
@@ -4,12 +4,20 @@ import { bufferToAddress } from '../../state/utils/address.js';
4
4
  import { sleep } from '../../../utils/helpers.js';
5
5
  import Scheduler from "../../../utils/Scheduler.js";
6
6
  import Network from "../Network.js";
7
- import { Logger } from '../../../utils/logger.js';
8
7
 
9
8
  const DELAY_INTERVAL = 50
10
9
  const VALIDATOR_CANDIDATES_PER_CYCLE = 10
11
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
12
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
+ };
20
+
13
21
  class ValidatorObserverService {
14
22
  #config;
15
23
  #state;
@@ -17,7 +25,6 @@ class ValidatorObserverService {
17
25
  #scheduler;
18
26
  #address;
19
27
  #isInterrupted
20
- #logger;
21
28
 
22
29
  /**
23
30
  * @param {Network} network
@@ -31,11 +38,12 @@ class ValidatorObserverService {
31
38
  this.#state = state;
32
39
  this.#address = address;
33
40
  this.#isInterrupted = false;
34
- this.#logger = new Logger(config);
35
- this.initTimestamp = Date.now();
36
- this.reachedMax = false;
37
- this.end = 0;
38
- this.begin = 0;
41
+ if (DEBUG) {
42
+ this.initTimestamp = Date.now();
43
+ this.reachedMax = false;
44
+ this.end = 0;
45
+ this.begin = 0;
46
+ }
39
47
  }
40
48
 
41
49
  get state() {
@@ -47,11 +55,11 @@ class ValidatorObserverService {
47
55
  // OS CALLS, ACCUMULATORS, MAYBE THIS IS POSSIBLE TO CHECK I/O QUEUE IF IT COINTAIN IT. FOR NOW WE ARE USING SLEEP.
48
56
  async start() {
49
57
  if (!this.#shouldRun()) {
50
- this.#logger.info('ValidatorObserverService can not start. Disabled by configuration.');
58
+ console.info('ValidatorObserverService can not start. Disabled by configuration.');
51
59
  return;
52
60
  }
53
61
  if (this.#scheduler && this.#scheduler.isRunning) {
54
- this.#logger.info('ValidatorObserverService is already started');
62
+ console.info('ValidatorObserverService is already started');
55
63
  return;
56
64
  }
57
65
 
@@ -65,12 +73,12 @@ class ValidatorObserverService {
65
73
  this.#isInterrupted = true;
66
74
  await this.#scheduler.stop(waitForCurrent);
67
75
  this.#scheduler = null;
68
- this.#logger.info('ValidatorObserverService: closing gracefully...');
76
+ console.info('ValidatorObserverService: closing gracefully...');
69
77
  }
70
78
 
71
79
  async #worker(next) {
72
80
  if (!this.#network.validatorConnectionManager.maxConnectionsReached()) {
73
- this.begin = Date.now();
81
+ if (DEBUG) this.begin = Date.now();
74
82
  const length = await this.#lengthEntry()
75
83
 
76
84
  const promises = [];
@@ -80,16 +88,16 @@ class ValidatorObserverService {
80
88
  }
81
89
  await Promise.all(promises);
82
90
 
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()}`);
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());
85
93
  }
86
- else {
94
+ else if (DEBUG) {
87
95
  if (!this.reachedMax) {
88
96
  this.reachedMax = true;
89
- this.#logger.debug('Max validator connections reached. Skipping this cycle.');
97
+ debugLog('Max validator connections reached. Skipping this cycle.');
90
98
  const now = Date.now();
91
99
  const elapsed = now - this.initTimestamp;
92
- this.#logger.debug(`>>> Time elapsed since start (ms): ${elapsed}`);
100
+ debugLog('>>> Time elapsed since start (ms):', elapsed);
93
101
  }
94
102
  }
95
103
  next(POLL_INTERVAL);
@@ -110,10 +118,10 @@ class ValidatorObserverService {
110
118
  }
111
119
 
112
120
  if (attempts >= maxAttempts) {
113
- this.#logger.debug('Max attempts reached without finding a valid validator.');
121
+ debugLog('Max attempts reached without finding a valid validator.');
114
122
  }
115
123
  else {
116
- this.#logger.debug(`Found valid validator to connect after ${attempts} attempts.`);
124
+ debugLog(`Found valid validator to connect after ${attempts} attempts.`);
117
125
  }
118
126
 
119
127
  if (!isValidatorValid) return;
@@ -40,15 +40,13 @@ import { safeWriteUInt32BE } from '../../utils/buffer.js';
40
40
  import deploymentEntryUtils from './utils/deploymentEntry.js';
41
41
  import { deepCopyBuffer } from '../../utils/buffer.js';
42
42
  import { Status } from './utils/transaction.js';
43
- import remote from 'hypercore/lib/fully-remote-proof.js'
44
- import PQueue from 'p-queue';
43
+ import Corestore from 'corestore';
45
44
 
46
45
  const OVERSIZED_BATCH_PENALTY_MULTIPLIER = BATCH_SIZE;
47
46
 
48
47
  // TODO: #addWriter, #removeWriter, #transfer, #transferFeeTxOperation need to be refactored to get in arguments actor's nodeEntries in buffer format.
49
48
 
50
49
  class State extends ReadyResource {
51
- #writeQueue = new PQueue({ concurrency: 1 });
52
50
  #base;
53
51
  #bee;
54
52
  #store;
@@ -189,6 +187,18 @@ class State extends ReadyResource {
189
187
  return !!nodeEntry.isWhitelisted;
190
188
  }
191
189
 
190
+ async isAddressWriter(address) {
191
+ const nodeEntry = await this.getNodeEntry(address);
192
+ if (nodeEntry === null) return false;
193
+ return !!nodeEntry.isWriter;
194
+ }
195
+
196
+ async isAddressIndexer(address) {
197
+ const nodeEntry = await this.getNodeEntry(address);
198
+ if (nodeEntry === null) return false;
199
+ return !!nodeEntry.isIndexer;
200
+ }
201
+
192
202
  async getIndexersEntry() {
193
203
  return Object.values(this.#base.system.indexers);
194
204
  }
@@ -233,67 +243,7 @@ class State extends ReadyResource {
233
243
  }
234
244
 
235
245
  async append(payload) {
236
- return this.#writeQueue.add(() => this.#base.append(payload));
237
- }
238
-
239
- async appendWithProofOfPublication(batch, batchTxHashes) {
240
- return this.#writeQueue.add(async () => {
241
-
242
- const core = this.#base.local;
243
- const end = await this.#base.append(batch);
244
- const start = end - batch.length;
245
- const timestamp = new Date();
246
- const snapshot = core.snapshot(); // consistent view while generating proofs.
247
- await snapshot.ready();
248
- // TODO: check state if specific tx has been appened THEN generate a proof.
249
- try {
250
- const receipts = [];
251
- let failedProofs = 0;
252
- for (let i = 0; i < batch.length; i++) {
253
- const blockNumber = start + i;
254
- const completeTx = batch[i];
255
- const txHash = batchTxHashes[i];
256
-
257
- let proof = null;
258
- let proofError = null;
259
-
260
- // wait:false makes get fail fast (null) instead of waiting for missing data/replication.
261
- const rawBlock = await snapshot.get(blockNumber, { raw: true, wait: false });
262
- if (!rawBlock) {
263
- proofError = `Missing raw block after append (block=${blockNumber}, start=${start}, end=${end})`;
264
- failedProofs++;
265
- } else {
266
- try {
267
- proof = await remote.proof(snapshot, { index: blockNumber, block: rawBlock });
268
- } catch (error) {
269
- proofError = `Proof generation failed (block=${blockNumber}, start=${start}, end=${end}): ${error?.message ?? 'unknown error'}`;
270
- failedProofs++;
271
- }
272
- }
273
- receipts.push({
274
- txHash,
275
- completeTx,
276
- proof,
277
- proofError,
278
- timestamp,
279
- blockNumber
280
- });
281
- }
282
- if (failedProofs > 0) {
283
- console.error(`appendWithProof completed with ${failedProofs} proof failures (batch=${batch.length})`);
284
- }
285
- return receipts;
286
- } finally {
287
- await snapshot.close();
288
- }
289
- });
290
- }
291
-
292
- async verifyProofOfPublication(proof) {
293
- // Valid concern. We currently rely on Hypercore’s internal fully-remote-proof helper, which requires low-level storage access
294
- const out = await remote.verify(this.#store.storage, proof);
295
- if (!out) throw new Error('Proof of publication verification failed');
296
- return out;
246
+ await this.#base.append(payload);
297
247
  }
298
248
 
299
249
  async getIndexerSequenceState() {
@@ -314,7 +264,6 @@ class State extends ReadyResource {
314
264
  return b4a.equals(initialization, safeWriteUInt32BE(0, 0))
315
265
  }
316
266
  }
317
-
318
267
  async getTransactionConfirmedLength(hash) {
319
268
  if (!isHexString(hash) || hash.length !== 64) {
320
269
  throw new Error("Invalid hash format");
@@ -1844,9 +1793,9 @@ class State extends ReadyResource {
1844
1793
  };
1845
1794
 
1846
1795
  /**
1847
- * Ensure that:
1848
- * 1) writer key exists in registry (we can not unregister something that was not registered),
1849
- * 2) matches the one in node entry ,
1796
+ * Ensure that:
1797
+ * 1) writer key exists in registry (we can not unregister something that was not registered),
1798
+ * 2) matches the one in node entry ,
1850
1799
  * 3) belongs to the requester - this prevents unauthorized key removal
1851
1800
  */
1852
1801
  const writerKeyHasBeenRegistered = await this.#getRegisteredWriterKeyApply(batch, op.rao.iw.toString('hex'))
@@ -2981,7 +2930,7 @@ class State extends ReadyResource {
2981
2930
  batch,
2982
2931
  node
2983
2932
  );
2984
-
2933
+
2985
2934
  // TODO: cover next 4 guards below with tests
2986
2935
  if (transferFeeTxOperationResult === null) {
2987
2936
  this.#safeLogApply(OperationType.TX, "Fee transfer operation failed completely.", node.from.key);
@@ -3439,7 +3388,7 @@ class State extends ReadyResource {
3439
3388
 
3440
3389
  /**
3441
3390
  * Retrieves the address assigned to a given writing key from the registry.
3442
- *
3391
+ *
3443
3392
  * @param {Object} batch - The current Hyperbee batch instance used for reading state.
3444
3393
  * @param {string} writingKey - The writing key in hex string format.
3445
3394
  * @returns {Buffer|null} The address buffer assigned to the writing key, or null if not registered.
package/src/index.js CHANGED
@@ -44,7 +44,6 @@ import {
44
44
  getLicenseCountCommand
45
45
  } from "./utils/cliCommands.js";
46
46
  import {safeEncodeApplyOperation} from "./utils/protobuf/operationHelpers.js";
47
- import {Config} from "./config/config.js";
48
47
 
49
48
  export class MainSettlementBus extends ReadyResource {
50
49
  #store;
@@ -62,7 +61,10 @@ export class MainSettlementBus extends ReadyResource {
62
61
  super();
63
62
  this.#config = config
64
63
  this.#store = new Corestore(this.#config.storesFullPath);
65
- this.#wallet = new PeerWallet({ networkPrefix: this.#config.addressPrefix });
64
+ this.#wallet = new PeerWallet({
65
+ networkPrefix: this.#config.addressPrefix,
66
+ derivationPath: this.#config.derivationPath
67
+ });
66
68
  this.#readline_instance = null;
67
69
 
68
70
  if (this.#config.enableInteractiveMode) {
@@ -304,7 +306,7 @@ export class MainSettlementBus extends ReadyResource {
304
306
  const success = await this.broadcastPartialTransaction(adminRecoveryMessage);
305
307
 
306
308
  if (!success) {
307
- throw new Error("Failed to broadcast transaction. Try again later.");
309
+ throw new Error("Failed to broadcast transaction after multiple attempts.");
308
310
  }
309
311
 
310
312
  console.info(`Transaction hash: ${adminRecoveryMessage.rao.tx}`);
@@ -425,7 +427,7 @@ export class MainSettlementBus extends ReadyResource {
425
427
  const success = await this.broadcastPartialTransaction(assembledMessage);
426
428
 
427
429
  if (!success) {
428
- throw new Error("Failed to broadcast transaction. Try again later.");
430
+ throw new Error("Failed to broadcast transaction after multiple attempts.");
429
431
  }
430
432
 
431
433
  console.info(`Transaction hash: ${assembledMessage.rao.tx}`);
@@ -459,7 +461,7 @@ export class MainSettlementBus extends ReadyResource {
459
461
  const success = await this.broadcastPartialTransaction(assembledMessage);
460
462
 
461
463
  if (!success) {
462
- throw new Error("Failed to broadcast transaction. Try again later.");
464
+ throw new Error("Failed to broadcast transaction after multiple attempts.");
463
465
  }
464
466
 
465
467
  console.info(`Transaction hash: ${assembledMessage.rao.tx}`);
@@ -683,7 +685,7 @@ export class MainSettlementBus extends ReadyResource {
683
685
  const success = await this.broadcastPartialTransaction(payload);
684
686
 
685
687
  if (!success) {
686
- throw new Error("Failed to broadcast transaction. Try again later.");
688
+ throw new Error("Failed to broadcast transaction after multiple attempts.");
687
689
  }
688
690
 
689
691
  console.info(`Transaction hash: ${payload.bdo.tx}`);