trac-msb 0.2.12 → 0.2.13

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 (114) hide show
  1. package/package.json +9 -4
  2. package/proto/network/v1/enums/message_type.proto +16 -0
  3. package/proto/network/v1/enums/result_code.proto +84 -0
  4. package/proto/network/v1/messages/broadcast_transaction_request.proto +9 -0
  5. package/proto/network/v1/messages/broadcast_transaction_response.proto +13 -0
  6. package/proto/network/v1/messages/liveness_request.proto +8 -0
  7. package/proto/network/v1/messages/liveness_response.proto +11 -0
  8. package/proto/network/v1/network_message.proto +22 -0
  9. package/rpc/rpc_services.js +22 -4
  10. package/scripts/generate-protobufs.js +37 -12
  11. package/src/config/config.js +26 -5
  12. package/src/config/env.js +25 -11
  13. package/src/core/network/Network.js +73 -36
  14. package/src/core/network/protocols/LegacyProtocol.js +21 -11
  15. package/src/core/network/protocols/NetworkMessages.js +38 -17
  16. package/src/core/network/protocols/ProtocolInterface.js +14 -2
  17. package/src/core/network/protocols/ProtocolSession.js +144 -17
  18. package/src/core/network/protocols/V1Protocol.js +37 -18
  19. package/src/core/network/protocols/connectionPolicies.js +88 -0
  20. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +25 -19
  21. package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +23 -12
  22. package/src/core/network/protocols/legacy/handlers/{GetRequestHandler.js → LegacyGetRequestHandler.js} +6 -6
  23. package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +23 -0
  24. package/src/core/network/protocols/{shared/handlers/RoleOperationHandler.js → legacy/handlers/LegacyRoleOperationHandler.js} +18 -11
  25. package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +28 -17
  26. package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +17 -11
  27. package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +27 -0
  28. package/src/core/network/protocols/shared/validators/{PartialBootstrapDeployment.js → PartialBootstrapDeploymentValidator.js} +9 -4
  29. package/src/core/network/protocols/shared/validators/{base/PartialOperation.js → PartialOperationValidator.js} +47 -25
  30. package/src/core/network/protocols/shared/validators/{PartialRoleAccess.js → PartialRoleAccessValidator.js} +51 -17
  31. package/src/core/network/protocols/shared/validators/{PartialTransaction.js → PartialTransactionValidator.js} +21 -7
  32. package/src/core/network/protocols/shared/validators/{PartialTransfer.js → PartialTransferValidator.js} +26 -9
  33. package/src/core/network/protocols/v1/NetworkMessageRouter.js +91 -7
  34. package/src/core/network/protocols/v1/V1ProtocolError.js +91 -0
  35. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +65 -0
  36. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +389 -0
  37. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +87 -0
  38. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +211 -0
  39. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +26 -0
  40. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +276 -0
  41. package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +15 -0
  42. package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +17 -0
  43. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +210 -0
  44. package/src/core/network/services/ConnectionManager.js +146 -94
  45. package/src/core/network/services/MessageOrchestrator.js +151 -27
  46. package/src/core/network/services/PendingRequestService.js +172 -0
  47. package/src/core/network/services/TransactionCommitService.js +149 -0
  48. package/src/core/network/services/TransactionPoolService.js +129 -18
  49. package/src/core/network/services/TransactionRateLimiterService.js +52 -34
  50. package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
  51. package/src/core/network/services/ValidatorObserverService.js +18 -26
  52. package/src/core/state/State.js +70 -19
  53. package/src/index.js +5 -4
  54. package/src/messages/network/v1/NetworkMessageBuilder.js +59 -79
  55. package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
  56. package/src/utils/Scheduler.js +0 -8
  57. package/src/utils/constants.js +71 -5
  58. package/src/utils/deepEqualApplyPayload.js +40 -0
  59. package/src/utils/helpers.js +10 -1
  60. package/src/utils/logger.js +25 -0
  61. package/src/utils/normalizers.js +38 -0
  62. package/src/utils/protobuf/networkV1.generated.cjs +2460 -0
  63. package/src/utils/protobuf/operationHelpers.js +24 -3
  64. package/tests/acceptance/v1/account/account.test.mjs +8 -2
  65. package/tests/acceptance/v1/tx/tx.test.mjs +23 -1
  66. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +34 -6
  67. package/tests/fixtures/networkV1.fixtures.js +2 -28
  68. package/tests/helpers/transactionPayloads.mjs +2 -2
  69. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +239 -79
  70. package/tests/unit/messages/network/NetworkMessageDirector.test.js +223 -77
  71. package/tests/unit/network/LegacyNetworkMessageRouter.test.js +54 -0
  72. package/tests/unit/network/ProtocolSession.test.js +127 -0
  73. package/tests/unit/network/networkModule.test.js +4 -1
  74. package/tests/unit/network/services/ConnectionManager.test.js +450 -0
  75. package/tests/unit/network/services/MessageOrchestrator.test.js +445 -0
  76. package/tests/unit/network/services/PendingRequestService.test.js +431 -0
  77. package/tests/unit/network/services/TransactionCommitService.test.js +246 -0
  78. package/tests/unit/network/services/TransactionPoolService.test.js +489 -0
  79. package/tests/unit/network/services/TransactionRateLimiterService.test.js +139 -0
  80. package/tests/unit/network/services/ValidatorHealthCheckService.test.js +115 -0
  81. package/tests/unit/network/services/services.test.js +17 -0
  82. package/tests/unit/network/utils/v1TestUtils.js +153 -0
  83. package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +151 -0
  84. package/tests/unit/network/v1/V1BaseOperation.test.js +356 -0
  85. package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +129 -0
  86. package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +53 -0
  87. package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +512 -0
  88. package/tests/unit/network/v1/V1LivenessRequest.test.js +32 -0
  89. package/tests/unit/network/v1/V1LivenessResponse.test.js +45 -0
  90. package/tests/unit/network/v1/V1ResultCode.test.js +84 -0
  91. package/tests/unit/network/v1/V1ValidationSchema.test.js +13 -0
  92. package/tests/unit/network/v1/connectionPolicies.test.js +49 -0
  93. package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +284 -0
  94. package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +794 -0
  95. package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +193 -0
  96. package/tests/unit/network/v1/v1.handlers.test.js +15 -0
  97. package/tests/unit/network/v1/v1.test.js +19 -0
  98. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +119 -0
  99. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +136 -0
  100. package/tests/unit/network/v1/v1ValidationSchema/common.test.js +308 -0
  101. package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +90 -0
  102. package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +133 -0
  103. package/tests/unit/unit.test.js +2 -2
  104. package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +102 -0
  105. package/tests/unit/utils/protobuf/operationHelpers.test.js +2 -4
  106. package/tests/unit/utils/utils.test.js +1 -0
  107. package/.github/workflows/acceptance-tests.yml +0 -38
  108. package/.github/workflows/lint-pr-title.yml +0 -26
  109. package/.github/workflows/publish.yml +0 -33
  110. package/.github/workflows/unit-tests.yml +0 -34
  111. package/proto/network.proto +0 -74
  112. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +0 -37
  113. package/src/utils/protobuf/network.cjs +0 -840
  114. package/tests/unit/network/ConnectionManager.test.js +0 -191
