trac-msb 0.2.6 → 0.2.8

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 (146) hide show
  1. package/.github/workflows/publish.yml +9 -16
  2. package/docs/networking-dualstack-plan.md +75 -0
  3. package/docs/networking-layer-redesign.md +155 -0
  4. package/msb.mjs +11 -23
  5. package/package.json +2 -3
  6. package/rpc/{create_server.mjs → create_server.js} +2 -2
  7. package/rpc/{handlers.mjs → handlers.js} +5 -5
  8. package/rpc/routes/{index.mjs → index.js} +1 -1
  9. package/rpc/routes/{v1.mjs → v1.js} +1 -1
  10. package/rpc/{rpc_server.mjs → rpc_server.js} +1 -1
  11. package/rpc/rpc_services.js +4 -4
  12. package/src/config/config.js +137 -0
  13. package/src/config/env.js +61 -0
  14. package/src/core/network/Network.js +131 -72
  15. package/src/core/network/identity/NetworkWalletFactory.js +3 -4
  16. package/src/core/network/messaging/NetworkMessages.js +12 -11
  17. package/src/core/network/messaging/handlers/GetRequestHandler.js +5 -4
  18. package/src/core/network/messaging/handlers/ResponseHandler.js +4 -5
  19. package/src/core/network/messaging/handlers/RoleOperationHandler.js +17 -19
  20. package/src/core/network/messaging/handlers/SubnetworkOperationHandler.js +44 -38
  21. package/src/core/network/messaging/handlers/TransferOperationHandler.js +29 -25
  22. package/src/core/network/messaging/handlers/base/BaseOperationHandler.js +20 -21
  23. package/src/core/network/messaging/routes/NetworkMessageRouter.js +24 -20
  24. package/src/core/network/messaging/validators/AdminResponse.js +2 -2
  25. package/src/core/network/messaging/validators/CustomNodeResponse.js +2 -2
  26. package/src/core/network/messaging/validators/PartialBootstrapDeployment.js +3 -3
  27. package/src/core/network/messaging/validators/PartialRoleAccess.js +15 -12
  28. package/src/core/network/messaging/validators/PartialTransaction.js +9 -10
  29. package/src/core/network/messaging/validators/PartialTransfer.js +10 -7
  30. package/src/core/network/messaging/validators/ValidatorResponse.js +2 -2
  31. package/src/core/network/messaging/validators/base/BaseResponse.js +13 -5
  32. package/src/core/network/messaging/validators/base/PartialOperation.js +37 -21
  33. package/src/core/network/services/ConnectionManager.js +248 -62
  34. package/src/core/network/services/MessageOrchestrator.js +83 -0
  35. package/src/core/network/services/TransactionPoolService.js +9 -8
  36. package/src/core/network/services/ValidatorObserverService.js +95 -34
  37. package/src/core/state/State.js +136 -139
  38. package/src/core/state/utils/address.js +18 -16
  39. package/src/core/state/utils/adminEntry.js +17 -16
  40. package/src/core/state/utils/deploymentEntry.js +15 -15
  41. package/src/core/state/utils/transaction.js +3 -95
  42. package/src/index.js +153 -201
  43. package/src/messages/completeStateMessages/CompleteStateMessageBuilder.js +36 -32
  44. package/src/messages/completeStateMessages/CompleteStateMessageOperations.js +39 -42
  45. package/src/messages/partialStateMessages/PartialStateMessageBuilder.js +20 -20
  46. package/src/messages/partialStateMessages/PartialStateMessageOperations.js +29 -22
  47. package/src/utils/check.js +21 -17
  48. package/src/utils/cliCommands.js +11 -11
  49. package/src/utils/constants.js +2 -9
  50. package/src/utils/fileUtils.js +1 -4
  51. package/src/utils/helpers.js +9 -20
  52. package/src/utils/migrationUtils.js +2 -2
  53. package/src/utils/normalizers.js +10 -9
  54. package/tests/acceptance/v1/account/account.test.mjs +2 -2
  55. package/tests/acceptance/v1/balance/balance.test.mjs +1 -1
  56. package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +11 -2
  57. package/tests/acceptance/v1/rpc.test.mjs +9 -9
  58. package/tests/acceptance/v1/tx/tx.test.mjs +4 -2
  59. package/tests/acceptance/v1/tx-details/tx-details.test.mjs +7 -3
  60. package/tests/fixtures/check.fixtures.js +42 -42
  61. package/tests/fixtures/protobuf.fixtures.js +27 -26
  62. package/tests/helpers/StateNetworkFactory.js +3 -5
  63. package/tests/helpers/autobaseTestHelpers.js +48 -2
  64. package/tests/helpers/config.js +3 -0
  65. package/tests/helpers/setupApplyTests.js +89 -82
  66. package/tests/helpers/transactionPayloads.mjs +26 -12
  67. package/tests/integration/apply/addAdmin/addAdminBasic.test.js +10 -9
  68. package/tests/integration/apply/addAdmin/addAdminRecovery.test.js +20 -19
  69. package/tests/integration/apply/addIndexer.test.js +23 -21
  70. package/tests/integration/apply/addWhitelist.test.js +9 -9
  71. package/tests/integration/apply/addWriter.test.js +33 -32
  72. package/tests/integration/apply/banValidator.test.js +16 -9
  73. package/tests/integration/apply/postTx/invalidSubValues.test.js +4 -4
  74. package/tests/integration/apply/postTx/postTx.test.js +7 -33
  75. package/tests/integration/apply/removeIndexer.test.js +11 -7
  76. package/tests/integration/apply/removeWriter.test.js +20 -19
  77. package/tests/integration/apply/transfer.test.js +18 -16
  78. package/tests/unit/messageOperations/assembleAddIndexerMessage.test.js +2 -2
  79. package/tests/unit/messageOperations/assembleAddWriterMessage.test.js +2 -1
  80. package/tests/unit/messageOperations/assembleAdminMessage.test.js +9 -10
  81. package/tests/unit/messageOperations/assembleBanWriterMessage.test.js +3 -2
  82. package/tests/unit/messageOperations/assemblePostTransaction.test.js +25 -43
  83. package/tests/unit/messageOperations/assembleRemoveIndexerMessage.test.js +2 -2
  84. package/tests/unit/messageOperations/assembleRemoveWriterMessage.test.js +2 -2
  85. package/tests/unit/messageOperations/assembleWhitelistMessages.test.js +5 -4
  86. package/tests/unit/messageOperations/commonsStateMessageOperationsTest.js +4 -3
  87. package/tests/unit/network/ConnectionManager.test.js +41 -70
  88. package/tests/unit/network/NetworkWalletFactory.test.js +14 -14
  89. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +6 -6
  90. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +8 -8
  91. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +6 -5
  92. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +24 -23
  93. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +10 -16
  94. package/tests/unit/state/apply/addWriter/addWriterValidatorRewardScenario.js +2 -1
  95. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +45 -41
  96. package/tests/unit/state/apply/adminRecovery/state.apply.adminRecovery.test.js +3 -7
  97. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +17 -16
  98. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +3 -4
  99. package/tests/unit/state/apply/balanceInitialization/nodeEntryBalanceUpdateFailureScenario.js +2 -1
  100. package/tests/unit/state/apply/banValidator/banValidatorBanAndReWhitelistScenario.js +2 -1
  101. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +23 -25
  102. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentDuplicateRegistrationScenario.js +2 -1
  103. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +19 -18
  104. package/tests/unit/state/apply/common/access-control/adminConsistencyMismatchScenario.js +5 -4
  105. package/tests/unit/state/apply/common/access-control/adminPublicKeyDecodeFailureScenario.js +4 -3
  106. package/tests/unit/state/apply/common/balances/base/requesterBalanceScenarioBase.js +2 -1
  107. package/tests/unit/state/apply/common/commonScenarioHelper.js +3 -4
  108. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +2 -2
  109. package/tests/unit/state/apply/common/payload-structure/invalidHashValidationScenario.js +2 -2
  110. package/tests/unit/state/apply/common/requester/requesterNodeEntryBufferMissingScenario.js +2 -1
  111. package/tests/unit/state/apply/common/requester/requesterNodeEntryDecodeFailureScenario.js +2 -1
  112. package/tests/unit/state/apply/common/validatorConsistency/base/validatorConsistencyScenarioBase.js +2 -1
  113. package/tests/unit/state/apply/common/validatorEntryValidation/base/validatorEntryValidationScenarioBase.js +2 -1
  114. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +11 -10
  115. package/tests/unit/state/apply/removeIndexer/removeIndexerScenarioHelpers.js +6 -5
  116. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +6 -7
  117. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +35 -34
  118. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +44 -43
  119. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +26 -25
  120. package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardScenarioFactory.js +2 -1
  121. package/tests/unit/state/stateModule.test.js +0 -1
  122. package/tests/unit/state/stateTestUtils.js +7 -3
  123. package/tests/unit/state/utils/address.test.js +3 -3
  124. package/tests/unit/state/utils/adminEntry.test.js +10 -9
  125. package/tests/unit/utils/check/adminControlOperation.test.js +3 -3
  126. package/tests/unit/utils/check/balanceInitializationOperation.test.js +2 -2
  127. package/tests/unit/utils/check/bootstrapDeploymentOperation.test.js +2 -3
  128. package/tests/unit/utils/check/common.test.js +7 -6
  129. package/tests/unit/utils/check/coreAdminOperation.test.js +3 -3
  130. package/tests/unit/utils/check/roleAccessOperation.test.js +3 -2
  131. package/tests/unit/utils/check/transactionOperation.test.js +3 -3
  132. package/tests/unit/utils/check/transferOperation.test.js +3 -3
  133. package/tests/unit/utils/fileUtils/readAddressesFromWhitelistFile.test.js +2 -1
  134. package/tests/unit/utils/fileUtils/readBalanceMigrationFile.test.js +2 -1
  135. package/tests/unit/utils/migrationUtils/validateAddressFromIncomingFile.test.js +7 -0
  136. package/tests/unit/utils/utils.test.js +0 -1
  137. package/src/core/state/utils/indexerEntry.js +0 -105
  138. package/src/utils/crypto.js +0 -11
  139. package/tests/unit/state/utils/indexerEntry.test.js +0 -83
  140. package/tests/unit/state/utils/transaction.test.js +0 -97
  141. package/tests/unit/utils/crypto/createHash.test.js +0 -15
  142. /package/rpc/{constants.mjs → constants.js} +0 -0
  143. /package/rpc/{cors.mjs → cors.js} +0 -0
  144. /package/rpc/utils/{confirmedParameter.mjs → confirmedParameter.js} +0 -0
  145. /package/rpc/utils/{helpers.mjs → helpers.js} +0 -0
  146. /package/rpc/utils/{url.mjs → url.js} +0 -0
