trac-msb 0.2.9 → 0.2.11

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 (143) hide show
  1. package/CODE_OF_CONDUCT.md +128 -0
  2. package/README.md +33 -18
  3. package/docker-compose.yml +1 -0
  4. package/docs/trac_network_http_api.openapi.yaml +889 -0
  5. package/msb.mjs +4 -21
  6. package/package.json +16 -12
  7. package/proto/network/v1/enums/message_type.proto +16 -0
  8. package/proto/network/v1/enums/result_code.proto +84 -0
  9. package/proto/network/v1/messages/broadcast_transaction_request.proto +9 -0
  10. package/proto/network/v1/messages/broadcast_transaction_response.proto +13 -0
  11. package/proto/network/v1/messages/liveness_request.proto +8 -0
  12. package/proto/network/v1/messages/liveness_response.proto +11 -0
  13. package/proto/network/v1/network_message.proto +22 -0
  14. package/rpc/handlers.js +163 -90
  15. package/rpc/routes/v1.js +3 -1
  16. package/rpc/rpc_server.js +3 -3
  17. package/rpc/rpc_services.js +45 -31
  18. package/rpc/utils/helpers.js +82 -51
  19. package/scripts/generate-protobufs.js +37 -12
  20. package/src/config/args.js +46 -0
  21. package/src/config/config.js +99 -5
  22. package/src/config/env.js +86 -7
  23. package/src/core/network/Network.js +79 -46
  24. package/src/core/network/protocols/LegacyProtocol.js +21 -11
  25. package/src/core/network/protocols/NetworkMessages.js +38 -17
  26. package/src/core/network/protocols/ProtocolInterface.js +14 -2
  27. package/src/core/network/protocols/ProtocolSession.js +144 -17
  28. package/src/core/network/protocols/V1Protocol.js +37 -18
  29. package/src/core/network/protocols/connectionPolicies.js +88 -0
  30. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +26 -20
  31. package/src/core/network/protocols/{shared/handlers/base/BaseOperationHandler.js → legacy/handlers/BaseStateOperationHandler.js} +25 -15
  32. package/src/core/network/protocols/legacy/handlers/{GetRequestHandler.js → LegacyGetRequestHandler.js} +6 -6
  33. package/src/core/network/protocols/legacy/handlers/LegacyResponseHandler.js +23 -0
  34. package/src/core/network/protocols/{shared/handlers/RoleOperationHandler.js → legacy/handlers/LegacyRoleOperationHandler.js} +20 -13
  35. package/src/core/network/protocols/{shared/handlers/SubnetworkOperationHandler.js → legacy/handlers/LegacySubnetworkOperationHandler.js} +29 -18
  36. package/src/core/network/protocols/{shared/handlers/TransferOperationHandler.js → legacy/handlers/LegacyTransferOperationHandler.js} +18 -12
  37. package/src/core/network/protocols/legacy/validators/base/BaseResponse.js +1 -1
  38. package/src/core/network/protocols/shared/errors/SharedValidatorRejectionError.js +27 -0
  39. package/src/core/network/protocols/shared/validators/{PartialBootstrapDeployment.js → PartialBootstrapDeploymentValidator.js} +9 -4
  40. package/src/core/network/protocols/shared/validators/{base/PartialOperation.js → PartialOperationValidator.js} +47 -25
  41. package/src/core/network/protocols/shared/validators/{PartialRoleAccess.js → PartialRoleAccessValidator.js} +51 -17
  42. package/src/core/network/protocols/shared/validators/{PartialTransaction.js → PartialTransactionValidator.js} +21 -7
  43. package/src/core/network/protocols/shared/validators/{PartialTransfer.js → PartialTransferValidator.js} +26 -9
  44. package/src/core/network/protocols/v1/NetworkMessageRouter.js +91 -7
  45. package/src/core/network/protocols/v1/V1ProtocolError.js +91 -0
  46. package/src/core/network/protocols/v1/handlers/V1BaseOperationHandler.js +65 -0
  47. package/src/core/network/protocols/v1/handlers/V1BroadcastTransactionOperationHandler.js +389 -0
  48. package/src/core/network/protocols/v1/handlers/V1LivenessOperationHandler.js +87 -0
  49. package/src/core/network/protocols/v1/validators/V1BaseOperation.js +211 -0
  50. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionRequest.js +26 -0
  51. package/src/core/network/protocols/v1/validators/V1BroadcastTransactionResponse.js +276 -0
  52. package/src/core/network/protocols/v1/validators/V1LivenessRequest.js +15 -0
  53. package/src/core/network/protocols/v1/validators/V1LivenessResponse.js +17 -0
  54. package/src/core/network/protocols/v1/validators/V1ValidationSchema.js +210 -0
  55. package/src/core/network/services/ConnectionManager.js +147 -95
  56. package/src/core/network/services/MessageOrchestrator.js +152 -28
  57. package/src/core/network/services/PendingRequestService.js +172 -0
  58. package/src/core/network/services/TransactionCommitService.js +149 -0
  59. package/src/core/network/services/TransactionPoolService.js +133 -22
  60. package/src/core/network/services/TransactionRateLimiterService.js +57 -42
  61. package/src/core/network/services/ValidatorHealthCheckService.js +127 -0
  62. package/src/core/network/services/ValidatorObserverService.js +23 -32
  63. package/src/core/state/State.js +72 -22
  64. package/src/index.js +8 -5
  65. package/src/messages/network/v1/NetworkMessageBuilder.js +61 -81
  66. package/src/messages/network/v1/NetworkMessageDirector.js +16 -50
  67. package/src/messages/state/ApplyStateMessageBuilder.js +1 -1
  68. package/src/utils/Scheduler.js +0 -8
  69. package/src/utils/check.js +1 -1
  70. package/src/utils/constants.js +68 -19
  71. package/src/utils/deepEqualApplyPayload.js +40 -0
  72. package/src/utils/fileUtils.js +13 -0
  73. package/src/utils/helpers.js +10 -1
  74. package/src/utils/logger.js +25 -0
  75. package/src/utils/normalizers.js +38 -0
  76. package/src/utils/protobuf/networkV1.generated.cjs +2460 -0
  77. package/src/utils/protobuf/operationHelpers.js +24 -3
  78. package/src/utils/type.js +26 -0
  79. package/tests/acceptance/v1/account/account.test.mjs +8 -2
  80. package/tests/acceptance/v1/balance/balance.test.mjs +1 -2
  81. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +26 -30
  82. package/tests/acceptance/v1/health/health.test.mjs +33 -0
  83. package/tests/acceptance/v1/rpc.test.mjs +3 -2
  84. package/tests/acceptance/v1/tx/tx.test.mjs +50 -17
  85. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +60 -18
  86. package/tests/fixtures/check.fixtures.js +33 -32
  87. package/tests/fixtures/networkV1.fixtures.js +2 -27
  88. package/tests/fixtures/protobuf.fixtures.js +33 -32
  89. package/tests/helpers/StateNetworkFactory.js +2 -2
  90. package/tests/helpers/address.js +6 -0
  91. package/tests/helpers/autobaseTestHelpers.js +2 -1
  92. package/tests/helpers/config.js +2 -1
  93. package/tests/helpers/setupApplyTests.js +6 -10
  94. package/tests/helpers/transactionPayloads.mjs +2 -2
  95. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +241 -81
  96. package/tests/unit/messages/network/NetworkMessageDirector.test.js +225 -81
  97. package/tests/unit/network/LegacyNetworkMessageRouter.test.js +54 -0
  98. package/tests/unit/network/ProtocolSession.test.js +127 -0
  99. package/tests/unit/network/networkModule.test.js +4 -1
  100. package/tests/unit/network/services/ConnectionManager.test.js +450 -0
  101. package/tests/unit/network/services/MessageOrchestrator.test.js +445 -0
  102. package/tests/unit/network/services/PendingRequestService.test.js +431 -0
  103. package/tests/unit/network/services/TransactionCommitService.test.js +246 -0
  104. package/tests/unit/network/services/TransactionPoolService.test.js +489 -0
  105. package/tests/unit/network/services/TransactionRateLimiterService.test.js +139 -0
  106. package/tests/unit/network/services/ValidatorHealthCheckService.test.js +115 -0
  107. package/tests/unit/network/services/services.test.js +17 -0
  108. package/tests/unit/network/utils/v1TestUtils.js +153 -0
  109. package/tests/unit/network/v1/NetworkMessageRouterV1.test.js +151 -0
  110. package/tests/unit/network/v1/V1BaseOperation.test.js +356 -0
  111. package/tests/unit/network/v1/V1BroadcastTransactionOperationHandler.test.js +129 -0
  112. package/tests/unit/network/v1/V1BroadcastTransactionRequest.test.js +53 -0
  113. package/tests/unit/network/v1/V1BroadcastTransactionResponse.test.js +512 -0
  114. package/tests/unit/network/v1/V1LivenessRequest.test.js +32 -0
  115. package/tests/unit/network/v1/V1LivenessResponse.test.js +45 -0
  116. package/tests/unit/network/v1/V1ResultCode.test.js +84 -0
  117. package/tests/unit/network/v1/V1ValidationSchema.test.js +13 -0
  118. package/tests/unit/network/v1/connectionPolicies.test.js +49 -0
  119. package/tests/unit/network/v1/handlers/V1BaseOperationHandler.test.js +284 -0
  120. package/tests/unit/network/v1/handlers/V1BroadcastTransactionOperationHandler.test.js +794 -0
  121. package/tests/unit/network/v1/handlers/V1LivenessOperationHandler.test.js +193 -0
  122. package/tests/unit/network/v1/v1.handlers.test.js +15 -0
  123. package/tests/unit/network/v1/v1.test.js +19 -0
  124. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionRequest.test.js +119 -0
  125. package/tests/unit/network/v1/v1ValidationSchema/broadcastTransactionResponse.test.js +136 -0
  126. package/tests/unit/network/v1/v1ValidationSchema/common.test.js +308 -0
  127. package/tests/unit/network/v1/v1ValidationSchema/livenessRequest.test.js +90 -0
  128. package/tests/unit/network/v1/v1ValidationSchema/livenessResponse.test.js +133 -0
  129. package/tests/unit/unit.test.js +2 -2
  130. package/tests/unit/utils/deepEqualApplyPayload/deepEqualApplyPayload.test.js +102 -0
  131. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +4 -3
  132. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +3 -2
  133. package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +3 -2
  134. package/tests/unit/utils/protobuf/operationHelpers.test.js +2 -4
  135. package/tests/unit/utils/type/type.test.js +25 -0
  136. package/tests/unit/utils/utils.test.js +2 -0
  137. package/.github/workflows/acceptance-tests.yml +0 -42
  138. package/.github/workflows/publish.yml +0 -33
  139. package/.github/workflows/unit-tests.yml +0 -40
  140. package/proto/network.proto +0 -74
  141. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +0 -37
  142. package/src/utils/protobuf/network.cjs +0 -840
  143. package/tests/unit/network/ConnectionManager.test.js +0 -191
