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.
- package/.github/workflows/acceptance-tests.yml +38 -0
- package/.github/workflows/lint-pr-title.yml +26 -0
- package/.github/workflows/publish.yml +33 -0
- package/.github/workflows/unit-tests.yml +34 -0
- package/package.json +7 -12
- package/proto/network.proto +74 -0
- package/rpc/rpc_services.js +4 -22
- package/scripts/generate-protobufs.js +12 -37
- package/src/config/config.js +9 -26
- package/src/config/env.js +17 -27
- package/src/core/network/Network.js +36 -73
- package/src/core/network/protocols/LegacyProtocol.js +11 -21
- package/src/core/network/protocols/NetworkMessages.js +17 -38
- package/src/core/network/protocols/ProtocolInterface.js +2 -14
- package/src/core/network/protocols/ProtocolSession.js +17 -144
- package/src/core/network/protocols/V1Protocol.js +18 -37
- package/src/core/network/protocols/legacy/NetworkMessageRouter.js +19 -25
- package/src/core/network/protocols/legacy/handlers/{LegacyGetRequestHandler.js → GetRequestHandler.js} +6 -6
- package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
- package/src/core/network/protocols/{legacy/handlers/LegacyRoleOperationHandler.js → shared/handlers/RoleOperationHandler.js} +11 -18
- package/src/core/network/protocols/{legacy/handlers/LegacySubnetworkOperationHandler.js → shared/handlers/SubnetworkOperationHandler.js} +17 -28
- package/src/core/network/protocols/{legacy/handlers/LegacyTransferOperationHandler.js → shared/handlers/TransferOperationHandler.js} +11 -17
- package/src/core/network/protocols/{legacy/handlers/BaseStateOperationHandler.js → shared/handlers/base/BaseOperationHandler.js} +12 -23
- package/src/core/network/protocols/shared/validators/{PartialBootstrapDeploymentValidator.js → PartialBootstrapDeployment.js} +4 -9
- package/src/core/network/protocols/shared/validators/{PartialRoleAccessValidator.js → PartialRoleAccess.js} +17 -51
- package/src/core/network/protocols/shared/validators/{PartialTransactionValidator.js → PartialTransaction.js} +7 -21
- package/src/core/network/protocols/shared/validators/{PartialTransferValidator.js → PartialTransfer.js} +9 -26
- package/src/core/network/protocols/shared/validators/{PartialOperationValidator.js → base/PartialOperation.js} +25 -47
- package/src/core/network/protocols/v1/NetworkMessageRouter.js +7 -91
- package/src/core/network/services/ConnectionManager.js +94 -146
- package/src/core/network/services/MessageOrchestrator.js +27 -151
- package/src/core/network/services/TransactionPoolService.js +18 -129
- package/src/core/network/services/TransactionRateLimiterService.js +34 -52
- package/src/core/network/services/ValidatorObserverService.js +26 -18
- package/src/core/state/State.js +19 -70
- package/src/index.js +8 -6
- package/src/messages/network/v1/NetworkMessageBuilder.js +79 -59
- package/src/messages/network/v1/NetworkMessageDirector.js +50 -16
- package/src/utils/Scheduler.js +8 -0
- package/src/utils/constants.js +5 -71
- package/src/utils/helpers.js +1 -10
- package/src/utils/normalizers.js +0 -38
- package/src/utils/protobuf/network.cjs +840 -0
- package/src/utils/protobuf/operationHelpers.js +3 -24
- package/tests/acceptance/v1/account/account.test.mjs +2 -8
- package/tests/acceptance/v1/tx/tx.test.mjs +1 -23
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +6 -34
- package/tests/fixtures/assembleMessage.fixtures.js +8 -7
- package/tests/fixtures/networkV1.fixtures.js +28 -2
- package/tests/helpers/autobaseTestHelpers.js +5 -2
- package/tests/helpers/createTestSignature.js +3 -2
- package/tests/helpers/transactionPayloads.mjs +2 -2
- package/tests/unit/messages/network/NetworkMessageBuilder.test.js +79 -239
- package/tests/unit/messages/network/NetworkMessageDirector.test.js +77 -223
- package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +5 -1
- package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +5 -1
- package/tests/unit/network/ConnectionManager.test.js +191 -0
- package/tests/unit/network/networkModule.test.js +1 -4
- package/tests/unit/unit.test.js +2 -2
- package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +2 -2
- package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +2 -2
- package/tests/unit/utils/protobuf/operationHelpers.test.js +4 -2
- package/tests/unit/utils/utils.test.js +0 -1
- package/proto/network/v1/enums/message_type.proto +0 -16
- package/proto/network/v1/enums/result_code.proto +0 -84
- package/proto/network/v1/messages/broadcast_transaction_request.proto +0 -9
- package/proto/network/v1/messages/broadcast_transaction_response.proto +0 -13
- package/proto/network/v1/messages/liveness_request.proto +0 -8
- package/proto/network/v1/messages/liveness_response.proto +0 -11
- package/proto/network/v1/network_message.proto +0 -22
- package/src/core/network/protocols/connectionPolicies.js +0 -88
- package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +0 -23
- package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +0 -27
- package/src/core/network/protocols/v1/V1ProtocolError.js +0 -91
- package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +0 -65
- package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +0 -389
- package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +0 -87
- package/src/core/network/protocols/v1/validators/V1BaseOperation.js +0 -211
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +0 -26
- package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +0 -276
- package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +0 -15
- package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +0 -17
- package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +0 -210
- package/src/core/network/services/PendingRequestService.js +0 -172
- package/src/core/network/services/TransactionCommitService.js +0 -149
- package/src/core/network/services/ValidatorHealthCheckService.js +0 -127
- package/src/utils/deepEqualApplyPayload.js +0 -40
- package/src/utils/logger.js +0 -25
- package/src/utils/protobuf/networkV1.generated.cjs +0 -2460
- package/tests/unit/network/LegacyNetworkMessageRouter.test.js +0 -54
- package/tests/unit/network/ProtocolSession.test.js +0 -127
- package/tests/unit/network/services/ConnectionManager.test.js +0 -450
- package/tests/unit/network/services/MessageOrchestrator.test.js +0 -445
- package/tests/unit/network/services/PendingRequestService.test.js +0 -431
- package/tests/unit/network/services/TransactionCommitService.test.js +0 -246
- package/tests/unit/network/services/TransactionPoolService.test.js +0 -489
- package/tests/unit/network/services/TransactionRateLimiterService.test.js +0 -139
- package/tests/unit/network/services/ValidatorHealthCheckService.test.js +0 -115
- package/tests/unit/network/services/services.test.js +0 -17
- package/tests/unit/network/utils/v1TestUtils.js +0 -153
- package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +0 -151
- package/tests/unit/network/v1/V1BaseOperation.test.js +0 -356
- package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +0 -129
- package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +0 -53
- package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +0 -512
- package/tests/unit/network/v1/V1LivenessRequest.test.js +0 -32
- package/tests/unit/network/v1/V1LivenessResponse.test.js +0 -45
- package/tests/unit/network/v1/V1ResultCode.test.js +0 -84
- package/tests/unit/network/v1/V1ValidationSchema.test.js +0 -13
- package/tests/unit/network/v1/connectionPolicies.test.js +0 -49
- package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +0 -284
- package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +0 -794
- package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +0 -193
- package/tests/unit/network/v1/v1.handlers.test.js +0 -15
- package/tests/unit/network/v1/v1.test.js +0 -19
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +0 -119
- package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +0 -136
- package/tests/unit/network/v1/v1ValidationSchema/common.test.js +0 -308
- package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +0 -90
- package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +0 -133
- 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
|
-
#
|
|
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,
|
|
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
|
|
31
|
-
return this.#
|
|
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.#
|
|
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 (
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
76
|
+
const isNodeAllowedToValidate = await this.state.allowedToValidate(this.address);
|
|
123
77
|
return isNodeAllowedToValidate || isAdminAllowedToValidate;
|
|
124
78
|
}
|
|
125
79
|
|
|
126
80
|
#prepareBatch() {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
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(
|
|
139
|
-
this.
|
|
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
|
|
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
|
|
22
|
-
- The number of transactions
|
|
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
|
|
23
|
+
#hasExceededRateLimit(peer) {
|
|
29
24
|
const peerData = this.#connectionsStatistics.get(peer);
|
|
30
|
-
const currentSecond = Math.floor((
|
|
31
|
-
|
|
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 =
|
|
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
|
|
44
|
-
If the peer has exceeded the rate limit, it disconnects the peer
|
|
45
|
-
Otherwise, it updates the connection info with the current timestamp
|
|
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
|
-
|
|
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.#
|
|
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
|
|
89
|
-
|
|
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
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
sessionStartTime: timestamp,
|
|
110
|
-
lastActivityTime: timestamp,
|
|
111
|
-
|
|
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:
|
|
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
|
|
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
|
|
123
|
+
#isConnectionExpired(peer) {
|
|
142
124
|
const peerData = this.#connectionsStatistics.get(peer);
|
|
143
|
-
return
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
58
|
+
console.info('ValidatorObserverService can not start. Disabled by configuration.');
|
|
51
59
|
return;
|
|
52
60
|
}
|
|
53
61
|
if (this.#scheduler && this.#scheduler.isRunning) {
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
97
|
+
debugLog('Max validator connections reached. Skipping this cycle.');
|
|
90
98
|
const now = Date.now();
|
|
91
99
|
const elapsed = now - this.initTimestamp;
|
|
92
|
-
|
|
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
|
-
|
|
121
|
+
debugLog('Max attempts reached without finding a valid validator.');
|
|
114
122
|
}
|
|
115
123
|
else {
|
|
116
|
-
|
|
124
|
+
debugLog(`Found valid validator to connect after ${attempts} attempts.`);
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
if (!isValidatorValid) return;
|
package/src/core/state/State.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
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
|
|
688
|
+
throw new Error("Failed to broadcast transaction after multiple attempts.");
|
|
687
689
|
}
|
|
688
690
|
|
|
689
691
|
console.info(`Transaction hash: ${payload.bdo.tx}`);
|