@@ -5,36 +5,29 @@ on:
5
5
  types: [published]
6
6
 
7
7
  permissions:
8
+ id-token: write # Required for OIDC
8
9
  contents: read
9
10
 
10
11
  jobs:
11
- publish-npm:
12
+ publish:
12
13
  runs-on: ubuntu-latest
13
-
14
14
  steps:
15
- - name: Checkout repo
15
+ - name: Checkout release tag
16
16
  uses: actions/checkout@v4
17
+ with:
18
+ ref: ${{ github.event.release.tag_name }}
17
19
 
18
20
  - name: Use Node.js
19
21
  uses: actions/setup-node@v4
20
22
  with:
21
23
  node-version: '24'
22
24
  registry-url: 'https://registry.npmjs.org'
23
-
24
- - name: Set package.json version from tag
25
- run: |
26
- TAG="${GITHUB_REF#refs/tags/}"
27
- VERSION="${TAG#v}"
28
- echo "Version from tag: $VERSION"
29
- npm version "$VERSION" --no-git-tag-version
30
-
31
25
  - name: Install dependencies
32
26
  run: npm ci
33
27
 
34
- - name: Run unit tests
35
- run: npm run test:unit:all
28
+ # unit tests are temporarily disabled because they lost stability on GH runners.
29
+ #- name: Run unit tests
30
+ # run: npm run test:unit:all
36
31
 
