trac-msb 0.2.5 → 0.2.7

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.
@@ -1,121 +1,313 @@
1
- import { MAX_VALIDATORS, MAX_REQUEST_COUNT } from "../../../utils/constants.js"
1
+ import { MAX_VALIDATORS_IN_CONNECTION_POOL } from "../../../utils/constants.js"
2
2
  import b4a from 'b4a'
3
3
  import PeerWallet from "trac-wallet"
4
4
  import { TRAC_NETWORK_MSB_MAINNET_PREFIX } from 'trac-wallet/constants.js';
5
5
 
6
+ // -- Debug Mode --
7
+ // TODO: Implement a better debug system in the future. This is just temporary.
8
+ const DEBUG = false;
9
+ const debugLog = (...args) => {
10
+ if (DEBUG) {
11
+ console.log('DEBUG [ConnectionManager] ==> ', ...args);
12
+ }
13
+ };
14
+
6
15
  class ConnectionManager {
7
16
  #validators
8
- #validatorsIndex
9
- #currentValidatorIndex
10
- #requestCount
11
17
  #maxValidators
12
18
 
13
- constructor({ maxValidators }) {
14
- this.#validators = {}
15
- this.#validatorsIndex = []
16
- this.#currentValidatorIndex = 0
17
- this.#requestCount = 0
18
- this.#maxValidators = maxValidators || MAX_VALIDATORS
19
+ // Note: #validators is using publicKey (Buffer) as key
20
+ // As Buffers are objects, we will rely on internal conversions done by JS to compare them.
21
+ // It would be better to handle these conversions manually by using hex strings as keys to avoid issues
22
+
23
+ constructor({ maxValidators }) {
24
+ this.#validators = new Map();
25
+ this.#maxValidators = maxValidators || MAX_VALIDATORS_IN_CONNECTION_POOL
19
26
  }
20
27
 
21
- send(message, retries = 3) {
22
- if (this.#requestCount >= MAX_REQUEST_COUNT) {
23
- this.#requestCount = 0
24
- this.#updateNext()
28
+ /**
29
+ * Sends a message to a single randomly selected connected validator.
30
+ * Returns the public key (buffer) of the validator used, or throws
31
+ * if the specified validator is unavailable.
32
+ * @param {Object} message - The message to send to the validator
33
+ * @returns {String} - The public key of the validator used
34
+ */
35
+ send(message) {
36
+ const connectedValidators = this.connectedValidators();
37
+
38
+ if (connectedValidators.length === 0) {
39
+ throw new Error('ConnectionManager: no connected validators available to send message');
25
40
  }
26
- this.#requestCount++
41
+
42
+ const target = this.pickRandomValidator(connectedValidators);
43
+ const entry = this.#validators.get(target);
44
+ if (!entry || !entry.connection || !entry.connection.messenger) return null;
27
45
 
28
46
  try {
29
- this.#getConnection().messenger.send(message)
30
- } catch (e) { // Some retrying mechanism before reacting to close
31
- if (retries > 0) {
32
- this.rotate()
33
- this.send(message, retries - 1)
34
- }
47
+ entry.connection.messenger.send(message);
48
+ entry.sent = (entry.sent || 0) + 1;
49
+ } catch (e) {
50
+ // Swallow individual send errors.
35
51
  }
52
+
53
+ return target;
36
54
  }
37
55
 
56
+ /**
57
+ * Sends a message through a specific validator without increasing sent messages count.
58
+ * @param {Object} message - The message to send to the validator
59
+ * @param {String | Buffer} publicKey - A validator public key hex string to be fetched from the pool.
60
+ * @returns {Boolean} True if the message was sent, false otherwise.
61
+ */
62
+ sendSingleMessage(message, publicKey) {
63
+ let publicKeyHex = this.#toHexString(publicKey);
64
+ if (!this.exists(publicKeyHex) || !this.connected(publicKeyHex)) return false; // Fail silently
65
+
66
+ const validator = this.#validators.get(publicKeyHex);
67
+ if (!validator || !validator.connection || !validator.connection.messenger) return false;
68
+ try {
69
+ validator.connection.messenger.send(message);
70
+ } catch (e) {
71
+ // Swallow individual send errors.
72
+ }
73
+ return true; // TODO: Implement better success/failure reporting
74
+ }
75
+
76
+ /**
77
+ * Creates a blank entry for a validator in the pool without a connection.
78
+ * @param {String | Buffer} publicKey - The public key hex string of the validator to whitelist
79
+ */
80
+ // TODO: Deprecated/Unused - remove if not needed
38
81
  whiteList(publicKey) {
39
- this.#validators[publicKey] = null
82
+ let publicKeyHex = this.#toHexString(publicKey);
83
+ this.#validators.set(publicKeyHex, { connection: null, sent: 0 });
40
84
  }
41
85
 
86
+ /**
87
+ * Adds a validator to the pool if not already present.
88
+ * @param {String | Buffer} publicKey - The public key hex string of the validator to add
89
+ * @param {Object} connection - The connection object associated with the validator
90
+ * @returns {Boolean} - Returns true if the validator was added or updated, false otherwise
91
+ */
42
92
  addValidator(publicKey, connection) {
43
- if (!this.exists(publicKey) && !this.maxConnections()) {
44
- return this.#append(publicKey, connection)
45
- } else if (!this.connected(publicKey)) {
46
- return this.#update(publicKey, connection)
93
+ let publicKeyHex = this.#toHexString(publicKey);
94
+ if (this.maxConnectionsReached()) {
95
+ debugLog(`addValidator: max connections reached.`);
96
+ return false;
47
97
  }
48
-
49
- return false
98
+ debugLog(`addValidator: adding validator ${this.#toAddress(publicKeyHex)}`);
99
+ if (!this.exists(publicKeyHex)) {
100
+ debugLog(`addValidator: appending validator ${this.#toAddress(publicKeyHex)}`);
101
+ this.#append(publicKeyHex, connection);
102
+ return true;
103
+ } else if (!this.connected(publicKeyHex)) {
104
+ debugLog(`addValidator: updating validator ${this.#toAddress(publicKeyHex)}`);
105
+ this.#update(publicKeyHex, connection);
106
+ return true;
107
+ }
108
+ debugLog(`addValidator: didn't add validator ${this.#toAddress(publicKeyHex)}`);
109
+ return false; // TODO: Implement better success/failure reporting
50
110
  }
51
111
 
112
+ /**
113
+ * Removes a validator from the pool.
114
+ * @param {String | Buffer} publicKey - The public key hex string of the validator to remove
115
+ */
52
116
  remove(publicKey) {
53
- const index = this.#validatorsIndex.findIndex(current => b4a.equals(publicKey, current));
54
- if (index !== -1) {
55
- this.#validatorsIndex.splice(index, 1);
56
- delete this.#validators[publicKey]
117
+ debugLog(`remove: removing validator ${this.#toAddress(publicKey)}`);
118
+ const publicKeyHex = this.#toHexString(publicKey);
119
+ if (this.exists(publicKeyHex)) {
120
+ // Close the connection socket
121
+ const entry = this.#validators.get(publicKeyHex);
122
+ if (entry && entry.connection && typeof entry.connection.end === 'function') {
123
+ try {
124
+ entry.connection.end();
125
+ } catch (e) {
126
+ // Ignore errors on connection end
127
+ // TODO: Consider logging these errors here in verbose mode
128
+ }
129
+ }
130
+ debugLog(`remove: removing validator from map: ${this.#toAddress(publicKeyHex)}. Map size before removal: ${this.#validators.size}.`);
131
+ this.#validators.delete(publicKeyHex);
132
+ debugLog(`remove: validator removed successfully. Map size is now ${this.#validators.size}.`);
57
133
  }
58
134
  }
59
135
 
60
- maxConnections() {
136
+ /**
137
+ * Checks if the maximum number of connections has been reached.
138
+ * @returns {Boolean} - Returns true if the maximum number of connections has been reached, false otherwise.
139
+ */
140
+ // Note: this function name is a bit misleading. It checks if we have reached max connections and returns boolean
141
+ // The name leads to think it returns the number of max connections
142
+ maxConnectionsReached() {
61
143
  return this.connectionCount() >= this.#maxValidators
62
144
  }
63
145
 
146
+ /**
147
+ * Gets a list of all currently connected validators' public keys.
148
+ * @returns {Array} - An array of public key hex strings of connected validators
149
+ */
150
+ connectedValidators() {
151
+ return Array.from(this.#validators.keys()).filter(pk => this.connected(pk));
152
+ }
153
+
154
+ /**
155
+ * Gets the current number of connected validators.
156
+ * @returns {Number} - The count of connected validators
157
+ */
64
158
  connectionCount() {
65
- return this.#validatorsIndex.filter(_ => this.connected(_)).length
159
+ return this.connectedValidators().length;
66
160
  }
67
161
 
162
+ /**
163
+ * Checks if a validator is currently connected.
164
+ * @param {String | Buffer} publicKey - The public key hex string of the validator to check
165
+ * @returns {Boolean} - Returns true if the validator is connected, false otherwise
166
+ */
68
167
  connected(publicKey) {
69
- return this.exists(publicKey) && this.#validators[publicKey]?.connected
168
+ const publicKeyHex = this.#toHexString(publicKey);
169
+ return this.exists(publicKeyHex) && this.#validators.get(publicKeyHex).connection !== null;
70
170
  }
71
171
 
72
- rotate() {
73
- this.#updateNext()
74
- this.#requestCount = 0
172
+ /**
173
+ * Checks if a validator exists in the pool.
174
+ * @param {String | Buffer} publicKey - The public key hex string of the validator to check
175
+ * @returns {Boolean} - Returns true if the validator exists, false otherwise
176
+ */
177
+ exists(publicKey) {
178
+ const publicKeyHex = this.#toHexString(publicKey);
179
+ return this.#validators.has(publicKeyHex);
75
180
  }
76
181
 
77
- exists(publicKey) {
78
- return !!this.#validators[publicKey]
182
+ /**
183
+ * Gets the number of messages sent through a validator.
184
+ * @param {String | Buffer} publicKey - The public key hex string of the validator
185
+ * @returns {Number} - The count of messages sent
186
+ */
187
+ getSentCount(publicKey) {
188
+ const publicKeyHex = this.#toHexString(publicKey);
189
+ const entry = this.#validators.get(publicKeyHex);
190
+ return entry ? (entry.sent || 0) : 0;
191
+ }
192
+
193
+ /**
194
+ * Increments the count of messages sent through a validator.
195
+ * @param {String | Buffer} publicKey - The public key hex string of the validator
196
+ */
197
+ incrementSentCount(publicKey) {
198
+ const publicKeyHex = this.#toHexString(publicKey);
199
+ const entry = this.#validators.get(publicKeyHex);
200
+ if (entry) {
201
+ entry.sent = (entry.sent || 0) + 1;
202
+ }
79
203
  }
80
204
 
81
205
  prettyPrint() {
82
206
  console.log('Connection count: ', this.connectionCount())
83
- console.log('Current connection: ', this.#currentValidator())
84
- console.log('Validators: ', this.#validatorsIndex.map(val => PeerWallet.encodeBech32m(TRAC_NETWORK_MSB_MAINNET_PREFIX, val)))
207
+ console.log('Validator map keys count: ', this.#validators.size)
208
+ console.log('Validator map keys: ', Array.from(this.#validators.keys()).map(val => this.#toAddress(val)).join(', '))
85
209
  }
86
210
 
87
- #currentValidator() {
88
- return this.#validatorsIndex[this.#currentValidatorIndex]
211
+ // Note 1: This method shuffles the whole array (in practice, probably around 50 elements)
212
+ // just to fetch a small subset of it (most times, 1 element).
213
+ // There are more efficient ways to pick a small subset of validators. Consider optimizing.
214
+ // Note 2: This method is unused now, but will be kept here for future reference
215
+ // TODO: Deprecated/Unused - remove if not needed
216
+ pickRandomSubset(validators, maxTargets) {
217
+ const copy = validators.slice();
218
+ const count = Math.min(maxTargets, copy.length);
219
+
220
+ for (let i = copy.length - 1; i > 0; i--) {
221
+ const j = Math.floor(Math.random() * (i + 1));
222
+ [copy[i], copy[j]] = [copy[j], copy[i]];
223
+ }
224
+
225
+ return copy.slice(0, count);
89
226
  }
90
227
 
91
- #getConnection() {
92
- return this.#validators[this.#currentValidator()]
228
+ /**
229
+ * Picks a random validator from the given array of validator public keys.
230
+ * @param {String[]} validatorPubKeys - An array of validator public key hex strings
231
+ * @returns {String|null} - A randomly selected validator public key
232
+ */
233
+ pickRandomValidator(validatorPubKeys) {
234
+ if (validatorPubKeys.length === 0) {
235
+ return null;
236
+ }
237
+ const index = Math.floor(Math.random() * validatorPubKeys.length);
238
+ return validatorPubKeys[index];
93
239
  }
94
240
 
95
- #append(publicKey, connection) {
96
- this.#validatorsIndex.push(publicKey)
97
- this.#validators[publicKey] = connection
241
+ /**
242
+ * Picks a random connected validator.
243
+ * @returns {String|null} - A randomly selected connected validator public key, or null if none are connected
244
+ */
245
+ pickRandomConnectedValidator() {
246
+ const connected = this.connectedValidators();
247
+ if (connected.length === 0) return null;
248
+ return this.pickRandomValidator(connected);
249
+ }
98
250
 
251
+ /**
252
+ * Appends a new validator connection.
253
+ * @param {String|Buffer} publicKey - The public key hex string of the validator
254
+ * @param {Object} connection - The connection object
255
+ */
256
+ #append(publicKey, connection) {
257
+ debugLog(`#append: appending validator ${this.#toAddress(publicKey)}`);
258
+ const publicKeyHex = this.#toHexString(publicKey);
259
+ if (this.#validators.has(publicKeyHex)) {
260
+ // This should never happen, but just in case, we log it
261
+ debugLog(`#append: tried to append existing validator: ${this.#toAddress(publicKey)}`);
262
+ return;
263
+ }
264
+ this.#validators.set(publicKeyHex, { connection, sent: 0 });
99
265
  connection.on('close', () => {
100
- if (this.#isRemoteEqual(publicKey)) {
101
- this.remove(publicKey)
102
- }
103
- })
266
+ debugLog(`#append: connection closing for validator ${this.#toAddress(publicKey)}`);
267
+ this.remove(publicKeyHex);
268
+ debugLog(`#append: connection closed for validator ${this.#toAddress(publicKey)}`);
269
+ });
104
270
  }
105
271
 
272
+ /**
273
+ * Updates an existing validator connection or adds it if not present.
274
+ * @param {String|Buffer} publicKey - The public key hex string of the validator
275
+ * @param {Object} connection - The connection object
276
+ */
106
277
  #update(publicKey, connection) {
107
- this.#validators[publicKey] = connection
278
+ // Note: Is there a good reason for the function 'update' to exist separately from 'append'?
279
+ // It seems that both could be merged into a single function that either adds or updates the entry.
280
+ // It would be preferable to keep them separated though, but we would need to review all usages to ensure correctness.
281
+ // Also, we should remove the 'else' branch below if we decide to keep 'update' and 'append' separated.
282
+ const publicKeyHex = this.#toHexString(publicKey);
283
+ debugLog(`#update: updating validator ${this.#toAddress(publicKey)}`);
284
+ if (this.#validators.has(publicKeyHex)) {
285
+ this.#validators.get(publicKeyHex).connection = connection;
286
+ } else {
287
+ this.#validators.set(publicKeyHex, { connection, sent: 0 });
288
+ }
108
289
  }
109
290
 
110
- #updateNext() {
111
- const next = this.#currentValidatorIndex + 1
112
- this.#currentValidatorIndex = next < this.#validatorsIndex.length ? next : 0
291
+ #evictRandomValidator() {
292
+ const connected = this.connectedValidators();
293
+ if (connected.length === 0) return;
294
+
295
+ const idx = Math.floor(Math.random() * connected.length);
296
+ const toRemove = connected[idx];
297
+ this.remove(toRemove);
113
298
  }
114
299
 
300
+ #toAddress(publicKey) {
301
+ const keyHex = b4a.isBuffer(publicKey) ? publicKey : b4a.from(publicKey, 'hex');
302
+ return PeerWallet.encodeBech32m(
303
+ TRAC_NETWORK_MSB_MAINNET_PREFIX, // TODO: This won't work for other networks. Make it dynamic after configuration is available.
304
+ keyHex
305
+ );
306
+ }
115
307
 
116
- #isRemoteEqual(publicKey) {
117
- return !!publicKey && !!this.#validators[publicKey]?.remotePublicKey && b4a.equals(this.#validators[publicKey]?.remotePublicKey, publicKey)
308
+ #toHexString(publicKey) {
309
+ return b4a.isBuffer(publicKey) ? publicKey.toString('hex') : publicKey;
118
310
  }
119
311
  }
120
312
 
121
- export default ConnectionManager;
313
+ export default ConnectionManager;
@@ -0,0 +1,95 @@
1
+ import { sleep } from '../../../utils/helpers.js';
2
+ import {
3
+ MAX_MESSAGE_SEND_ATTEMPTS,
4
+ MAX_SUCCESSIVE_MESSAGES_PER_VALIDATOR,
5
+ MESSAGE_VALIDATOR_RESPONSE_TIMEOUT_MS,
6
+ MESSAGE_VALIDATOR_RETRY_DELAY_MS
7
+ } from '../../../utils/constants.js';
8
+
9
+ /**
10
+ * MessageOrchestrator coordinates message submission, retry, and validator management.
11
+ * It works with ConnectionManager and ledger state to ensure reliable message delivery.
12
+ */
13
+ class MessageOrchestrator {
14
+ /**
15
+ * Attempts to send a message to validators with retries and state checks.
16
+ * @param {ConnectionManager} connectionManager - The connection manager instance
17
+ * @param {object} state - The state to look for the message outcome
18
+ * @param {object} options - { messageThreshold: number, maxRetries: number, retryDelay: number (milliseconds), timeout: number (milliseconds) }
19
+ * messageThreshold: How many successful sends before removing a validator from the pool
20
+ * maxRetries: How many times to retry sending a message to a single validator
21
+ * retryDelay: How long to wait between retries (ms)
22
+ * timeout: Overall timeout for sending a message (ms)
23
+ */
24
+ constructor(connectionManager, state, options = {}) {
25
+ this.connectionManager = connectionManager;
26
+ this.state = state;
27
+ // TODO: Adjust these default values or fetch them from config
28
+ this.messageThreshold = options.messageThreshold || MAX_SUCCESSIVE_MESSAGES_PER_VALIDATOR;
29
+ this.maxRetries = options.maxRetries || MAX_MESSAGE_SEND_ATTEMPTS; // Amount of retries for a single validator
30
+ this.retryDelay = options.retryDelay || MESSAGE_VALIDATOR_RETRY_DELAY_MS; // How long to wait before retrying (ms)
31
+ this.timeout = options.timeout || MESSAGE_VALIDATOR_RESPONSE_TIMEOUT_MS; // Overall timeout for sending a message (ms)
32
+ }
33
+
34
+ /**
35
+ * Sends a message to a single randomly selected connected validator.
36
+ * @param {object} message - The message object to be sent
37
+ * @returns {Promise<boolean>} - true if successful, false otherwise
38
+ */
39
+ async send(message) {
40
+ const startTime = Date.now();
41
+ while (Date.now() - startTime < this.timeout) {
42
+ const validator = this.connectionManager.pickRandomConnectedValidator();
43
+ if (!validator) return false;
44
+
45
+ const success = await this.#attemptSendMessage(validator, message);
46
+ if (success) {
47
+ return true;
48
+ }
49
+ }
50
+ return false;
51
+ }
52
+
53
+ async #attemptSendMessage(validator, message) {
54
+ let attempts = 0;
55
+ while (attempts < this.maxRetries) {
56
+ this.connectionManager.sendSingleMessage(message, validator);
57
+
58
+ const appeared = await this.waitForUnsignedState(message.tro.tx, this.retryDelay);
59
+ if (appeared) {
60
+ this.incrementSentCount(validator);
61
+ if (this.shouldRemove(validator)) {
62
+ this.connectionManager.remove(validator);
63
+ }
64
+ return true;
65
+ }
66
+ attempts++;
67
+ }
68
+
69
+ // If all retries fail, remove validator from pool
70
+ this.connectionManager.remove(validator);
71
+ return false;
72
+ }
73
+
74
+ async waitForUnsignedState(txHash, timeout) {
75
+ // Polls state for the transaction hash for up to timeout ms
76
+ const start = Date.now();
77
+ let entry = null;
78
+ while (Date.now() - start < timeout) {
79
+ await sleep(200);
80
+ entry = await this.state.get(txHash)
81
+ if (entry) return true;
82
+ }
83
+ return false;
84
+ }
85
+
86
+ incrementSentCount(validatorPubKey) {
87
+ this.connectionManager.incrementSentCount(validatorPubKey);
88
+ }
89
+
90
+ shouldRemove(validatorPubKey) {
91
+ return this.connectionManager.getSentCount(validatorPubKey) >= this.messageThreshold;
92
+ }
93
+ }
94
+
95
+ export default MessageOrchestrator;
@@ -8,6 +8,15 @@ import Scheduler from "../../../utils/Scheduler.js";
8
8
  const POLL_INTERVAL = 3500 // This was increase since the iterations dont wait for the execution its about 10 * DELAY_INTERVAL
9
9
  const DELAY_INTERVAL = 250
10
10
 
11
+ // -- Debug Mode --
12
+ // TODO: Implement a better debug system in the future. This is just temporary.
13
+ const DEBUG = false;
14
+ const debugLog = (...args) => {
15
+ if (DEBUG) {
16
+ console.log('DEBUG [ValidatorObserverService] ==> ', ...args);
17
+ }
18
+ };
19
+
11
20
  class ValidatorObserverService {
12
21
  #enable_validator_observer;
13
22
  #state;
@@ -55,36 +64,65 @@ class ValidatorObserverService {
55
64
  }
56
65
 
57
66
  async #worker(next) {
58
- if (!this.#network.validatorConnectionManager.maxConnections()) {
67
+ if (!this.#network.validatorConnectionManager.maxConnectionsReached()) {
59
68
  const length = await this.#lengthEntry()
60
69
 
61
70
  const promises = [];
62
71
  for (let i = 0; i < 10; i++) {
63
- promises.push(this.#findValidator(this.#address, length));
72
+ promises.push(this.#findValidator(this.#address, length + 1));
64
73
  await sleep(DELAY_INTERVAL); // Low key dangerous as the network progresses
65
74
  }
66
75
  await Promise.all(promises);
76
+
77
+ next(POLL_INTERVAL)
67
78
  }
68
- next(POLL_INTERVAL)
69
79
  }
70
80
 
71
- async #findValidator(address, length) {
72
- if (this.#network.validatorConnectionManager.maxConnections() || !this.#shouldRun()) return;
81
+ async #findValidator(address, validatorListLength) {
82
+ if (!this.#shouldRun()) return;
83
+ const maxAttempts = 50; // TODO: make configurable
84
+ let attempts = 0;
85
+ let isValidatorValid = false;
86
+ let validatorAddressBuffer = b4a.alloc(0);
87
+
88
+ while (attempts < maxAttempts && !isValidatorValid) {
89
+ const rndIndex = Math.floor(Math.random() * validatorListLength);
90
+ validatorAddressBuffer = await this.state.getWriterIndex(rndIndex);
91
+ isValidatorValid = await this.#isValidatorValid(address, validatorAddressBuffer, validatorListLength);
92
+ attempts++;
93
+ }
73
94
 
74
- const rndIndex = Math.floor(Math.random() * length);
75
- const validatorAddressBuffer = await this.state.getWriterIndex(rndIndex);
95
+ if (attempts >= maxAttempts) {
96
+ debugLog('Max attempts reached without finding a valid validator.');
97
+ }
98
+ else {
99
+ debugLog(`Found valid validator to connect after ${attempts} attempts.`);
100
+ }
101
+
102
+ if (!isValidatorValid) return;
103
+
104
+ const validatorAddress = bufferToAddress(validatorAddressBuffer);
105
+ const validatorPubKeyBuffer = PeerWallet.decodeBech32m(validatorAddress);
106
+ const validatorPubKeyHex = validatorPubKeyBuffer.toString('hex');
107
+ const adminEntry = await this.state.getAdminEntry();
76
108
 
77
- if (validatorAddressBuffer === null || b4a.byteLength(validatorAddressBuffer) !== TRAC_ADDRESS_SIZE) return;
78
109
 
110
+ if (validatorAddress !== adminEntry?.address || validatorListLength < MAX_WRITERS_FOR_ADMIN_INDEXER_CONNECTION) {
111
+ await this.#network.tryConnect(validatorPubKeyHex, 'validator');
112
+ }
113
+ };
114
+
115
+ async #isValidatorValid(forbiddenAddress, validatorAddressBuffer, validatorListLength) {
116
+ if (validatorAddressBuffer === null || b4a.byteLength(validatorAddressBuffer) !== TRAC_ADDRESS_SIZE) return false;
117
+
79
118
  const validatorAddress = bufferToAddress(validatorAddressBuffer);
80
- if (validatorAddress === address) return;
119
+ if (validatorAddress === forbiddenAddress) return false;
81
120
 
82
- const validatorPubKey = PeerWallet.decodeBech32m(validatorAddress).toString('hex');
121
+ const validatorPubKeyBuffer = PeerWallet.decodeBech32m(validatorAddress);
83
122
  const validatorEntry = await this.state.getNodeEntry(validatorAddress);
84
123
  const adminEntry = await this.state.getAdminEntry();
85
124
 
86
- if (validatorAddress === adminEntry?.address && length >= MAX_WRITERS_FOR_ADMIN_INDEXER_CONNECTION) {
87
- const validatorPubKeyBuffer = b4a.from(validatorPubKey, 'hex')
125
+ if (validatorAddress === adminEntry?.address && validatorListLength >= MAX_WRITERS_FOR_ADMIN_INDEXER_CONNECTION) {
88
126
  if (this.#network.validatorConnectionManager.exists(validatorPubKeyBuffer)) {
89
127
  this.#network.validatorConnectionManager.remove(validatorPubKeyBuffer)
90
128
  }
@@ -95,19 +133,17 @@ class ValidatorObserverService {
95
133
  // - Validator must exist and be a writer
96
134
  // - Cannot connect to indexers, except for admin-indexer
97
135
  // - Admin-indexer connection is allowed only when writers length has less than 10 writers
98
- if (
99
- this.#network.validatorConnectionManager.connected(validatorPubKey) ||
136
+ if (this.#network.validatorConnectionManager.connected(validatorPubKeyBuffer) ||
137
+ this.#network.validatorConnectionManager.maxConnectionsReached() ||
100
138
  validatorEntry === null ||
101
139
  !validatorEntry.isWriter ||
102
- (validatorEntry.isIndexer && (validatorAddress !== adminEntry?.address || length >= MAX_WRITERS_FOR_ADMIN_INDEXER_CONNECTION))
140
+ (validatorEntry.isIndexer && (validatorAddress !== adminEntry?.address || validatorListLength >= MAX_WRITERS_FOR_ADMIN_INDEXER_CONNECTION))
103
141
  ) {
104
- return;
142
+ return false;
105
143
  }
106
144
 
107
- if (validatorAddress !== adminEntry?.address || length < MAX_WRITERS_FOR_ADMIN_INDEXER_CONNECTION) {
108
- await this.#network.tryConnect(validatorPubKey, 'validator');
109
- }
110
- };
145
+ return true;
146
+ }
111
147
 
112
148
  #shouldRun() {
113
149
  if (!this.#enable_validator_observer || this.#isInterrupted) {