@@ -0,0 +1,91 @@
1
+ import {ResultCode} from '../../../../utils/constants.js';
2
+
3
+ export function getResultCode(err) {
4
+ return err instanceof V1ProtocolError ? err.resultCode : ResultCode.UNEXPECTED_ERROR;
5
+ }
6
+
7
+ export function shouldEndConnection(err) {
8
+ return err instanceof V1ProtocolError ? Boolean(err.endConnection) : false;
9
+ }
10
+
11
+ /**
12
+ * V1 protocol error type.
13
+ *
14
+ * `V1ProtocolError` is the v1 base class used by handlers/validators to attach:
15
+ * - `resultCode`: a stable `ResultCode` enum value for programmatic handling
16
+ * - `endConnection`: a transport hint (close peer connection after responding)
17
+ */
18
+ export class V1ProtocolError extends Error {
19
+ /**
20
+ * @param {number} resultCode Stable rejection reason (a `ResultCode` enum value).
21
+ * @param {string} message Human-readable error message.
22
+ * @param {boolean} [endConnection=false] Whether the transport should end the connection after responding.
23
+ */
24
+ constructor(resultCode, message, endConnection = false) {
25
+ super(message);
26
+ this.name = this.constructor.name;
27
+ this.resultCode = resultCode;
28
+ this.endConnection = Boolean(endConnection);
29
+ }
30
+ }
31
+
32
+ export class V1InvalidPayloadError extends V1ProtocolError {
33
+ constructor(message = 'Invalid payload', endConnection = false) {
34
+ super(ResultCode.INVALID_PAYLOAD, message, endConnection);
35
+ }
36
+ }
37
+
38
+ export class V1TxInvalidPayloadError extends V1ProtocolError {
39
+ constructor(message = 'Invalid tx payload', endConnection = false) {
40
+ super(ResultCode.TX_INVALID_PAYLOAD, message, endConnection);
41
+ }
42
+ }
43
+
44
+ export class V1SignatureInvalidError extends V1ProtocolError {
45
+ constructor(message = 'Signature invalid', endConnection = true) {
46
+ super(ResultCode.SIGNATURE_INVALID, message, endConnection);
47
+ }
48
+ }
49
+
50
+ export class V1RateLimitedError extends V1ProtocolError {
51
+ constructor(message = 'Rate limited', endConnection = true) {
52
+ super(ResultCode.RATE_LIMITED, message, endConnection);
53
+ }
54
+ }
55
+
56
+ export class V1UnexpectedError extends V1ProtocolError {
57
+ constructor(message = 'Unexpected error', endConnection = true) {
58
+ super(ResultCode.UNEXPECTED_ERROR, message, endConnection);
59
+ }
60
+ }
61
+
62
+ export class V1TimeoutError extends V1ProtocolError {
63
+ constructor(message = 'Request timed out', endConnection = false) {
64
+ super(ResultCode.TIMEOUT, message, endConnection);
65
+ }
66
+ }
67
+
68
+ export class V1NodeHasNoWriteAccess extends V1ProtocolError {
69
+ constructor(message = 'Node has no write access', endConnection = true) {
70
+ super(ResultCode.NODE_HAS_NO_WRITE_ACCESS, message, endConnection);
71
+ }
72
+ }
73
+
74
+ export class V1TxAcceptedProofUnavailable extends V1ProtocolError {
75
+ constructor(message = 'Transaction accepted but proof is unavailable', endConnection = false, timestamp = 0) {
76
+ super(ResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE, message, endConnection);
77
+ this.timestamp = Number.isSafeInteger(timestamp) && timestamp > 0 ? timestamp : 0;
78
+ }
79
+ }
80
+
81
+ export class V1NodeOverloadedError extends V1ProtocolError {
82
+ constructor(message = 'Commit queue is full', endConnection = true) {
83
+ super(ResultCode.NODE_OVERLOADED, message, endConnection);
84
+ }
85
+ }
86
+
87
+ export class V1TxAlreadyPendingError extends V1ProtocolError {
88
+ constructor(message = 'Transaction is already pending', endConnection = false) {
89
+ super(ResultCode.TX_ALREADY_PENDING, message, endConnection);
90
+ }
91
+ }
@@ -0,0 +1,65 @@
1
+ import {publicKeyToAddress} from "../../../../../utils/helpers.js";
2
+ import {V1ProtocolError, V1UnexpectedError} from "../V1ProtocolError.js";
3
+
4
+ class V1BaseOperationHandler {
5
+ #rateLimiterService;
6
+ #pendingRequestService;
7
+ #config;
8
+
9
+ constructor(rateLimiterService, pendingRequestService, config) {
10
+ this.#rateLimiterService = rateLimiterService;
11
+ this.#pendingRequestService = pendingRequestService;
12
+ this.#config = config;
13
+ }
14
+
15
+ get config() {
16
+ return this.#config;
17
+ }
18
+
19
+ applyRateLimit(connection) {
20
+ if (!this.#config.disableRateLimit) {
21
+ this.#rateLimiterService.v1HandleRateLimit(connection);
22
+ }
23
+ }
24
+
25
+ async resolvePendingResponse(message, connection, validator, extractResultCode) {
26
+ const pendingRequestServiceEntry = this.#pendingRequestService.getPendingRequest(message.id);
27
+ if (!pendingRequestServiceEntry) return false;
28
+
29
+ this.#pendingRequestService.stopPendingRequestTimeout(message.id);
30
+ await validator.validate(message, connection, pendingRequestServiceEntry);
31
+
32
+ const resultCode = extractResultCode(message);
33
+ this.#pendingRequestService.resolvePendingRequest(message.id, resultCode);
34
+ return true;
35
+ }
36
+
37
+ handlePendingResponseError(messageId, connection, error, step) {
38
+ const protocolError = this.#toProtocolError(error);
39
+ const rejected = this.#pendingRequestService.rejectPendingRequest(messageId, protocolError);
40
+ if (!rejected) return;
41
+ this.displayError(step, connection.remotePublicKey, protocolError);
42
+ }
43
+
44
+ async sendResponseAndMaybeClose(connection, response, endConnection) {
45
+ connection.protocolSession.sendAndForget(response);
46
+ if (!endConnection) return;
47
+
48
+ await connection.flush();
49
+ connection.end();
50
+ }
51
+
52
+ displayError(step = "undefined step", senderPublicKey, error) {
53
+ const errorMessage = error?.message ?? 'Unexpected error';
54
+ console.error(`${this.constructor.name}: ${step} ${publicKeyToAddress(senderPublicKey, this.#config)}: ${errorMessage}`);
55
+ }
56
+
57
+ #toProtocolError(error) {
58
+ if (error instanceof V1ProtocolError) {
59
+ return error;
60
+ }
61
+ return new V1UnexpectedError(error?.message ?? 'Unexpected error');
62
+ }
63
+ }
64
+
65
+ export default V1BaseOperationHandler;
@@ -0,0 +1,389 @@
1
+ import {networkMessageFactory} from "../../../../../messages/network/v1/networkMessageFactory.js";
2
+ import {
3
+ NETWORK_CAPABILITIES,
4
+ OperationType,
5
+ ResultCode
6
+ } from "../../../../../utils/constants.js";
7
+ import {
8
+ getResultCode,
9
+ V1TxInvalidPayloadError,
10
+ V1NodeHasNoWriteAccess,
11
+ shouldEndConnection,
12
+ V1TxAcceptedProofUnavailable,
13
+ V1UnexpectedError,
14
+ V1NodeOverloadedError,
15
+ V1TxAlreadyPendingError,
16
+ V1TimeoutError,
17
+ V1ProtocolError
18
+ } from "../V1ProtocolError.js";
19
+ import V1BroadcastTransactionRequest from "../validators/V1BroadcastTransactionRequest.js";
20
+ import {
21
+ unsafeDecodeApplyOperation,
22
+ unsafeEncodeApplyOperation
23
+ } from "../../../../../utils/protobuf/operationHelpers.js";
24
+ import {isBootstrapDeployment, isRoleAccess, isTransaction, isTransfer} from '../../../../../utils/applyOperations.js';
25
+ import PartialRoleAccessValidator from "../../shared/validators/PartialRoleAccessValidator.js";
26
+ import PartialBootstrapDeploymentValidator from "../../shared/validators/PartialBootstrapDeploymentValidator.js";
27
+ import PartialTransactionValidator from "../../shared/validators/PartialTransactionValidator.js";
28
+ import PartialTransferValidator from "../../shared/validators/PartialTransferValidator.js";
29
+ import {applyStateMessageFactory} from "../../../../../messages/state/applyStateMessageFactory.js";
30
+ import V1BroadcastTransactionResponse from "../validators/V1BroadcastTransactionResponse.js";
31
+ import V1BaseOperationHandler from "./V1BaseOperationHandler.js";
32
+ import {
33
+ TransactionPoolMissingCommitReceiptError,
34
+ TransactionPoolProofUnavailableError,
35
+ TransactionPoolFullError, TransactionPoolInvalidIncomingDataError, TransactionPoolAlreadyQueuedError
36
+ } from "../../../services/TransactionPoolService.js";
37
+ import {
38
+ PendingCommitInvalidTxHashError,
39
+ PendingCommitAlreadyExistsError,
40
+ PendingCommitBufferFullError, PendingCommitTimeoutError
41
+ } from "../../../services/TransactionCommitService.js";
42
+ import b4a from "b4a";
43
+
44
+ class V1BroadcastTransactionOperationHandler extends V1BaseOperationHandler {
45
+ #state;
46
+ #wallet;
47
+ #txPoolService;
48
+ #broadcastTransactionRequestValidator;
49
+ #broadcastTransactionResponseValidator;
50
+ #partialRoleAccessValidator;
51
+ #partialBootstrapDeploymentValidator;
52
+ #partialTransactionValidator;
53
+ #partialTransferValidator;
54
+ #transactionCommitService;
55
+
56
+ constructor(state, wallet, rateLimiterService, txPoolService, pendingRequestService, transactionCommitService, config) {
57
+ super(rateLimiterService, pendingRequestService, config);
58
+ this.#state = state;
59
+ this.#wallet = wallet;
60
+ this.#txPoolService = txPoolService;
61
+ this.#broadcastTransactionRequestValidator = new V1BroadcastTransactionRequest(config);
62
+ this.#partialRoleAccessValidator = new PartialRoleAccessValidator(state, this.#wallet.address, config);
63
+ this.#partialBootstrapDeploymentValidator = new PartialBootstrapDeploymentValidator(state, this.#wallet.address, config);
64
+ this.#partialTransactionValidator = new PartialTransactionValidator(state, this.#wallet.address, config);
65
+ this.#partialTransferValidator = new PartialTransferValidator(state, this.#wallet.address, config);
66
+ this.#broadcastTransactionResponseValidator = new V1BroadcastTransactionResponse(state, config);
67
+ this.#transactionCommitService = transactionCommitService;
68
+ }
69
+
70
+ async handleRequest(message, connection) {
71
+ let resultCode = ResultCode.OK;
72
+ let endConnection = false;
73
+ let proof = null
74
+ let timestamp = 0;
75
+
76
+ try {
77
+ this.applyRateLimit(connection);
78
+ await this.#broadcastTransactionRequestValidator.validate(message, connection.remotePublicKey);
79
+ await this.#validationCapability();
80
+ this.#isTxPoolFull();
81
+ const decodedTransaction = this.decodeApplyOperation(message.broadcast_transaction_request.data);
82
+ this.#sanitizeDecodedPartialTransaction(decodedTransaction);
83
+ const receipt = await this.dispatchTransaction(decodedTransaction);
84
+ proof = receipt.proof;
85
+ timestamp = receipt.timestamp;
86
+ } catch (error) {
87
+ const protocolError = error instanceof V1ProtocolError
88
+ ? error
89
+ : new V1UnexpectedError(error?.message ?? 'Unexpected error', true);
90
+ resultCode = getResultCode(protocolError);
91
+ if (
92
+ resultCode === ResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE &&
93
+ Number.isSafeInteger(protocolError.timestamp) &&
94
+ protocolError.timestamp > 0
95
+ ) {
96
+ timestamp = protocolError.timestamp;
97
+ }
98
+ endConnection = shouldEndConnection(protocolError);
99
+ this.displayError(
100
+ "failed to process broadcast transaction request from sender",
101
+ connection.remotePublicKey,
102
+ protocolError
103
+ );
104
+ }
105
+
106
+ try {
107
+ const response = await this.#buildBroadcastTransactionResponse(
108
+ message.id,
109
+ NETWORK_CAPABILITIES,
110
+ proof,
111
+ timestamp,
112
+ resultCode,
113
+ );
114
+
115
+ await this.sendResponseAndMaybeClose(
116
+ connection,
117
+ response,
118
+ endConnection
119
+ );
120
+ } catch (error) {
121
+ this.displayError(
122
+ "failed to build/send response to sender",
123
+ connection.remotePublicKey,
124
+ error
125
+ );
126
+ connection.end();
127
+ }
128
+ }
129
+
130
+ async handleResponse(message, connection) {
131
+ try {
132
+ this.applyRateLimit(connection);
133
+ await this.resolvePendingResponse(
134
+ message,
135
+ connection,
136
+ this.#broadcastTransactionResponseValidator,
137
+ this.#extractBroadcastResultCode,
138
+ );
139
+ } catch (error) {
140
+ this.handlePendingResponseError(
141
+ message.id,
142
+ connection,
143
+ error,
144
+ "failed to process broadcast transaction response from sender"
145
+ );
146
+ }
147
+ }
148
+
149
+ async #buildBroadcastTransactionResponse(id, capabilities, proof, timestamp = null, resultCode) {
150
+ try {
151
+ return await networkMessageFactory(this.#wallet, this.config).buildBroadcastTransactionResponse(
152
+ id,
153
+ capabilities,
154
+ resultCode,
155
+ proof,
156
+ timestamp
157
+ );
158
+ } catch (error) {
159
+ throw new V1UnexpectedError(`Failed to build broadcast transaction response: ${error.message}`);
160
+ }
161
+ }
162
+
163
+ decodeApplyOperation(message) {
164
+ try {
165
+ return unsafeDecodeApplyOperation(message);
166
+ } catch (error) {
167
+ throw new V1UnexpectedError(`Failed to decode apply operation from message: ${error.message}`);
168
+ }
169
+ }
170
+
171
+ #sanitizeDecodedPartialTransaction(decodedTransaction) {
172
+ // Protobuf decode sets optional completion fields as null, but partial validators expect those fields to be absent.
173
+ // Otherwise, the presence of null fields causes validation to fail with "Expected type X but got null" errors.
174
+
175
+ const type = decodedTransaction?.type;
176
+ const operationKey = this.#getOperationPayloadKey(type);
177
+
178
+ if (!operationKey || !decodedTransaction?.[operationKey] || typeof decodedTransaction[operationKey] !== 'object') {
179
+ return;
180
+ }
181
+
182
+ for (const completionField of ['va', 'vn', 'vs']) {
183
+ if (decodedTransaction[operationKey][completionField] === null) {
184
+ delete decodedTransaction[operationKey][completionField];
185
+ }
186
+ }
187
+ }
188
+
189
+ #getOperationPayloadKey(type) {
190
+ if (isRoleAccess(type)) return 'rao';
191
+ if (isTransaction(type)) return 'txo';
192
+ if (isBootstrapDeployment(type)) return 'bdo';
193
+ if (isTransfer(type)) return 'tro';
194
+ return null;
195
+ }
196
+
197
+ #isTxPoolFull() {
198
+ try {
199
+ this.#txPoolService.validateEnqueue();
200
+ } catch (error) {
201
+ if (error instanceof TransactionPoolFullError) {
202
+ throw new V1NodeOverloadedError('Transaction pool is full, ignoring incoming transaction.');
203
+ }
204
+ throw error;
205
+ }
206
+ }
207
+
208
+ async #validationCapability() {
209
+ const isAllowedToValidate = await this.#state.allowedToValidate(this.#wallet.address);
210
+ const isAdminAllowedToValidate = await this.#state.isAdminAllowedToValidate();
211
+ const canValidate = isAllowedToValidate || isAdminAllowedToValidate;
212
+ if (!canValidate) {
213
+ throw new V1NodeHasNoWriteAccess('State is not writable or is an indexer without admin privileges.');
214
+ }
215
+ }
216
+
217
+ async dispatchTransaction(decodedTransaction) {
218
+ if (!decodedTransaction || !Number.isInteger(decodedTransaction.type) || decodedTransaction.type === 0) {
219
+ throw new V1TxInvalidPayloadError('Decoded transaction type is missing.', false);
220
+ }
221
+ if (!this.#transactionCommitService) {
222
+ throw new V1UnexpectedError('TransactionCommitService is not configured.');
223
+ }
224
+
225
+ const type = decodedTransaction.type;
226
+ let completeTransactionOperation;
227
+
228
+ // TODO: Consider moving logic below to strategy design pattern.
229
+ if (isRoleAccess(type)) {
230
+ await this.#partialRoleAccessValidator.validate(decodedTransaction);
231
+ completeTransactionOperation = await this.#buildCompleteRoleAccessOperation(decodedTransaction);
232
+ } else if (isTransaction(type)) {
233
+ await this.#partialTransactionValidator.validate(decodedTransaction);
234
+ completeTransactionOperation = await this.#buildCompleteTransactionOperation(decodedTransaction);
235
+ } else if (isBootstrapDeployment(type)) {
236
+ await this.#partialBootstrapDeploymentValidator.validate(decodedTransaction);
237
+ completeTransactionOperation = await this.#buildCompleteBootstrapDeploymentOperation(decodedTransaction);
238
+ } else if (isTransfer(type)) {
239
+ await this.#partialTransferValidator.validate(decodedTransaction);
240
+ completeTransactionOperation = await this.#buildCompleteTransferOperation(decodedTransaction);
241
+ } else {
242
+ throw new V1TxInvalidPayloadError(`Unsupported transaction type: ${type}`, false);
243
+ }
244
+
245
+ const payloadKey = this.#getOperationPayloadKey(type);
246
+ const txHash = b4a.toString(decodedTransaction[payloadKey].tx, 'hex');
247
+
248
+ const encodedCompleteTransaction = unsafeEncodeApplyOperation(completeTransactionOperation);
249
+ let pendingCommit;
250
+
251
+ try {
252
+ pendingCommit = this.#transactionCommitService.registerPendingCommit(txHash);
253
+ pendingCommit.catch(() => {});
254
+ } catch (error) {
255
+ if (error instanceof PendingCommitInvalidTxHashError) {
256
+ throw new V1ProtocolError(ResultCode.TX_HASH_INVALID_FORMAT, error.message, false);
257
+ }
258
+ if (error instanceof PendingCommitAlreadyExistsError) {
259
+ throw new V1TxAlreadyPendingError(error.message);
260
+ }
261
+ if (error instanceof PendingCommitBufferFullError) {
262
+ throw new V1NodeOverloadedError(error.message);
263
+ }
264
+ throw error;
265
+ }
266
+
267
+ try {
268
+ this.#txPoolService.addTransaction(txHash, encodedCompleteTransaction);
269
+ } catch (error) {
270
+ let err = error;
271
+ if (error instanceof TransactionPoolFullError) {
272
+ err = new V1NodeOverloadedError(error.message);
273
+ } else if (error instanceof TransactionPoolAlreadyQueuedError) {
274
+ err = new V1TxAlreadyPendingError(error.message);
275
+ } else if (error instanceof TransactionPoolInvalidIncomingDataError) {
276
+ err = new V1ProtocolError(
277
+ ResultCode.INTERNAL_ENQUEUE_VALIDATION_FAILED,
278
+ `Internal enqueue validation failed: ${error.message}`,
279
+ false
280
+ );
281
+ }
282
+ this.#transactionCommitService.rejectPendingCommit(txHash, err);
283
+ throw err; // will be mapped anyway on lower level.
284
+ }
285
+
286
+ let receipt;
287
+ try {
288
+ receipt = await pendingCommit;
289
+ } catch (error) {
290
+ if (error instanceof TransactionPoolProofUnavailableError) {
291
+ throw new V1TxAcceptedProofUnavailable(error.message, false, error.timestamp);
292
+ }
293
+ if (error instanceof TransactionPoolMissingCommitReceiptError) {
294
+ throw new V1ProtocolError(ResultCode.TX_COMMITTED_RECEIPT_MISSING, error.message, false);
295
+ }
296
+ if (error instanceof PendingCommitTimeoutError) {
297
+ throw new V1TimeoutError(error.message);
298
+ }
299
+ throw error;
300
+ }
301
+
302
+ return receipt;
303
+ }
304
+
305
+
306
+ //TODO: Move responsibility to new class (next 4 functions)
307
+ async #buildCompleteRoleAccessOperation(decodedTransaction) {
308
+ const factory = applyStateMessageFactory(this.#wallet, this.config);
309
+
310
+ switch (decodedTransaction.type) {
311
+ case OperationType.ADD_WRITER:
312
+ return factory.buildCompleteAddWriterMessage(
313
+ decodedTransaction.address,
314
+ decodedTransaction.rao.tx,
315
+ decodedTransaction.rao.txv,
316
+ decodedTransaction.rao.iw,
317
+ decodedTransaction.rao.in,
318
+ decodedTransaction.rao.is
319
+ );
320
+ case OperationType.REMOVE_WRITER:
321
+ return factory.buildCompleteRemoveWriterMessage(
322
+ decodedTransaction.address,
323
+ decodedTransaction.rao.tx,
324
+ decodedTransaction.rao.txv,
325
+ decodedTransaction.rao.iw,
326
+ decodedTransaction.rao.in,
327
+ decodedTransaction.rao.is
328
+ );
329
+ case OperationType.ADMIN_RECOVERY:
330
+ return factory.buildCompleteAdminRecoveryMessage(
331
+ decodedTransaction.address,
332
+ decodedTransaction.rao.tx,
333
+ decodedTransaction.rao.txv,
334
+ decodedTransaction.rao.iw,
335
+ decodedTransaction.rao.in,
336
+ decodedTransaction.rao.is
337
+ );
338
+ default:
339
+ throw new V1TxInvalidPayloadError(`Unsupported role access transaction type: ${decodedTransaction.type}`, false);
340
+ }
341
+ }
342
+
343
+ async #buildCompleteTransactionOperation(decodedTransaction) {
344
+ return applyStateMessageFactory(this.#wallet, this.config)
345
+ .buildCompleteTransactionOperationMessage(
346
+ decodedTransaction.address,
347
+ decodedTransaction.txo.tx,
348
+ decodedTransaction.txo.txv,
349
+ decodedTransaction.txo.iw,
350
+ decodedTransaction.txo.in,
351
+ decodedTransaction.txo.ch,
352
+ decodedTransaction.txo.is,
353
+ decodedTransaction.txo.bs,
354
+ decodedTransaction.txo.mbs
355
+ );
356
+ }
357
+
358
+ async #buildCompleteBootstrapDeploymentOperation(decodedTransaction) {
359
+ return applyStateMessageFactory(this.#wallet, this.config)
360
+ .buildCompleteBootstrapDeploymentMessage(
361
+ decodedTransaction.address,
362
+ decodedTransaction.bdo.tx,
363
+ decodedTransaction.bdo.txv,
364
+ decodedTransaction.bdo.bs,
365
+ decodedTransaction.bdo.ic,
366
+ decodedTransaction.bdo.in,
367
+ decodedTransaction.bdo.is
368
+ );
369
+ }
370
+
371
+ async #buildCompleteTransferOperation(decodedTransaction) {
372
+ return applyStateMessageFactory(this.#wallet, this.config)
373
+ .buildCompleteTransferOperationMessage(
374
+ decodedTransaction.address,
375
+ decodedTransaction.tro.tx,
376
+ decodedTransaction.tro.txv,
377
+ decodedTransaction.tro.in,
378
+ decodedTransaction.tro.to,
379
+ decodedTransaction.tro.am,
380
+ decodedTransaction.tro.is
381
+ );
382
+ }
383
+
384
+ #extractBroadcastResultCode(payload) {
385
+ return payload.broadcast_transaction_response.result;
386
+ }
387
+ }
388
+
389
+ export default V1BroadcastTransactionOperationHandler;
@@ -0,0 +1,87 @@
1
+ import { networkMessageFactory } from "../../../../../messages/network/v1/networkMessageFactory.js";
2
+ import { NETWORK_CAPABILITIES, ResultCode } from "../../../../../utils/constants.js";
3
+ import V1LivenessRequest from "../validators/V1LivenessRequest.js";
4
+ import {getResultCode, shouldEndConnection, V1UnexpectedError} from "../V1ProtocolError.js";
5
+ import V1LivenessResponse from "../validators/V1LivenessResponse.js";
6
+ import V1BaseOperationHandler from "./V1BaseOperationHandler.js";
7
+
8
+ class V1LivenessOperationHandler extends V1BaseOperationHandler {
9
+ #wallet;
10
+ #v1LivenessRequestValidator;
11
+ #v1LivenessResponseValidator;
12
+
13
+ constructor(wallet, rateLimiterService, pendingRequestService, config) {
14
+ super(rateLimiterService, pendingRequestService, config);
15
+ this.#wallet = wallet;
16
+ this.#v1LivenessRequestValidator = new V1LivenessRequest(config);
17
+ this.#v1LivenessResponseValidator = new V1LivenessResponse(config);
18
+ }
19
+
20
+ async handleRequest(message, connection) {
21
+ let resultCode = ResultCode.OK;
22
+ let endConnection = false;
23
+ try {
24
+ this.applyRateLimit(connection);
25
+ await this.#v1LivenessRequestValidator.validate(message, connection.remotePublicKey);
26
+ } catch (error) {
27
+ resultCode = getResultCode(error);
28
+ endConnection = shouldEndConnection(error);
29
+ this.displayError("failed to process liveness request from sender",
30
+ connection.remotePublicKey,
31
+ error
32
+ );
33
+ }
34
+
35
+ try {
36
+ const response = await this.#buildLivenessResponsePayload(message.id, NETWORK_CAPABILITIES, resultCode);
37
+ await this.sendResponseAndMaybeClose(
38
+ connection,
39
+ response,
40
+ endConnection
41
+ );
42
+ } catch (error) {
43
+ this.displayError("failed to build/send response to sender",
44
+ connection.remotePublicKey,
45
+ error
46
+ );
47
+ connection.end();
48
+ }
49
+ }
50
+
51
+ async handleResponse(message, connection) {
52
+ try {
53
+ this.applyRateLimit(connection);
54
+ await this.resolvePendingResponse(
55
+ message,
56
+ connection,
57
+ this.#v1LivenessResponseValidator,
58
+ this.#extractLivenessResultCode
59
+ );
60
+ } catch (error) {
61
+ this.handlePendingResponseError(
62
+ message.id,
63
+ connection,
64
+ error,
65
+ "Failed to process liveness response from sender"
66
+ );
67
+ }
68
+ }
69
+
70
+ async #buildLivenessResponsePayload(id, capabilities, resultCode) {
71
+ try {
72
+ return await networkMessageFactory(this.#wallet, this.config).buildLivenessResponse(
73
+ id,
74
+ capabilities,
75
+ resultCode
76
+ );
77
+ } catch (error) {
78
+ throw new V1UnexpectedError(`Failed to build liveness response: ${error.message}`);
79
+ }
80
+ }
81
+
82
+ #extractLivenessResultCode(payload) {
83
+ return payload.liveness_response.result;
84
+ }
85
+ }
86
+
87
+ export default V1LivenessOperationHandler;