37
32
  - name: Publish to npm
38
- env:
39
- NODE_AUTH_TOKEN: ${{ secrets.NPM_MSB_PUBLISH_TOKEN }}
40
- run: npm publish
33
+ run: npm publish --access public
@@ -0,0 +1,75 @@
1
+ # Dual-stack plan with design patterns (legacy JSON + network/v1)
2
+
3
+ This is a design plan only (no implementation yet).
4
+
5
+ ## Project map (current)
6
+ - `src/core/network/Network.js`: facade for swarm, services, and message setup.
7
+ - `src/core/network/protocols/NetworkMessages.js`: Protomux wiring for legacy JSON.
8
+ - `src/core/network/protocols/legacy/`: legacy router + handlers + validators.
9
+ - `src/core/network/protocols/shared/`: shared operation handlers + validators.
10
+ - `src/core/network/services/ConnectionManager.js`: validator pool and messaging.
11
+ - `src/core/network/services/MessageOrchestrator.js`: legacy broadcast heuristic.
12
+ - `src/messages/network/v1/`: builder/director/factory for v1 messages.
13
+ - `proto/network.proto` + `src/utils/protobuf/network.cjs`: v1 schema/codec.
14
+
15
+ ## Design patterns to use (and where)
16
+ - Strategy + Adapter: protocol-specific adapters per version (`legacy`, `v1`, future `v2`).
17
+ - Registry + Factory: centralized list of protocol descriptors, created once.
18
+ - Chain of Responsibility: routing table of predicates -> handlers (replace long if/else).
19
+ - Template Method: already in `BaseOperationHandler`, keep as shared entry point.
20
+ - Command: operation handlers as commands dispatched by a protocol router.
21
+ - State Machine: handshake + capability negotiation for `network/v1`.
22
+ - Mediator: `MessageOrchestrator` coordinates `ConnectionManager` and state.
23
+ - Observer: emit connection/protocol events to interested services.
24
+ - Decorator: wrap handlers with rate limit, logging, metrics.
25
+ - Null Object: no-op messenger to avoid null checks in send paths.
26
+
27
+ ## Target architecture (pattern-oriented)
28
+ 1. ProtocolAdapter interface (Strategy + Adapter)
29
+ - Shape: `id`, `protocolName`, `encoding`, `router`, `handshake`, `encode`, `decode`.
30
+ - `LegacyProtocolAdapter` wraps `legacy/NetworkMessageRouter` with `c.json`.
31
+ - `V1ProtocolAdapter` wraps protobuf decode/encode and a v1 router.
32
+
33
+ 2. ProtocolRegistry (Registry + Factory)
34
+ - Single source of enabled protocols: `[legacyAdapter, v1Adapter]`.
35
+ - Owns protocol names (`legacy` channel from config, `network/v1`).
36
+ - Exposed to `NetworkMessages` for channel creation.
37
+
38
+ 3. ProtocolSession per connection (Facade + Null Object)
39
+ - `session.messengers.{legacy,v1}` with a `NullMessenger` fallback.
40
+ - `session.protocols` is a `Set` of negotiated protocols.
41
+ - Stored in `ConnectionManager` alongside peer metadata.
42
+
43
+ 4. Router refactor (Chain of Responsibility + Command)
44
+ - `legacy` router: table of `canHandle(message)` + `handler.execute()`.
45
+ - `v1` router: map `MessageType -> handler`.
46
+ - Shared operation handlers remain in `shared/handlers/`.
47
+
48
+ 5. Handshake state machine (State)
49
+ - Per-connection state: `init -> awaiting_ack -> open -> failed`.
50
+ - On success, mark `session.protocols.add('v1')`.
51
+ - On timeout, keep `legacy` only (no disconnect).
52
+
53
+ 6. Orchestrator protocol selection (Strategy)
54
+ - `ProtocolSelector`: choose v1 if peer supports it, else legacy.
55
+ - v1 ACK uses response codes; legacy uses state-based heuristic.
56
+ - Optional strict verify for v1: wait for append before success.
57
+
58
+ 7. Validators layout (Template Method + Adapter)
59
+ - Keep shared validators in `shared/validators/`.
60
+ - Protocol-specific validators live under `legacy/validators/` and `v1/validators/`.
61
+ - Protocol adapter decides which validator set to use.
62
+
63
+ ## Step-by-step plan
64
+ 1. Add `ProtocolRegistry` and `ProtocolAdapter` definitions (design only).
65
+ 2. Update `NetworkMessages` to iterate registry and open one channel per protocol.
66
+ 3. Introduce `ProtocolSession` and store it in `ConnectionManager`.
67
+ 4. Implement v1 router map based on `proto/network.proto` types.
68
+ 5. Add v1 handshake state machine and capability negotiation.
69
+ 6. Add `ProtocolSelector` in `MessageOrchestrator` with legacy fallback.
70
+ 7. Align validators: shared core + protocol-specific wrappers.
71
+ 8. Tests (future): legacy-only peers, v1 negotiation, mixed cluster.
72
+
73
+ ## Notes for v2
74
+ - Add `network/v2` adapter + protobuf schema.
75
+ - Registry chooses the highest common protocol per peer.
@@ -0,0 +1,155 @@
1
+ # Networking layer redesign (draft)
2
+
3
+ Cel: uporządkować warstwę sieciową w `src/core/network`, wprowadzić binarne wiadomości (Protobuf + Protomux), system ACK/heartbeat, zunifikowane kody błędów oraz nowy endpoint RPC, bez ingerencji w istniejącą implementację (ten dokument jest tylko projektem).
4
+
5
+ ## Założenia
6
+ - Komunikacja p2p przez Hyperswarm zostaje, ale wiadomości idą binarnie (Protobuf) na kanałach Protomux.
7
+ - Każdy typ wiadomości zwraca `Result` z kodem sukcesu/błędu (własne kody, nie HTTP).
8
+ - Heartbeat/liveness co ~10s z losowym jitterem; łatwa konfiguracja z `config`/`constants`.
9
+ - Handshake z zachowaniem PKI, ale mniej round-tripów i z ochroną DoS.
10
+ - Modularna architektura (klasy/serwisy), separacja transportu, protokołu, zdrowia, bezpieczeństwa i RPC.
11
+
12
+ ## Architektura warstw
13
+ - **Transport (HyperswarmTransport)**: zarządza swarmem, tworzy kanały Protomux (binary encoding), egzekwuje limity połączeń.
14
+ - **Protocol (EnvelopeCodec, Protobuf schemas)**: wspólny `Envelope` + `Result`; mapowanie typów wiadomości; validacja długości/pól przed dekodowaniem.
15
+ - **Handshake (ValidatorHandshake)**: szybki trójfazowy handshake z wyzwaniem nonce + podpis, negocjacja wersji/capabilities, limity prób.
16
+ - **Health (HeartbeatScheduler + LivenessStore)**: wysyła heartbeat do wszystkich znanych peerów (z konekcji lub z listy validatorów), aktualizuje liveness, generuje zdarzenia dla RPC.
17
+ - **Security (DosGuard)**: rate limit per peer/endpoint, maks. rozmiary payloadów, kolejki handshake, banlista czasowa.
18
+ - **RPC adapter (ValidatorStatusController)**: odczytuje `LivenessStore` i zwraca status walidatora.
19
+
20
+ ## Proponowane moduły / pliki (kierunek, bez implementacji)
21
+ - `src/core/network/transport/HyperswarmTransport.js`: opakowanie na Hyperswarm + Protomux, jednolity interfejs kanałów binarnych.
22
+ - `src/core/network/protocol/EnvelopeCodec.js`: enkodowanie/dekodowanie Protobuf, walidacja schematu, mapowanie `MessageType` -> handler.
23
+ - `src/core/network/protocol/error-codes.js`: stałe kodów (`ErrorCode` enum) współdzielone między node'ami.
24
+ - `src/core/network/handshake/ValidatorHandshake.js`: stan maszyny handshake, ochrona DoS, fallbacki wersji.
25
+ - `src/core/network/health/HeartbeatScheduler.js`: harmonogram heartbeatów + jitter, retry/backoff.
26
+ - `src/core/network/health/LivenessStore.js`: przechowuje `lastSeen`, `missed`, `state=active/inactive/banned`.
27
+ - `src/core/network/security/DosGuard.js`: token bucket + ograniczenia równoległych handshake, limit rozmiaru wiadomości.
28
+ - `src/core/network/rpc/ValidatorStatusController.js`: integracja z istniejącym RPC routerem (`/validators/:address/status`).
29
+
30
+ ## Szkic Protobuf (draft, do dodania jako nowy `.proto`)
31
+ ```proto
32
+ syntax = "proto3";
33
+ package network.v1;
34
+
35
+ enum MessageType {
36
+ UNKNOWN = 0;
37
+ HANDSHAKE_INIT = 1;
38
+ HANDSHAKE_ACK = 2;
39
+ HEARTBEAT_PING = 3;
40
+ HEARTBEAT_ACK = 4;
41
+ VALIDATOR_STATUS_REQUEST = 5;
42
+ VALIDATOR_STATUS_RESPONSE = 6;
43
+ // ...pozostałe typy operacyjne (transfer/role) mogą być zmapowane później
44
+ }
45
+
46
+ enum ResultCode {
47
+ RESULT_OK = 0;
48
+ RESULT_RETRY = 1; // używane do łagodnego backoffu
49
+ RESULT_INVALID_PAYLOAD = 2;
50
+ RESULT_UNAUTHORIZED = 3;
51
+ RESULT_UNAUTHENTICATED = 4;
52
+ RESULT_UNSUPPORTED_VERSION = 5;
53
+ RESULT_RATE_LIMITED = 6;
54
+ RESULT_TOO_MANY_CONNECTIONS = 7;
55
+ RESULT_BUSY = 8;
56
+ RESULT_TIMEOUT = 9;
57
+ RESULT_PEER_BANNED = 10;
58
+ RESULT_SIGNATURE_INVALID = 11;
59
+ RESULT_NOT_VALIDATOR = 12;
60
+ RESULT_INTERNAL = 13;
61
+ }
62
+
63
+ message Result {
64
+ ResultCode code = 1;
65
+ string reason = 2;
66
+ }
67
+
68
+ message Envelope {
69
+ uint64 session_id = 1;
70
+ MessageType type = 3;
71
+ bytes payload = 4; // zakodowany message specyficzny dla type
72
+ Result result = 5; // dla odpowiedzi/ACK
73
+ uint64 timestamp = 6;
74
+ }
75
+
76
+ message HandshakeInit {
77
+ bytes node_pk = 1;
78
+ uint32 version = 2;
79
+ bytes nonce = 3;
80
+ bytes signature = 4; // sig(nonce || channel || version)
81
+ repeated string capabilities = 5; // np. ["binary", "hb:v1"]
82
+ }
83
+
84
+ message HandshakeAck {
85
+ bytes nonce = 1; // nonce od inicjatora
86
+ bytes signature = 2; // sig(nonce || channel || version)
87
+ uint32 version = 3; // negocjowany
88
+ Result result = 4; // ERROR_UNSUPPORTED_VERSION itd.
89
+ }
90
+
91
+ message HeartbeatPing {
92
+ bytes node_pk = 1;
93
+ uint64 sequence = 2;
94
+ uint64 timestamp_ms = 3;
95
+ }
96
+
97
+ message HeartbeatAck {
98
+ uint64 sequence = 1;
99
+ Result result = 2; // ERROR_OK lub powód degradacji
100
+ uint64 timestamp_ms = 3;
101
+ }
102
+
103
+ message ValidatorStatusRequest {
104
+ bytes validator_pk = 1;
105
+ }
106
+
107
+ message ValidatorStatusResponse {
108
+ Result result = 1;
109
+ bool active = 2;
110
+ uint64 last_seen_ms = 3;
111
+ uint32 missed_heartbeats = 4;
112
+ }
113
+ ```
114
+
115
+ ## Handshake (przykładowy przebieg)
116
+ 1. **Init**: inicjator wysyła `HandshakeInit` na dedykowany kanał Protomux `protocol:handshake/v1` (binary). Dodajemy `DosGuard` limitujący równoległe init-y i weryfikujący rozmiar.
117
+ 2. **Ack**: odbiorca weryfikuje podpis i wersję, odpowiada `HandshakeAck` (sukces lub błąd). Przy sukcesie odkłada połączenie do `ConnectionManager` razem z metadanymi (rola, capabilities).
118
+ 3. **Promotion**: dopiero po sukcesie handshake kanały operacyjne/heartbeat są otwierane; w przeciwnym razie połączenie jest zamykane i peer może trafić na banlistę czasową.
119
+
120
+ Minimalizuje to round-trip (1.5 RTT), utrzymuje PKI (nonce + podpis) i pozwala na rollback do niższej wersji jeśli różne węzły.
121
+
122
+ ## Heartbeat / liveness
123
+ - **Interwał**: domyślnie 10s ± losowy jitter (np. 0–2s) by uniknąć szpilek. Parametryzacja w constants/config.
124
+ - **Zakres**: każda znana para (aktywny validator + inne role) dostaje `HeartbeatPing`. Po 3 nieudanych ACK (lub braków w oknie czasowym) peer przechodzi w `inactive`, po większej liczbie w `unhealthy`.
125
+ - **Integracja**: `HeartbeatScheduler` korzysta z `ConnectionManager` do nadawania; `LivenessStore` aktualizuje się na podstawie `HeartbeatAck` i czasu.
126
+ - **Odpowiedź**: `HeartbeatAck.result.code` pozwala zakomunikować throttle (`ERROR_RATE_LIMITED`) lub przepełnienie (`ERROR_BUSY`), co zmniejsza logowanie błędów.
127
+
128
+ ## Nowy endpoint RPC
129
+ - Ścieżka: `GET /validators/:address/status` w warstwie RPC.
130
+ - Źródło prawdy: `LivenessStore` (ostatni heartbeat, liczba nieodebranych, stan). `active=true` gdy ostatni heartbeat < 3*interval i brak poważnych błędów.
131
+ - Payload (200): `{ address, active, lastSeenMs, missed, reason }`. Brak zależności od kodów HTTP dla logiki aplikacyjnej; HTTP 404 tylko gdy adres niepoprawny (opcjonalnie 200 z `active:false` i `result.code=ERROR_NOT_VALIDATOR`).
132
+ - Przygotować kontrakt dla klientów (CLI/SDK) by interpretowali `Result`.
133
+
134
+ ## Ochrona przed DoS
135
+ - **Rate limiting**: token bucket per peer na handshake i heartbeat, limity równoległych handshake w `DosGuard`.
136
+ - **Rozmiary**: twarde limity na rozmiar pakietu Protobuf przed deserializacją; odrzucenie z `ERROR_INVALID_PAYLOAD`.
137
+ - **Kolejki**: ograniczona kolejka zadań handshake/heartbeat; przy przepełnieniu `ERROR_BUSY` w ACK i zamknięcie połączenia.
138
+ - **Bany czasowe**: peer wysyłający zbyt wiele złych podpisów lub oversize payloadów trafia na banlistę na X minut.
139
+ - **Separacja kanałów**: osobne kanały Protomux (handshake, control/heartbeat, operations) by odciąć hałas od głównych operacji.
140
+
141
+ ## Kody błędów (wspólny enum)
142
+ - Sukces: `ERROR_OK`.
143
+ - Autoryzacja: `ERROR_UNAUTHENTICATED`, `ERROR_UNAUTHORIZED`, `ERROR_SIGNATURE_INVALID`, `ERROR_NOT_VALIDATOR`.
144
+ - Dostępność: `ERROR_RATE_LIMITED`, `ERROR_BUSY`, `ERROR_TOO_MANY_CONNECTIONS`, `ERROR_PEER_BANNED`.
145
+ - Protokół: `ERROR_INVALID_PAYLOAD`, `ERROR_UNSUPPORTED_VERSION`, `ERROR_TIMEOUT`.
146
+ - Ogólne: `ERROR_RETRY` (miękkie), `ERROR_INTERNAL`.
147
+
148
+ ## Plan wdrożenia (wysoki poziom)
149
+ 1. Dodać schemat `.proto` z Envelope/Result/Handshake/Heartbeat/Status.
150
+ 2. Wydzielić `transport/` + `protocol/` + `handshake/` + `health/` + `security/` katalogi i przenieść istniejące odpowiedniki (`NetworkMessages`, router, itp.) stopniowo.
151
+ 3. Zaimplementować `EnvelopeCodec` w Protomux (binary), przełączyć routing na typy wiadomości zamiast JSON.
152
+ 4. Zaimplementować handshake (1.5 RTT) i dołożyć `DosGuard` oraz limity na poziomie Protomux channel.
153
+ 5. Dodać HeartbeatScheduler + LivenessStore, zasilić je danymi do RPC.
154
+ 6. Dodać endpoint RPC `/validators/:address/status`, mapujący dane z LivenessStore.
155
+ 7. Testy: symulacja DoS (próby handshake > limit, oversize payload), degradacja heartbeat, negocjacja wersji i kompatybilności.
package/msb.mjs CHANGED
@@ -1,33 +1,22 @@
1
1
  import { MainSettlementBus } from './src/index.js';