@@ -1,29 +1,29 @@
1
1
  import {OperationType} from '../../../../../utils/constants.js';
2
- import PartialRoleAccess from "../validators/PartialRoleAccess.js";
3
- import BaseOperationHandler from './base/BaseOperationHandler.js';
2
+ import PartialRoleAccessValidator from "../../shared/validators/PartialRoleAccessValidator.js";
3
+ import BaseStateOperationHandler from './BaseStateOperationHandler.js';
4
4
  import {applyStateMessageFactory} from "../../../../../messages/state/applyStateMessageFactory.js";
5
5
  import {safeEncodeApplyOperation} from "../../../../../utils/protobuf/operationHelpers.js";
6
6
  import {normalizeRoleAccessOperation} from "../../../../../utils/normalizers.js";
7
+ import { publicKeyToAddress } from "../../../../../utils/helpers.js"
8
+ import b4a from "b4a";
7
9
 
8
- class RoleOperationHandler extends BaseOperationHandler {
10
+ class LegacyRoleOperationHandler extends BaseStateOperationHandler {
9
11
  #partialRoleAccessValidator;
10
12
  #wallet;
11
13
  #config;
12
- #txPoolService;
13
14
 
14
15
  /**
15
16
  * @param {State} state
16
17
  * @param {PeerWallet} wallet
17
18
  * @param {TransactionRateLimiterService} rateLimiter
18
19
  * @param {TransactionPoolService} txPoolService
19
- * @param {object} config
20
+ * @param {Config} config
20
21
  **/
21
- constructor(state, wallet, rateLimiter, txPoolService ,config) {
22
+ constructor(state, wallet, rateLimiter, txPoolService, config) {
22
23
  super(state, wallet, rateLimiter, txPoolService, config);
23
24
  this.#wallet = wallet;
24
25
  this.#config = config;
25
- this.#partialRoleAccessValidator = new PartialRoleAccess(state, this.#wallet.address ,this.#config)
26
- this.#txPoolService = txPoolService;
26
+ this.#partialRoleAccessValidator = new PartialRoleAccessValidator(state, this.#wallet.address ,this.#config)
27
27
  }
28
28
 
29
29
  get partialRoleAccessValidator() {
@@ -35,7 +35,8 @@ class RoleOperationHandler extends BaseOperationHandler {
35
35
  const isValid = await this.partialRoleAccessValidator.validate(normalizedPartialRoleAccessPayload)
36
36
  let completePayload = null
37
37
  if (!isValid) {
38
- throw new Error("OperationHandler: partial role access payload validation failed.");
38
+ throw new Error(
39
+ `OperationHandler: partial role access payload validation failed. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`);
39
40
  }
40
41
 
41
42
  switch (normalizedPartialRoleAccessPayload.type) {
@@ -74,15 +75,21 @@ class RoleOperationHandler extends BaseOperationHandler {
74
75
  )
75
76
  break;
76
77
  default:
77
- throw new Error("OperationHandler: Assembling complete role access operation failed due to unsupported operation type.");
78
+ throw new Error(
79
+ `OperationHandler: Assembling complete role access operation failed due to unsupported operation type. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`
80
+ );
78
81
  }
79
82
 
80
83
  if (!completePayload) {
81
- throw new Error("OperationHandler: Assembling complete role access operation failed.");
84
+ throw new Error(
85
+ `OperationHandler: Assembling complete role access operation failed. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`
86
+ );
82
87
  }
83
88
 
84
- this.#txPoolService.addTransaction(safeEncodeApplyOperation(completePayload))
89
+ const encodedOperation = safeEncodeApplyOperation(completePayload);
90
+ const txHash = b4a.toString(completePayload.rao.tx, 'hex');
91
+ this.enqueueTransaction(txHash, encodedOperation);
85
92
  }
86
93
  }
87
94
 
88
- export default RoleOperationHandler;
95
+ export default LegacyRoleOperationHandler;
@@ -1,47 +1,49 @@
1
- import BaseOperationHandler from './base/BaseOperationHandler.js';
1
+ import BaseStateOperationHandler from './BaseStateOperationHandler.js';
2
2
  import {
3
3
  OperationType
4
4
  } from '../../../../../utils/constants.js';
5
- import PartialBootstrapDeployment from "../validators/PartialBootstrapDeployment.js";
6
- import PartialTransaction from "../validators/PartialTransaction.js";
5
+ import PartialBootstrapDeploymentValidator from "../../shared/validators/PartialBootstrapDeploymentValidator.js";
6
+ import PartialTransactionValidator from "../../shared/validators/PartialTransactionValidator.js";
7
7
  import {applyStateMessageFactory} from "../../../../../messages/state/applyStateMessageFactory.js";
8
8
  import {safeEncodeApplyOperation} from "../../../../../utils/protobuf/operationHelpers.js";
9
9
  import {
10
10
  normalizeBootstrapDeploymentOperation,
11
11
  normalizeTransactionOperation
12
12
  } from "../../../../../utils/normalizers.js";
13
+ import b4a from "b4a";
14
+ import {publicKeyToAddress} from "../../../../../utils/helpers.js";
13
15
 
14
16
 
15
- class SubnetworkOperationHandler extends BaseOperationHandler {
17
+ class LegacySubnetworkOperationHandler extends BaseStateOperationHandler {
16
18
  #partialBootstrapDeploymentValidator;
17
19
  #partialTransactionValidator;
18
20
  #config;
19
21
  #wallet;
20
- #txPoolService;
21
22
 
22
23
  /**
23
24
  * @param {State} state
24
25
  * @param {PeerWallet} wallet
25
26
  * @param {TransactionRateLimiterService} rateLimiter
26
27
  * @param {TransactionPoolService} txPoolService
27
- * @param {object} config
28
+ * @param {Config} config
28
29
  **/
29
- constructor( state, wallet, rateLimiter, txPoolService, config) {
30
+ constructor(state, wallet, rateLimiter, txPoolService, config) {
30
31
  super(state, wallet, rateLimiter, txPoolService, config);
31
32
  this.#config = config;
32
33
  this.#wallet = wallet
33
- this.#partialBootstrapDeploymentValidator = new PartialBootstrapDeployment(state, this.#wallet.address, config);
34
- this.#partialTransactionValidator = new PartialTransaction(state, this.#wallet.address, config);
35
- this.#txPoolService = txPoolService;
34
+ this.#partialBootstrapDeploymentValidator = new PartialBootstrapDeploymentValidator(state, this.#wallet.address, config);
35
+ this.#partialTransactionValidator = new PartialTransactionValidator(state, this.#wallet.address, config);
36
36
  }
37
37
 
38
- async handleOperation(payload, connection) {
38
+ async handleOperation(payload, connection) {
39
39
  if (payload.type === OperationType.TX) {
40
40
  await this.#partialTransactionSubHandler(payload, connection);
41
41
  } else if (payload.type === OperationType.BOOTSTRAP_DEPLOYMENT) {
42
42
  await this.#partialBootstrapDeploymentSubHandler(payload, connection);
43
43
  } else {
44
- throw new Error('Unsupported operation type for SubnetworkOperationHandler');
44
+ throw new Error(
45
+ `Unsupported operation type for LegacySubnetworkOperationHandler. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)} `
46
+ );
45
47
  }
46
48
  }
47
49
 
@@ -49,10 +51,12 @@ class SubnetworkOperationHandler extends BaseOperationHandler {
49
51
  const normalizedPayload = normalizeTransactionOperation(payload, this.#config);
50
52
  const isValid = await this.#partialTransactionValidator.validate(normalizedPayload);
51
53
  if (!isValid) {
52
- throw new Error("SubnetworkHandler: Transaction validation failed.");
54
+ throw new Error(`
55
+ SubnetworkHandler: Transaction validation failed. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`
56
+ );
53
57
  }
54
58
 
55
- const completeTransactionOperation = await applyStateMessageFactory(this.#wallet,this.#config)
59
+ const completeTransactionOperation = await applyStateMessageFactory(this.#wallet, this.#config)
56
60
  .buildCompleteTransactionOperationMessage(
57
61
  normalizedPayload.address,
58
62
  normalizedPayload.txo.tx,
@@ -64,14 +68,19 @@ class SubnetworkOperationHandler extends BaseOperationHandler {
64
68
  normalizedPayload.txo.bs,
65
69
  normalizedPayload.txo.mbs
66
70
  )
67
- this.#txPoolService.addTransaction(safeEncodeApplyOperation(completeTransactionOperation));
71
+ const encodedOperation = safeEncodeApplyOperation(completeTransactionOperation);
72
+ const txHash = b4a.toString(normalizedPayload.txo.tx, 'hex');
73
+ this.enqueueTransaction(txHash, encodedOperation);
74
+
68
75
  }
69
76
 
70
77
  async #partialBootstrapDeploymentSubHandler(payload, connection) {
71
78
  const normalizedPayload = normalizeBootstrapDeploymentOperation(payload, this.#config);
72
79
  const isValid = await this.#partialBootstrapDeploymentValidator.validate(normalizedPayload);
73
80
  if (!isValid) {
74
- throw new Error("SubnetworkHandler: Bootstrap deployment validation failed.");
81
+ throw new Error(
82
+ `SubnetworkHandler: Bootstrap deployment validation failed. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`
83
+ );
75
84
  }
76
85
 
77
86
 
@@ -85,9 +94,11 @@ class SubnetworkOperationHandler extends BaseOperationHandler {
85
94
  normalizedPayload.bdo.in,
86
95
  normalizedPayload.bdo.is
87
96
  )
88
- this.#txPoolService.addTransaction(safeEncodeApplyOperation(completeBootstrapDeploymentOperation));
97
+ const encodedOperation = safeEncodeApplyOperation(completeBootstrapDeploymentOperation);
98
+ const txHash = b4a.toString(normalizedPayload.bdo.tx, 'hex');
99
+ this.enqueueTransaction(txHash, encodedOperation);
89
100
 
90
101
  }
91
102
  }
92
103
 
93
- export default SubnetworkOperationHandler;
104
+ export default LegacySubnetworkOperationHandler;
@@ -1,33 +1,36 @@
1
- import BaseOperationHandler from './base/BaseOperationHandler.js';
1
+ import BaseStateOperationHandler from './BaseStateOperationHandler.js';
2
2
  import {OperationType} from '../../../../../utils/constants.js';
3
- import PartialTransfer from "../validators/PartialTransfer.js";
3
+ import PartialTransferValidator from "../../shared/validators/PartialTransferValidator.js";
4
4
  import {normalizeTransferOperation} from "../../../../../utils/normalizers.js"
5
5
  import {applyStateMessageFactory} from "../../../../../messages/state/applyStateMessageFactory.js";
6
6
  import {safeEncodeApplyOperation} from "../../../../../utils/protobuf/operationHelpers.js";
7
+ import b4a from "b4a";
8
+ import {publicKeyToAddress} from "../../../../../utils/helpers.js";
7
9
 
8
- class TransferOperationHandler extends BaseOperationHandler {
10
+ class LegacyTransferOperationHandler extends BaseStateOperationHandler {
9
11
  #partialTransferValidator;
10
12
  #config;
11
13
  #wallet;
12
- #txPoolService;
13
14
 
14
15
  /**
15
16
  * @param {State} state
16
17
  * @param {PeerWallet} wallet
17
18
  * @param {TransactionRateLimiterService} rateLimiter
18
- * @param {object} config
19
+ * @param {TransactionPoolService} txPoolService
20
+ * @param {Config} config
19
21
  **/
20
22
  constructor(state, wallet, rateLimiter, txPoolService, config) {
21
23
  super(state, wallet, rateLimiter, txPoolService, config);
22
24
  this.#config = config;
23
25
  this.#wallet = wallet;
24
- this.#partialTransferValidator = new PartialTransfer(state, this.#wallet.address, this.#config);
25
- this.#txPoolService = txPoolService;
26
+ this.#partialTransferValidator = new PartialTransferValidator(state, this.#wallet.address, this.#config);
26
27
  }
27
28
 
28
29
  async handleOperation(payload, connection) {
29
30
  if (payload.type !== OperationType.TRANSFER) {
30
- throw new Error('Unsupported operation type for TransferOperationHandler');
31
+ throw new Error(
32
+ `Unsupported operation type for LegacyTransferOperationHandler. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`
33
+ );
31
34
  }
32
35
  await this.#handleTransfer(payload, connection);
33
36
  }
@@ -36,7 +39,9 @@ class TransferOperationHandler extends BaseOperationHandler {
36
39
  const normalizedPayload = normalizeTransferOperation(payload, this.#config);
37
40
  const isValid = await this.#partialTransferValidator.validate(normalizedPayload);
38
41
  if (!isValid) {
39
- throw new Error("TransferHandler: Transfer validation failed.");
42
+ throw new Error(
43
+ `TransferHandler: Transfer validation failed. Requested by ${publicKeyToAddress(connection.remotePublicKey, this.#config)}`
44
+ );
40
45
  }
41
46
 
42
47
  const completeTransferOperation = await applyStateMessageFactory(this.#wallet, this.#config)
@@ -49,9 +54,10 @@ class TransferOperationHandler extends BaseOperationHandler {
49
54
  normalizedPayload.tro.am,
50
55
  normalizedPayload.tro.is
51
56
  )
52
-
53
- this.#txPoolService.addTransaction(safeEncodeApplyOperation(completeTransferOperation));
57
+ const encodedOperation = safeEncodeApplyOperation(completeTransferOperation);
58
+ const txHash = b4a.toString(normalizedPayload.tro.tx, 'hex');
59
+ this.enqueueTransaction(txHash, encodedOperation);
54
60
  }
55
61
  }
56
62
 
57
- export default TransferOperationHandler;
63
+ export default LegacyTransferOperationHandler;
@@ -15,7 +15,7 @@ class BaseResponse {
15
15
  *
16
16
  * @param {State} state
17
17
  * @param {PeerWallet} wallet
18
- * @param {object} config
18
+ * @param {Config} config
19
19
  */
20
20
  constructor(state, wallet, config) {
21
21
  this.#state = state;
@@ -0,0 +1,27 @@
1
+ import {V1ProtocolError} from '../../v1/V1ProtocolError.js';
2
+
3
+ /**
4
+ * Shared validator rejection error.
5
+ *
6
+ * Used by shared validators in `src/core/network/protocols/shared/validators/**` so they can
7
+ * reject remote peer input with a stable `resultCode`.
8
+ *
9
+ * Note: this currently extends `V1ProtocolError` so the v1 protocol can consistently treat shared
10
+ * validator rejections as typed protocol errors. Legacy protocol codepaths remain compatible because
11
+ * this is still an `Error` and preserves `.message`.
12
+ *
13
+ * Default `endConnection = true` because shared-validator rejections are typically triggered
14
+ * by invalid/malicious peer payloads and should terminate the peer connection after responding.
15
+ */
16
+ export class SharedValidatorRejectionError extends V1ProtocolError {
17
+ /**
18
+ * @param {number} resultCode Stable rejection reason (a `ResultCode` value).
19
+ * @param {string} message Human-readable error message.
20
+ * @param {boolean} [endConnection=true] Whether the transport should end the connection after responding.
21
+ */
22
+ constructor(resultCode, message, endConnection = true) {
23
+ super(resultCode, message, endConnection);
24
+ }
25
+ }
26
+
27
+ export default SharedValidatorRejectionError;
@@ -1,6 +1,8 @@
1
- import PartialOperation from './base/PartialOperation.js';
1
+ import PartialOperationValidator from './PartialOperationValidator.js';
2
+ import {ResultCode} from "../../../../../utils/constants.js";
3
+ import SharedValidatorRejectionError from '../errors/SharedValidatorRejectionError.js';
2
4
 
3
- class PartialBootstrapDeployment extends PartialOperation {
5
+ class PartialBootstrapDeploymentValidator extends PartialOperationValidator {
4
6
  constructor(state, selfAddress , config) {
5
7
  super(state, selfAddress , config);
6
8
  }
@@ -26,9 +28,12 @@ class PartialBootstrapDeployment extends PartialOperation {
26
28
  async validateBootstrapRegistration(payload) {
27
29
  const bootstrapString = payload.bdo.bs.toString('hex');
28
30
  if (null !== await this.state.getRegisteredBootstrapEntryUnsigned(bootstrapString)) {
29
- throw new Error(`Bootstrap with hash ${bootstrapString} already exists in the state. Bootstrap must be unique.`);
31
+ throw new SharedValidatorRejectionError(
32
+ ResultCode.BOOTSTRAP_ALREADY_EXISTS,
33
+ `Bootstrap with hash ${bootstrapString} already exists in the state. Bootstrap must be unique.`
34
+ );
30
35
  }
31
36
  }
32
37
  }
33
38
 
34
- export default PartialBootstrapDeployment;
39
+ export default PartialBootstrapDeploymentValidator;
@@ -1,18 +1,19 @@
1
1
  import b4a from 'b4a';
2
2
  import PeerWallet from 'trac-wallet';
3
- import Check from '../../../../../../utils/check.js';
4
- import {bufferToAddress} from "../../../../../state/utils/address.js";
5
- import {createMessage} from "../../../../../../utils/buffer.js";
6
- import {OperationType} from "../../../../../../utils/constants.js";
7
- import {bufferToBigInt} from "../../../../../../utils/amountSerialization.js";
8
- import {FEE} from "../../../../../state/utils/transaction.js";
9
- import * as operationsUtils from '../../../../../../utils/applyOperations.js';
3
+ import Check from '../../../../../utils/check.js';
4
+ import {bufferToAddress} from "../../../../state/utils/address.js";
5
+ import {createMessage} from "../../../../../utils/buffer.js";
6
+ import {OperationType, ResultCode} from "../../../../../utils/constants.js";
7
+ import {bufferToBigInt} from "../../../../../utils/amountSerialization.js";
8
+ import {FEE} from "../../../../state/utils/transaction.js";
9
+ import * as operationsUtils from '../../../../../utils/applyOperations.js';
10
+ import SharedValidatorRejectionError from '../errors/SharedValidatorRejectionError.js';
10
11
 
11
12
  const MAX_AMOUNT = BigInt('0xffffffffffffffffffffffffffffffff');
12
13
  const FEE_BIGINT = bufferToBigInt(FEE);
13
14
  const PUBLIC_KEY_LENGTH = 32;
14
15
 
15
- class PartialOperation {
16
+ class PartialOperationValidator {
16
17
  #state;
17
18
  #check;
18
19
  #config
@@ -36,18 +37,18 @@ class PartialOperation {
36
37
  }
37
38
 
38
39
  async validate(payload) {
39
- throw new Error("Method 'validate()' must be implemented.");
40
+ throw new SharedValidatorRejectionError(ResultCode.UNEXPECTED_ERROR, "Method 'validate()' must be implemented.");
40
41
  }
41
42
 
42
43
  isPayloadSchemaValid(payload) {
43
44
  if (!payload || !payload.type) {
44
- throw new Error('Payload or payload type is missing.');
45
+ throw new SharedValidatorRejectionError(ResultCode.TX_INVALID_PAYLOAD, 'Payload or payload type is missing.');
45
46
  }
46
47
 
47
48
  const selectedValidator = this.#selectCheckSchemaValidator(payload.type);
48
49
  const isPayloadValid = selectedValidator(payload);
49
50
  if (!isPayloadValid) {
50
- throw new Error(`Payload is invalid.`);
51
+ throw new SharedValidatorRejectionError(ResultCode.SCHEMA_VALIDATION_FAILED, 'Payload is invalid.');
51
52
  }
52
53
  }
53
54
 
@@ -64,21 +65,24 @@ class PartialOperation {
64
65
  case OperationType.TRANSFER:
65
66
  return this.check.validateTransferOperation.bind(this.check);
66
67
  default:
67
- throw new Error(`Unknown operation type: ${type}`);
68
+ throw new SharedValidatorRejectionError(
69
+ ResultCode.OPERATION_TYPE_UNKNOWN,
70
+ `Unknown operation type: ${type}`
71
+ );
68
72
  }
69
73
  }
70
74
 
71
75
  validateRequesterAddress(payload) {
72
76
  const incomingAddress = bufferToAddress(payload.address, this.#config.addressPrefix);
73
77
  if (!incomingAddress) {
74
- throw new Error('Invalid requesting address in payload.');
78
+ throw new SharedValidatorRejectionError(ResultCode.REQUESTER_ADDRESS_INVALID, 'Invalid requesting address in payload.');
75
79
  }
76
80
 
77
81
  const incomingPublicKey = PeerWallet.decodeBech32mSafe(incomingAddress);
78
82
 
79
83
  // TODO: We can add check if public key belongs to the Ed25519 curve. Validate signature already checks that but it would be amazing to catch it earlier.
80
84
  if (!incomingPublicKey || incomingPublicKey.length !== PUBLIC_KEY_LENGTH) {
81
- throw new Error('Invalid requesting public key in payload.');
85
+ throw new SharedValidatorRejectionError(ResultCode.REQUESTER_PUBLIC_KEY_INVALID, 'Invalid requesting public key in payload.');
82
86
  }
83
87
  }
84
88
 
@@ -127,7 +131,10 @@ class PartialOperation {
127
131
  OperationType.TRANSFER
128
132
  ];
129
133
  default:
130
- throw new Error(`Unknown operation type: ${payload.type}`);
134
+ throw new SharedValidatorRejectionError(
135
+ ResultCode.OPERATION_TYPE_UNKNOWN,
136
+ `Unknown operation type: ${payload.type}`
137
+ );
131
138
  }
132
139
  }
133
140
 
@@ -143,11 +150,14 @@ class PartialOperation {
143
150
  const messageHash = await PeerWallet.blake3(message);
144
151
  const payloadHash = operation.tx;
145
152
  if (!b4a.equals(payloadHash, messageHash)) {
146
- throw new Error('Regenerated transaction does not match incoming transaction in payload.');
153
+ throw new SharedValidatorRejectionError(
154
+ ResultCode.TX_HASH_MISMATCH,
155
+ 'Regenerated transaction does not match incoming transaction in payload.'
156
+ );
147
157
  }
148
158
 
149
159
  if (!PeerWallet.verify(incomingSignature, messageHash, incomingPublicKey)) {
150
- throw new Error('Invalid signature in payload.');
160
+ throw new SharedValidatorRejectionError(ResultCode.TX_SIGNATURE_INVALID, 'Invalid signature in payload.');
151
161
  }
152
162
  }
153
163
 
@@ -158,7 +168,7 @@ class PartialOperation {
158
168
  const incomingTxv = operation.txv
159
169
 
160
170
  if (!b4a.equals(currentTxv, incomingTxv)) {
161
- throw new Error(`Transaction has expired.`);
171
+ throw new SharedValidatorRejectionError(ResultCode.TX_EXPIRED, 'Transaction has expired.');
162
172
  }
163
173
  }
164
174
 
@@ -169,7 +179,10 @@ class PartialOperation {
169
179
  const txHex = tx.toString('hex');
170
180
 
171
181
  if (await this.state.get(txHex) !== null) {
172
- throw new Error(`Transaction with hash ${txHex} already exists in the state.`);
182
+ throw new SharedValidatorRejectionError(
183
+ ResultCode.TX_ALREADY_EXISTS,
184
+ `Transaction with hash ${txHex} already exists in the state.`
185
+ );
173
186
  }
174
187
  }
175
188
 
@@ -180,7 +193,10 @@ class PartialOperation {
180
193
 
181
194
  const condition = va === undefined && vn === undefined && vs === undefined
182
195
  if (!condition) {
183
- throw new Error('Transfer operation must not be completed already (va, vn, vs must be undefined).');
196
+ throw new SharedValidatorRejectionError(
197
+ ResultCode.OPERATION_ALREADY_COMPLETED,
198
+ 'Transfer operation must not be completed already (va, vn, vs must be undefined).'
199
+ );
184
200
  }
185
201
  }
186
202
 
@@ -194,12 +210,12 @@ class PartialOperation {
194
210
  }
195
211
 
196
212
  if (!requesterEntry) {
197
- throw new Error('Requester address not found in state');
213
+ throw new SharedValidatorRejectionError(ResultCode.REQUESTER_NOT_FOUND, 'Requester address not found in state');
198
214
  }
199
215
 
200
216
  const requesterBalance = bufferToBigInt(requesterEntry.balance);
201
217
  if (requesterBalance < FEE_BIGINT) {
202
- throw new Error('Insufficient balance to cover transaction fee.');
218
+ throw new SharedValidatorRejectionError(ResultCode.INSUFFICIENT_FEE_BALANCE, 'Insufficient balance to cover transaction fee.');
203
219
  }
204
220
  }
205
221
 
@@ -209,7 +225,10 @@ class PartialOperation {
209
225
  const operation = payload[operationKey];
210
226
  const bs = operation.bs;
211
227
  if (b4a.equals(this.#config.bootstrap, bs)) {
212
- throw new Error(`External bootstrap is the same as MSB bootstrap: ${bs.toString('hex')}`);
228
+ throw new SharedValidatorRejectionError(
229
+ ResultCode.EXTERNAL_BOOTSTRAP_EQUALS_MSB_BOOTSTRAP,
230
+ `External bootstrap is the same as MSB bootstrap: ${bs.toString('hex')}`
231
+ );
213
232
  }
214
233
  }
215
234
 
@@ -223,10 +242,13 @@ class PartialOperation {
223
242
 
224
243
  const requesterAddress = bufferToAddress(payload.address, this.#config.addressPrefix);
225
244
  if (this.#selfAddress === requesterAddress) {
226
- throw new Error('Requester address cannot be the same as the validator wallet address.');
245
+ throw new SharedValidatorRejectionError(
246
+ ResultCode.SELF_VALIDATION_FORBIDDEN,
247
+ 'Requester address cannot be the same as the validator wallet address.'
248
+ );
227
249
  }
228
250
  }
229
251
 
230
252
  }
231
253
 
232
- export default PartialOperation;
254
+ export default PartialOperationValidator;
@@ -1,10 +1,11 @@
1
1
  import b4a from 'b4a';
2
- import {OperationType} from "../../../../../utils/constants.js";
2
+ import {OperationType, ResultCode} from "../../../../../utils/constants.js";
3
3
  import {bufferToAddress} from "../../../../state/utils/address.js";
4
- import PartialOperation from './base/PartialOperation.js';
4
+ import PartialOperationValidator from './PartialOperationValidator.js';
5
5
  import {bufferToBigInt} from "../../../../../utils/amountSerialization.js";
6
+ import SharedValidatorRejectionError from '../errors/SharedValidatorRejectionError.js';
6
7
 
7
- class PartialRoleAccess extends PartialOperation {
8
+ class PartialRoleAccessValidator extends PartialOperationValidator {
8
9
  #config;
9
10
 
10
11
  constructor(state, selfAddress, config) {
@@ -42,17 +43,26 @@ class PartialRoleAccess extends PartialOperation {
42
43
  const nodeAddress = bufferToAddress(payload.address, this.#config.addressPrefix);
43
44
  const nodeEntry = await this.state.getNodeEntry(nodeAddress);
44
45
  if (!nodeEntry) {
45
- throw new Error(`Node with address ${nodeAddress} entry does not exist.`);
46
+ throw new SharedValidatorRejectionError(
47
+ ResultCode.ROLE_NODE_ENTRY_NOT_FOUND,
48
+ `Node with address ${nodeAddress} entry does not exist.`
49
+ );
46
50
  }
47
51
 
48
52
  const isNodeAlreadyWriter = nodeEntry.isWriter;
49
53
  if (isNodeAlreadyWriter) {
50
- throw new Error(`Node with address ${nodeAddress} is already a writer.`);
54
+ throw new SharedValidatorRejectionError(
55
+ ResultCode.ROLE_NODE_ALREADY_WRITER,
56
+ `Node with address ${nodeAddress} is already a writer.`
57
+ );
51
58
  }
52
59
 
53
60
  const isNodeWhitelisted = nodeEntry.isWhitelisted;
54
61
  if (!isNodeWhitelisted) {
55
- throw new Error(`Node with address ${nodeAddress} is not whitelisted.`);
62
+ throw new SharedValidatorRejectionError(
63
+ ResultCode.ROLE_NODE_NOT_WHITELISTED,
64
+ `Node with address ${nodeAddress} is not whitelisted.`
65
+ );
56
66
  }
57
67
  return;
58
68
 
@@ -60,24 +70,33 @@ class PartialRoleAccess extends PartialOperation {
60
70
  const nodeAddress = bufferToAddress(payload.address, this.#config.addressPrefix);
61
71
  const nodeEntry = await this.state.getNodeEntry(nodeAddress);
62
72
  if (!nodeEntry) {
63
- throw new Error(`Node with address ${nodeAddress} entry does not exist.`);
73
+ throw new SharedValidatorRejectionError(
74
+ ResultCode.ROLE_NODE_ENTRY_NOT_FOUND,
75
+ `Node with address ${nodeAddress} entry does not exist.`
76
+ );
64
77
  }
65
78
 
66
79
  const isAlreadyWriter = nodeEntry.isWriter;
67
80
  if (!isAlreadyWriter) {
68
- throw new Error(`Node with address ${nodeAddress} is not a writer.`);
81
+ throw new SharedValidatorRejectionError(
82
+ ResultCode.ROLE_NODE_NOT_WRITER,
83
+ `Node with address ${nodeAddress} is not a writer.`
84
+ );
69
85
  }
70
86
 
71
87
  const isAlreadyIndexer = nodeEntry.isIndexer;
72
88
  if (isAlreadyIndexer) {
73
- throw new Error(`Node with address ${nodeAddress} is an indexer.`);
89
+ throw new SharedValidatorRejectionError(
90
+ ResultCode.ROLE_NODE_IS_INDEXER,
91
+ `Node with address ${nodeAddress} is an indexer.`
92
+ );
74
93
  }
75
94
  return;
76
95
 
77
96
  } else if (type === OperationType.ADMIN_RECOVERY) {
78
97
  const adminEntry = await this.state.getAdminEntry();
79
98
  if (!adminEntry) {
80
- throw new Error('Admin entry does not exist.');
99
+ throw new SharedValidatorRejectionError(ResultCode.ROLE_ADMIN_ENTRY_MISSING, 'Admin entry does not exist.');
81
100
  }
82
101
 
83
102
  const adminAddressBuffer = payload.address;
@@ -87,20 +106,29 @@ class PartialRoleAccess extends PartialOperation {
87
106
  !b4a.equals(payload.rao.iw, adminEntry.wk)
88
107
  );
89
108
  if (!isRecoveryCase) {
90
- throw new Error(`Node with address ${adminAddress} is not a valid recovery case.`);
109
+ throw new SharedValidatorRejectionError(
110
+ ResultCode.ROLE_INVALID_RECOVERY_CASE,
111
+ `Node with address ${adminAddress} is not a valid recovery case.`
112
+ );
91
113
  }
92
114
 
93
115
  return;
94
116
  }
95
117
 
96
- throw new Error(`Unknown role access operation type: ${type}`);
118
+ throw new SharedValidatorRejectionError(
119
+ ResultCode.ROLE_UNKNOWN_OPERATION,
120
+ `Unknown role access operation type: ${type}`
121
+ );
97
122
  }
98
123
 
99
124
  async validateWriterKey(payload) {
100
125
  const requesterAddress = bufferToAddress(payload.address, this.#config.addressPrefix);
101
126
  const nodeEntry = await this.state.getNodeEntry(requesterAddress);
102
127
  if (!nodeEntry) {
103
- throw new Error(`Node entry not found for address ${requesterAddress}`);
128
+ throw new SharedValidatorRejectionError(
129
+ ResultCode.REQUESTER_NOT_FOUND,
130
+ `Node entry not found for address ${requesterAddress}`
131
+ );
104
132
  }
105
133
 
106
134
  const writerKey = payload.rao.iw.toString('hex');
@@ -111,7 +139,10 @@ class PartialRoleAccess extends PartialOperation {
111
139
  const isOwner = b4a.equals(addressFromRegisteredWritingKey, payload.address);
112
140
 
113
141
  if (!isCurrentWk || !isOwner) {
114
- throw new Error('Invalid writer key: either not owned by requester or different from assigned key');
142
+ throw new SharedValidatorRejectionError(
143
+ ResultCode.ROLE_INVALID_WRITER_KEY,
144
+ 'Invalid writer key: either not owned by requester or different from assigned key'
145
+ );
115
146
  }
116
147
  }
117
148
  }
@@ -126,15 +157,18 @@ class PartialRoleAccess extends PartialOperation {
126
157
  }
127
158
 
128
159
  if (!requesterEntry) {
129
- throw new Error('Requester address not found in state');
160
+ throw new SharedValidatorRejectionError(ResultCode.REQUESTER_NOT_FOUND, 'Requester address not found in state');
130
161
  }
131
162
  const requesterBalance = bufferToBigInt(requesterEntry.balance);
132
163
 
133
164
  const requiredBalance = this.fee * 11n;
134
165
  if (requesterBalance < requiredBalance) {
135
- throw new Error('Insufficient requester balance to cover role access operation FEE.');
166
+ throw new SharedValidatorRejectionError(
167
+ ResultCode.ROLE_INSUFFICIENT_FEE_BALANCE,
168
+ 'Insufficient requester balance to cover role access operation FEE.'
169
+ );
136
170
  }
137
171
  }
138
172
  }
139
173
 
140
- export default PartialRoleAccess;
174
+ export default PartialRoleAccessValidator;