2
+ import { startRpcServer } from './rpc/rpc_server.js';
3
+ import { createConfig, ENV } from './src/config/env.js';
2
4
 
3
5
  const pearApp = typeof Pear !== 'undefined' ? (Pear.app ?? Pear.config) : undefined;
4
6
  const runtimeArgs = typeof process !== 'undefined' ? process.argv.slice(2) : [];
5
7
  const args = pearApp?.args ?? runtimeArgs;
6
8
  const runRpc = args.includes('--rpc');
9
+ const storeName = pearApp?.args?.[0] ?? runtimeArgs[0]
7
10
 
8
- const opts = {
9
- stores_directory: 'stores/',
10
- store_name: pearApp?.args?.[0] ?? runtimeArgs[0],
11
- bootstrap: 'acbc3a4344d3a804101d40e53db1dda82b767646425af73599d4cd6577d69685',
12
- channel: '0000trac0network0msb0mainnet0000',
13
- enable_role_requester: false,
14
- enable_wallet: true,
15
- enable_validator_observer: true,
16
- enable_interactive_mode: true,
17
- disable_rate_limit: false,
18
- enable_tx_apply_logs: false,
19
- enable_error_apply_logs: false,
20
- };
11
+ const rpc = {
12
+ storeName: pearApp?.args?.[0] ?? runtimeArgs[0],
13
+ enableWallet: false,
14
+ enableInteractiveMode: false
15
+ }
21
16
 
22
- const rpc_opts = {
23
- ...opts,
24
- enable_tx_apply_logs: false,
25
- enable_error_apply_logs: false,
26
- enable_wallet: false,
27
- enable_interactive_mode: false,
28
- };
17
+ const options = args.includes('--rpc') ? rpc : { storeName }
29
18
 
30
- const msb = new MainSettlementBus(runRpc ? rpc_opts : opts);
19
+ const msb = new MainSettlementBus(createConfig(ENV.MAINNET, options));
31
20
 
32
21
  msb.ready().then(async () => {
33
22
  if (runRpc) {
@@ -36,10 +25,9 @@ msb.ready().then(async () => {
36
25
  const port = (portIndex !== -1 && args[portIndex + 1]) ? parseInt(args[portIndex + 1], 10) : 5000;
37
26
  const hostIndex = args.indexOf('--host');
38
27
  const host = (hostIndex !== -1 && args[hostIndex + 1]) ? args[hostIndex + 1] : 'localhost';
39
-
40
- const {startRpcServer} = await import('./rpc/rpc_server.mjs');
41
28
  startRpcServer(msb, host, port);
42
29
  } else {
30
+ console.log('RPC server will not be started.');
43
31
  msb.interactiveMode();
44
32
  }
45
33
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "trac-msb",
3
3
  "main": "msb.mjs",
4
- "version": "0.2.6",
4
+ "version": "0.2.8",
5
5
  "pear": {
6
6
  "name": "trac-msb",
7
7
  "type": "terminal"
@@ -25,7 +25,6 @@
25
25
  "test:unit:bare:cov": "brittle-bare -c -t 60000 tests/unit/unit.test.js"
26
26
  },
27
27
  "dependencies": {
28
- "@tracsystems/blake3": "0.0.13",
29
28
  "autobase": "7.20.1",
30
29
  "b4a": "1.6.7",
31
30
  "bare-crypto": "1.12.0",
@@ -50,7 +49,7 @@
50
49
  "protomux-wakeup": "2.4.0",
51
50
  "readline": "npm:bare-node-readline",
52
51
  "ready-resource": "1.1.2",
53
- "trac-wallet": "0.0.43-msb-r2.8",
52
+ "trac-wallet": "0.0.43-msb-r2.9",
54
53
  "tty": "npm:bare-node-tty"
55
54
  },
56
55
  "devDependencies": {
@@ -1,7 +1,7 @@
1
1
  // rpc_server.mjs
2
2
  import http from 'http'
3
- import { applyCors } from './cors.mjs';
4
- import { routes } from './routes/index.mjs';
3
+ import { applyCors } from './cors.js';
4
+ import { routes } from './routes/index.js';
5
5
 
6
6
  export const createServer = (msbInstance) => {
7
7
  const server = http.createServer({}, async (req, res) => {
@@ -1,6 +1,6 @@
1
- import { decodeBase64Payload, isBase64, sanitizeBulkPayloadsRequestBody, sanitizeTransferPayload, validatePayloadStructure } from "./utils/helpers.mjs"
2
- import { MAX_SIGNED_LENGTH, ZERO_WK } from "./constants.mjs";
3
- import { buildRequestUrl } from "./utils/url.mjs";
1
+ import { decodeBase64Payload, isBase64, sanitizeBulkPayloadsRequestBody, sanitizeTransferPayload, validatePayloadStructure } from "./utils/helpers.js"
2
+ import { MAX_SIGNED_LENGTH, ZERO_WK } from "./constants.js";
3
+ import { buildRequestUrl } from "./utils/url.js";
4
4
  import { isHexString } from "../src/utils/helpers.js";
5
5
  import {
6
6
  getBalance,
@@ -16,7 +16,7 @@ import {
16
16
  } from "./rpc_services.js";
17
17
  import { bufferToBigInt, licenseBufferToBigInt } from "../src/utils/amountSerialization.js";
18
18
  import { isAddressValid } from "../src/core/state/utils/address.js";
19
- import { getConfirmedParameter } from "./utils/confirmedParameter.mjs";
19
+ import { getConfirmedParameter } from "./utils/confirmedParameter.js";
20
20
 
21
21
  export async function handleBalance({ req, respond, msbInstance }) {
22
22
  const url = buildRequestUrl(req);
@@ -258,7 +258,7 @@ export async function handleAccountDetails({ msbInstance, respond, req }) {
258
258
  return respond(400, { error: 'Parameter "confirmed" must be exactly "true" or "false"' });
259
259
  }
260
260
 
261
- if (!isAddressValid(address)) {
261
+ if (!isAddressValid(address, msbInstance.config.addressPrefix)) {
262
262
  return respond(400, { error: "Invalid account address format" });
263
263
  }
264
264
 
@@ -1,4 +1,4 @@
1
- import { v1Routes } from './v1.mjs';
1
+ import { v1Routes } from './v1.js';
2
2
 
3
3
  // future version
4
4
  // import { v2Routes } from './v2.mjs';
@@ -10,7 +10,7 @@ import {
10
10
  handleFetchBulkTxPayloads,
11
11
  handleTransactionExtendedDetails,
12
12
  handleAccountDetails
13
- } from '../handlers.mjs';
13
+ } from '../handlers.js';
14
14
 
15
15
  export const v1Routes = [
16
16
  { method: 'GET', path: '/balance', handler: handleBalance },
@@ -1,4 +1,4 @@
1
- import { createServer } from "./create_server.mjs";
1
+ import { createServer } from "./create_server.js";
2
2
 
3
3
  // Called by msb.mjs file
4
4
  export function startRpcServer(msbInstance, host, port) {
@@ -51,7 +51,7 @@ export async function getTxDetails(msbInstance, hash) {
51
51
  return null;
52
52
  }
53
53
 
54
- return normalizeDecodedPayloadForJson(rawPayload.decoded);
54
+ return normalizeDecodedPayloadForJson(rawPayload.decoded, msbInstance.config);
55
55
  }
56
56
 
57
57
  export async function fetchBulkTxPayloads(msbInstance, hashes) {
@@ -73,7 +73,7 @@ export async function fetchBulkTxPayloads(msbInstance, hashes) {
73
73
  if (result === null || result === undefined) {
74
74
  res.missing.push(hash);
75
75
  } else {
76
- const decodedResult = normalizeDecodedPayloadForJson(result.decoded);
76
+ const decodedResult = normalizeDecodedPayloadForJson(result.decoded, msbInstance.config);
77
77
  res.results.push({ hash, payload: decodedResult });
78
78
  }
79
79
  });
@@ -93,7 +93,7 @@ export async function getExtendedTxDetails(msbInstance, hash, confirmed) {
93
93
  if (confirmedLength === null) {
94
94
  throw new Error(`No confirmed length found for tx hash: ${hash} in confirmed mode`);
95
95
  }
96
- const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, true);
96
+ const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, msbInstance.config);
97
97
  const feeBuffer = state.getFee();
98
98
  return {
99
99
  txDetails: normalizedPayload,
@@ -107,7 +107,7 @@ export async function getExtendedTxDetails(msbInstance, hash, confirmed) {
107
107
  throw new Error(`No payload found for tx hash: ${hash}`);
108
108
  }
109
109
 
110
- const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, true);
110
+ const normalizedPayload = normalizeDecodedPayloadForJson(rawPayload.decoded, msbInstance.config);
111
111
  const length = await state.getTransactionConfirmedLength(hash);
112
112
  if (length === null) {
113
113
  return {
@@ -0,0 +1,137 @@
1
+ import b4a from 'b4a'
2
+
3
+ export class Config {
4
+ #options
5
+ #config
6
+ #bootstrap
7
+ #channel
8
+
9
+ constructor(options = {}, config = {}) {
10
+ this.#validate(options, config)
11
+ this.#options = options
12
+ this.#config = config
13
+ this.#bootstrap = b4a.from(this.#options.bootstrap || this.#config.bootstrap, 'hex')
14
+ // Ensure a 32-byte channel buffer (repeat-fill from string/Buffer if provided)
15
+ this.#channel = b4a.alloc(32).fill(this.#options.channel || this.#config.channel)
16
+ }
17
+
18
+ get addressLength() {
19
+ return this.#config.addressLength
20
+ }
21
+
22
+ get addressPrefix() {
23
+ return this.#config.addressPrefix
24
+ }
25
+
26
+ get addressPrefixLength() {
27
+ return this.addressPrefix.length
28
+ }
29
+
30
+ get bech32mHrpLength() {
31
+ return this.#config.bech32mHrpLength
32
+ }
33
+
34
+ get bootstrap() {
35
+ return this.#bootstrap
36
+ }
37
+
38
+ get channel() {
39
+ return this.#channel
40
+ }
41
+
42
+ get dhtBootstrap() {
43
+ if (this.#isOverriden('dhtBootstrap')) return this.#options.dhtBootstrap
44
+ return this.#config.dhtBootstrap
45
+ }
46
+
47
+ get disableRateLimit() {
48
+ if (this.#isOverriden('disableRateLimit')) return !!this.#options.disableRateLimit
49
+ return !!this.#config.disableRateLimit
50
+ }
51
+
52
+ get enableErrorApplyLogs() {
53
+ if (this.#isOverriden('enableErrorApplyLogs')) return !!this.#options.enableErrorApplyLogs
54
+ return !!this.#config.enableErrorApplyLogs
55
+ }
56
+
57
+ get enableInteractiveMode() {
58
+ if (this.#isOverriden('enableInteractiveMode')) return this.#options.enableInteractiveMode !== false
59
+ return !!this.#config.enableInteractiveMode
60
+ }
61
+
62
+ get enableRoleRequester() {
63
+ if (this.#isOverriden('enableRoleRequester')) return !!this.#options.enableRoleRequester
64
+ return !!this.#config.enableRoleRequester
65
+ }
66
+
67
+ get enableValidatorObserver() {
68
+ if (this.#isOverriden('enableValidatorObserver')) return !!this.#options.enableValidatorObserver
69
+ return !!this.#config.enableValidatorObserver
70
+ }
71
+
72
+ get enableTxApplyLogs() {
73
+ if (this.#isOverriden('enableTxApplyLogs')) return !!this.#options.enableTxApplyLogs
74
+ return !!this.#config.enableTxApplyLogs
75
+ }
76
+
77
+ get enableWallet() {
78
+ if (this.#isOverriden('enableWallet')) return this.#options.enableWallet !== false
79
+ return !!this.#config.enableWallet
80
+ }
81
+
82
+ get isAdminMode() {
83
+ return this.#options.storeName === 'admin'
84
+ }
85
+
86
+ get keyPairPath() {
87
+ return `${this.storesFullPath}/db/keypair.json`
88
+ }
89
+
90
+ get maxRetries() {
91
+ if (this.#isOverriden('maxRetries')) return this.#options.maxRetries
92
+ return this.#config.maxRetries
93
+ }
94
+
95
+ get maxValidators() {
96
+ if (this.#isOverriden('maxValidators')) return this.#options.maxValidators
97
+ return this.#config.maxValidators
98
+ }
99
+
100
+ get networkId() {
101
+ return this.#config.networkId
102
+ }
103
+
104
+ get storesDirectory() {
105
+ if (this.#isOverriden('storesDirectory')) return this.#options.storesDirectory
106
+ return this.#config.storesDirectory
107
+ }
108
+
109
+ get storesFullPath() {
110
+ return `${this.storesDirectory}${this.#options.storeName}`
111
+ }
112
+
113
+ get messageThreshold() {
114
+ return this.#config.messageThreshold
115
+ }
116
+
117
+ get messageValidatorRetryDelay() {
118
+ return this.#config.messageValidatorRetryDelay
119
+ }
120
+
121
+ get messageValidatorResponseTimeout() {
122
+ return this.#config.messageValidatorResponseTimeout
123
+ }
124
+
125
+ // Most of these properties are boolean
126
+ #isOverriden(prop) {
127
+ return this.#options.hasOwnProperty(prop)
128
+ }
129
+
130
+ #validate(options, config) {
131
+ if (!options.channel && !config.channel) {
132
+ throw new Error(
133
+ "MainSettlementBus: Channel is required. Application cannot start without channel."
134
+ );
135
+ }
136
+ }
137
+ }
@@ -0,0 +1,61 @@
1
+ import { TRAC_NETWORK_MSB_MAINNET_PREFIX } from 'trac-wallet/constants.js';
2
+ import { Config } from './config.js';
3
+
4
+ export const ENV = {
5
+ MAINNET: 'mainnet',
6
+ DEVELOPMENT: 'development',
7
+ TESTNET1: 'testnet1'
8
+ }
9
+
10
+ const configData = {
11
+ [ENV.MAINNET]: {
12
+ addressLength: 63,
13
+ addressPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX,
14
+ addressPrefixLength: TRAC_NETWORK_MSB_MAINNET_PREFIX.length,
15
+ bech32mHrpLength: TRAC_NETWORK_MSB_MAINNET_PREFIX.length + 1, // len(addressPrefix + separator)
16
+ bootstrap: 'acbc3a4344d3a804101d40e53db1dda82b767646425af73599d4cd6577d69685',
17
+ channel: '0000trac0network0msb0mainnet0000',
18
+ dhtBootstrap: ['116.202.214.149:10001', '157.180.12.214:10001', 'node1.hyperdht.org:49737', 'node2.hyperdht.org:49737', 'node3.hyperdht.org:49737'],
19
+ disableRateLimit: false,
20
+ enableErrorApplyLogs: false,
21
+ enableInteractiveMode: true,
22
+ enableRoleRequester: false,
23
+ enableTxApplyLogs: false,
24
+ enableValidatorObserver: true,
25
+ enableWallet: true,
26
+ maxValidators: 50,
27
+ maxRetries: 3,
28
+ messageThreshold: 3,
29
+ messageValidatorRetryDelay: 1000, //How long to wait before retrying (ms) MESSAGE_VALIDATOR_RETRY_DELAY_MS
30
+ messageValidatorResponseTimeout: 3 * 3 * 1000, //Overall timeout for sending a message (ms). This is 3 * maxRetries * messageValidatorRetryDelay;
31
+ networkId: 918,
32
+ storesDirectory: 'stores/',
33
+ },
34
+ [ENV.DEVELOPMENT]: {
35
+ addressLength: 63,
36
+ addressPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX,
37
+ addressPrefixLength: TRAC_NETWORK_MSB_MAINNET_PREFIX.length,
38
+ bech32mHrpLength: TRAC_NETWORK_MSB_MAINNET_PREFIX.length + 1, // len(addressPrefix + separator)
39
+ bootstrap: 'e90cca53847a12a82f3bf0f67401e45e2ccc1698ee163e61414c2894eb3c6b12',
40
+ channel: '12312313123123',
41
+ dhtBootstrap: ['116.202.214.149:10001', '157.180.12.214:10001', 'node1.hyperdht.org:49737', 'node2.hyperdht.org:49737', 'node3.hyperdht.org:49737'],
42
+ disableRateLimit: false,
43
+ enableErrorApplyLogs: true,
44
+ enableInteractiveMode: true,
45
+ enableRoleRequester: false,
46
+ enableTxApplyLogs: true,
47
+ enableValidatorObserver: true,
48
+ enableWallet: true,
49
+ maxValidators: 6,
50
+ maxRetries: 0,
51
+ messageThreshold: 1000,
52
+ messageValidatorRetryDelay: 1000, //How long to wait before retrying (ms) MESSAGE_VALIDATOR_RETRY_DELAY_MS
53
+ messageValidatorResponseTimeout: 3 * 3 * 1000, //Overall timeout for sending a message (ms). This is 3 * maxRetries * messageValidatorRetryDelay;
54
+ networkId: 918,
55
+ storesDirectory : 'stores/',
56
+ }
57
+ }
58
+
59
+ export const createConfig = (environment, options) => {
60
+ return new Config(options, configData[environment])
61
+ }