trac-msb 0.2.26 → 0.3.0-beta.1

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 (136) hide show
  1. package/SECURITY.md +2 -2
  2. package/cli/commandHandler.js +317 -20
  3. package/cli/handlers.js +9 -1
  4. package/cli/interactive.js +4 -3
  5. package/cli/tokenSupplyAudit.js +90 -0
  6. package/msb.mjs +1 -0
  7. package/package.json +15 -6
  8. package/rpc/create_server.js +12 -3
  9. package/rpc/handlers.js +103 -0
  10. package/rpc/routes/v1.js +8 -0
  11. package/src/codecs/apply/applyOperationCodec.js +334 -0
  12. package/src/codecs/apply/applyOperations.generated.cjs +6380 -0
  13. package/src/codecs/consensus/v1/consensusV1.generated.cjs +3508 -0
  14. package/src/codecs/consensus/v1/consensusV1OperationCodec.js +161 -0
  15. package/src/codecs/consensus/v1/vdfConfigCodec.js +83 -0
  16. package/src/{utils/protobuf → codecs/network/v1}/networkV1.generated.cjs +664 -393
  17. package/src/codecs/network/v1/networkV1OperationCodec.js +25 -0
  18. package/src/codecs/protobufRuntime.cjs +95 -0
  19. package/src/codecs/state/escrowEntryCodec.js +45 -0
  20. package/src/codecs/state/state.generated.cjs +659 -0
  21. package/src/config/args.js +0 -1
  22. package/src/config/config.js +41 -7
  23. package/src/config/env.js +33 -9
  24. package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionError.js +55 -0
  25. package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionOperations.js +118 -0
  26. package/src/core/consensus/epochAppenderSelection/EpochAppenderSelector.js +1042 -0
  27. package/src/core/consensus/epochAppenderSelection/createEpochAppenderSelector.js +23 -0
  28. package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionLedgerReader.js +274 -0
  29. package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionState.js +488 -0
  30. package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionStorage.js +93 -0
  31. package/src/core/consensus/quorum.js +11 -0
  32. package/src/core/consensus/services/EpochCoordinationRound.js +839 -0
  33. package/src/core/consensus/services/EpochCoordinatorOperations.js +211 -0
  34. package/src/core/consensus/services/EpochCoordinatorService.js +335 -0
  35. package/src/core/consensus/services/EpochRoundListeners.js +116 -0
  36. package/src/core/consensus/services/EpochStateMachine.js +141 -0
  37. package/src/core/consensus/services/VDFBare.js +291 -0
  38. package/src/core/consensus/services/VDFNode.js +111 -0
  39. package/src/core/consensus/services/VDFProgressWatchdog.js +79 -0
  40. package/src/core/consensus/services/VDFService.js +87 -0
  41. package/src/core/consensus/services/VDFServiceManager.js +147 -0
  42. package/src/core/consensus/services/createVDFService.js +16 -0
  43. package/src/core/consensus/services/vdf-guardian.js +32 -0
  44. package/src/core/consensus/services/vdf-worker.js +90 -0
  45. package/src/core/network/Network.js +327 -238
  46. package/src/core/network/protocols/consensus/IndexerMessages.js +72 -0
  47. package/src/core/network/protocols/consensus/v1/ConsensusV1ConnectionPolicy.js +57 -0
  48. package/src/core/network/protocols/consensus/v1/ConsensusV1MessageRouter.js +147 -0
  49. package/src/core/network/protocols/consensus/v1/ConsensusV1Protocol.js +72 -0
  50. package/src/core/network/protocols/consensus/v1/IndexerPendingRequestService.js +108 -0
  51. package/src/core/network/protocols/consensus/v1/V1ConsensusProtocolError.js +29 -0
  52. package/src/core/network/protocols/consensus/v1/handlers/V1EpochAppendOperationHandler.js +68 -0
  53. package/src/core/network/protocols/consensus/v1/handlers/V1EpochProofProposalOperationHandler.js +226 -0
  54. package/src/core/network/protocols/consensus/v1/validators/ConsensusValidationSchema.js +192 -0
  55. package/src/core/network/protocols/consensus/v1/validators/V1BaseConsensusOperation.js +428 -0
  56. package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendReply.js +47 -0
  57. package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendRequest.js +17 -0
  58. package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalApproval.js +116 -0
  59. package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalRequest.js +152 -0
  60. package/src/core/network/protocols/consensus/v1/validators/consensusValidationSchemaHelpers.js +145 -0
  61. package/src/core/network/protocols/shared/ConnectionOperationHandler.js +27 -0
  62. package/src/core/network/protocols/{NetworkMessages.js → validators/ValidatorMessages.js} +24 -14
  63. package/src/core/network/protocols/{ProtocolSession.js → validators/ValidatorProtocolSession.js} +22 -25
  64. package/src/core/network/protocols/{legacy/NetworkMessageRouter.js → validators/legacy/ValidatorLegacyMessageRouter.js} +6 -6
  65. package/src/core/network/protocols/{LegacyProtocol.js → validators/legacy/ValidatorLegacyProtocol.js} +9 -7
  66. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/BaseStateOperationHandler.js +1 -1
  67. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyGetRequestHandler.js +5 -5
  68. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyRoleOperationHandler.js +5 -5
  69. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacySubnetworkOperationHandler.js +5 -5
  70. package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyTransferOperationHandler.js +5 -5
  71. package/src/core/network/protocols/{ProtocolInterface.js → validators/shared/ValidatorProtocolInterface.js} +5 -5
  72. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialBootstrapDeploymentValidator.js +1 -1
  73. package/src/core/network/protocols/validators/shared/validators/PartialHtlcValidator.js +325 -0
  74. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialOperationValidator.js +44 -21
  75. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialRoleAccessValidator.js +3 -3
  76. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransactionValidator.js +3 -3
  77. package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransferValidator.js +4 -4
  78. package/src/core/network/protocols/{v1 → validators/v1}/V1ProtocolError.js +1 -1
  79. package/src/core/network/{services/PendingRequestService.js → protocols/validators/v1/ValidatorPendingRequestService.js} +19 -82
  80. package/src/core/network/protocols/{v1/NetworkMessageRouter.js → validators/v1/ValidatorV1MessageRouter.js} +9 -9
  81. package/src/core/network/protocols/{V1Protocol.js → validators/v1/ValidatorV1Protocol.js} +10 -8
  82. package/src/core/network/protocols/{connectionPolicies.js → validators/v1/connectionPolicies.js} +1 -1
  83. package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BaseOperationHandler.js +6 -23
  84. package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BroadcastTransactionOperationHandler.js +14 -9
  85. package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1LivenessOperationHandler.js +5 -4
  86. package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionErrorMapper.js +3 -3
  87. package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionOperationStrategies.js +44 -1
  88. package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionPayloadUtils.js +1 -1
  89. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BaseOperation.js +19 -15
  90. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionRequest.js +2 -2
  91. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionResponse.js +34 -18
  92. package/src/core/network/protocols/{v1 → validators/v1}/validators/V1ValidationSchema.js +3 -3
  93. package/src/core/network/services/IndexerConnectionManager.js +88 -0
  94. package/src/core/network/services/TransactionPoolService.js +11 -18
  95. package/src/core/network/services/TransactionRateLimiterService.js +1 -1
  96. package/src/core/network/services/ValidatorConnectionManager.js +163 -0
  97. package/src/core/network/services/{MessageOrchestrator.js → ValidatorMessageOrchestrator.js} +34 -42
  98. package/src/core/network/services/ValidatorObserverService.js +21 -36
  99. package/src/core/network/shared/BasePendingRequestService.js +67 -0
  100. package/src/core/network/shared/PeerConnectionManager.js +185 -0
  101. package/src/core/state/State.js +1789 -173
  102. package/src/core/state/StateEventQueue.js +69 -0
  103. package/src/core/state/utils/address.js +10 -6
  104. package/src/core/state/utils/epochProof.js +109 -0
  105. package/src/core/state/utils/escrowEntry.js +178 -0
  106. package/src/core/state/validators/StateValidationSchema.js +757 -0
  107. package/src/core/state/validators/stateValidationRules.js +312 -0
  108. package/src/index.js +255 -65
  109. package/src/messages/consensus/v1/ConsensusMessageBuilder.js +368 -0
  110. package/src/messages/consensus/v1/ConsensusMessageDirector.js +142 -0
  111. package/src/messages/consensus/v1/consensusMessageFactory.js +12 -0
  112. package/src/messages/consensus/v1/epochAppendMessage.js +58 -0
  113. package/src/messages/network/v1/NetworkMessageBuilder.js +15 -7
  114. package/src/messages/state/ApplyStateMessageBuilder.js +499 -13
  115. package/src/messages/state/ApplyStateMessageDirector.js +261 -2
  116. package/src/utils/StateMachine.js +125 -0
  117. package/src/utils/applyOperations.js +45 -2
  118. package/src/utils/buffer.js +239 -59
  119. package/src/utils/consensusConfig.js +76 -0
  120. package/src/utils/constants.js +174 -73
  121. package/src/utils/helpers.js +0 -25
  122. package/src/utils/htlcLock.js +122 -0
  123. package/src/utils/logger.js +10 -4
  124. package/src/utils/migrationUtils.js +2 -1
  125. package/src/utils/normalizers.js +113 -0
  126. package/src/utils/scheduler/SchedulableService.js +107 -0
  127. package/src/utils/{Scheduler.js → scheduler/Scheduler.js} +56 -17
  128. package/src/core/network/services/ConnectionManager.js +0 -406
  129. package/src/utils/check.js +0 -525
  130. package/src/utils/protobuf/applyOperations.cjs +0 -1373
  131. package/src/utils/protobuf/operationHelpers.js +0 -81
  132. /package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyResponseHandler.js +0 -0
  133. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/ValidatorResponse.js +0 -0
  134. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/base/BaseResponse.js +0 -0
  135. /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessRequest.js +0 -0
  136. /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessResponse.js +0 -0
@@ -0,0 +1,368 @@
1
+ import b4a from 'b4a';
2
+ import { appendSignatureData, toAppendIntent } from './epochAppendMessage.js';
3
+ import tracCryptoApi from 'trac-crypto-api';
4
+ import { encodeProofProposalApproval } from '../../../codecs/consensus/v1/consensusV1OperationCodec.js';
5
+ import { addressToBuffer, isAddressValid } from "../../../core/state/utils/address.js";
6
+ import {
7
+ ConsensusOperationType,
8
+ ConsensusResultCode
9
+ } from '../../../utils/constants.js';
10
+ import {
11
+ createMessage,
12
+ uint16ToBuffer,
13
+ uint64ToBuffer, uint32ToBuffer
14
+ } from "../../../utils/buffer.js";
15
+
16
+ class ConsensusMessageBuilder {
17
+ #wallet;
18
+ #config;
19
+ #header;
20
+ #type;
21
+ #session_id;
22
+ #timestamp;
23
+ #network_id;
24
+ #epoch;
25
+ #previous_epoch_record_hash;
26
+ #proposer;
27
+ #difficulty;
28
+ #discriminant_bit_size;
29
+ #proof;
30
+ #resultCode;
31
+ #approver;
32
+ #body;
33
+ #payloadKey;
34
+ #requester_proof_signature;
35
+ #appendIntent;
36
+ #appendVote;
37
+
38
+ constructor(wallet, config) {
39
+ this.#config = config;
40
+ this.#wallet = wallet;
41
+ }
42
+
43
+ #validateBuffer(value, fieldName) {
44
+ if (!b4a.isBuffer(value)) {
45
+ throw new Error(`${fieldName} must be a buffer.`);
46
+ }
47
+
48
+ return value;
49
+ }
50
+
51
+ #validateAddress(address, fieldName) {
52
+ if (typeof address !== 'string' || !isAddressValid(address, this.#config.addressPrefix)) {
53
+ throw new Error(`${fieldName} must be a valid TRAC address.`);
54
+ }
55
+
56
+ try {
57
+ const publicKey = tracCryptoApi.address.decode(address);
58
+ const canonicalAddress = tracCryptoApi.address.encode(this.#config.addressPrefix, publicKey);
59
+ if (canonicalAddress !== address) {
60
+ throw new Error('Address is not canonical for configured prefix.');
61
+ }
62
+
63
+ return addressToBuffer(address, this.#config.addressPrefix);
64
+ } catch {
65
+ throw new Error(`${fieldName} must be a valid TRAC address.`);
66
+ }
67
+ }
68
+
69
+ #setHeader() {
70
+ if (!this.#type) throw new Error('Header requires type to be set');
71
+ if (!this.#session_id) throw new Error('Header requires session to be set');
72
+ if (!this.#timestamp) throw new Error('Header requires a timestamp provider');
73
+
74
+ this.#header = {
75
+ type: this.#type,
76
+ session_id: this.#session_id,
77
+ timestamp: this.#timestamp,
78
+ };
79
+ return this;
80
+ }
81
+
82
+ setType(type) {
83
+ if (!Object.values(ConsensusOperationType).includes(type) || type === ConsensusOperationType.UNSPECIFIED) {
84
+ throw new Error(`Invalid consensus operation type: ${type}`);
85
+ }
86
+ this.#type = type;
87
+ return this;
88
+ }
89
+
90
+ setSessionId(sessionId) {
91
+ if (typeof sessionId !== 'string' || sessionId.length === 0) {
92
+ throw new Error('Session id must be a non-empty string.');
93
+ }
94
+
95
+ this.#session_id = sessionId;
96
+ return this;
97
+ }
98
+
99
+ setTimestamp() {
100
+ this.#timestamp = Date.now();
101
+ return this;
102
+ }
103
+
104
+ setNetworkId(networkId) {
105
+ const value = uint16ToBuffer(networkId);
106
+ if (networkId === 0) {
107
+ throw new Error('Network id must be greater than zero.');
108
+ }
109
+
110
+ this.#network_id = value;
111
+ return this;
112
+ }
113
+
114
+ setEpoch(epoch) {
115
+ const value = uint64ToBuffer(epoch);
116
+ const epochValue = typeof epoch === 'bigint' ? epoch : BigInt(epoch);
117
+ if (epochValue === 0n) {
118
+ throw new Error('Epoch must be greater than zero.');
119
+ }
120
+
121
+ this.#epoch = value;
122
+ return this;
123
+ }
124
+
125
+ setPreviousEpochRecordHash(previousEpochRecordHash) {
126
+ this.#previous_epoch_record_hash = this.#validateBuffer(
127
+ previousEpochRecordHash,
128
+ 'Previous epoch record hash'
129
+ );
130
+ return this;
131
+ }
132
+
133
+ setProposer(proposer) {
134
+ this.#proposer = this.#validateAddress(proposer, 'Proposer');
135
+ return this;
136
+ }
137
+
138
+ setDifficulty(difficulty) {
139
+ this.#difficulty = this.#validateBuffer(difficulty, 'Difficulty');
140
+ return this;
141
+
142
+ }
143
+
144
+ setDiscriminantBitSize(discriminantBitSize) {
145
+ this.#discriminant_bit_size = this.#validateBuffer(discriminantBitSize, 'Discriminant bit size');
146
+ return this;
147
+ }
148
+
149
+ setProof(proof) {
150
+ this.#proof = this.#validateBuffer(proof, 'Proof');
151
+ return this;
152
+ }
153
+
154
+
155
+ setResultCode(code) {
156
+ if (!Object.values(ConsensusResultCode).includes(code)) {
157
+ throw new Error(`Invalid consensus result code: ${code}`);
158
+ }
159
+
160
+ this.#resultCode = code;
161
+ return this;
162
+ }
163
+
164
+ setApprover(approver) {
165
+ this.#approver = this.#validateAddress(approver, 'Approver');
166
+ return this;
167
+ }
168
+
169
+ setRequesterProofSignature(proofSignature) {
170
+ this.#requester_proof_signature = this.#validateBuffer(
171
+ proofSignature,
172
+ 'Requester proof signature'
173
+ );
174
+ return this;
175
+ }
176
+
177
+ #buildUnsignedProofProposal() {
178
+ return {
179
+ network_id: this.#validateBuffer(this.#network_id, 'Network id'),
180
+ epoch: this.#validateBuffer(this.#epoch, 'Epoch'),
181
+ previous_epoch_record_hash: this.#validateBuffer(this.#previous_epoch_record_hash, 'Previous epoch record hash'),
182
+ proposer: this.#validateBuffer(this.#proposer, 'Proposer'),
183
+ difficulty: this.#validateBuffer(this.#difficulty, 'Difficulty'),
184
+ discriminant_bit_size: this.#validateBuffer(this.#discriminant_bit_size, 'Discriminant bit size'),
185
+ proof: this.#validateBuffer(this.#proof, 'Proof')
186
+ };
187
+ }
188
+
189
+ #getResultCode() {
190
+ if (this.#resultCode === undefined) {
191
+ throw new Error('Result code must be set before build.');
192
+ }
193
+
194
+ return this.#resultCode;
195
+ }
196
+
197
+ #getApprover() {
198
+ return this.#validateBuffer(this.#approver, 'Approver');
199
+ }
200
+
201
+ #getRequesterProofSignature() {
202
+ return this.#validateBuffer(
203
+ this.#requester_proof_signature,
204
+ 'Requester proof signature'
205
+ );
206
+ }
207
+
208
+ async #hashProofProposal(proofProposal) {
209
+ return await tracCryptoApi.hash.blake3(createMessage(
210
+ proofProposal.network_id,
211
+ proofProposal.epoch,
212
+ proofProposal.previous_epoch_record_hash,
213
+ proofProposal.proposer,
214
+ proofProposal.difficulty,
215
+ proofProposal.discriminant_bit_size,
216
+ proofProposal.proof
217
+ ));
218
+ }
219
+
220
+ async #hashProofProposalApproval(proofProposal, approver, requesterProofSignature) {
221
+ return await tracCryptoApi.hash.blake3(createMessage(
222
+ proofProposal.network_id,
223
+ proofProposal.epoch,
224
+ proofProposal.previous_epoch_record_hash,
225
+ proofProposal.proposer,
226
+ proofProposal.difficulty,
227
+ proofProposal.discriminant_bit_size,
228
+ proofProposal.proof,
229
+ approver,
230
+ requesterProofSignature
231
+ ));
232
+ }
233
+
234
+ async #hashProofProposalResponse(resultCode, proofProposalApproval) {
235
+ const resultCodeBuffer = uint32ToBuffer(resultCode);
236
+ const message = proofProposalApproval
237
+ ? createMessage(resultCodeBuffer, encodeProofProposalApproval(proofProposalApproval))
238
+ : createMessage(resultCodeBuffer);
239
+
240
+ return await tracCryptoApi.hash.blake3(message);
241
+ }
242
+
243
+ async #signProofProposal(proofProposal) {
244
+ return this.#wallet.sign(await this.#hashProofProposal(proofProposal));
245
+ }
246
+
247
+ async #buildProofProposalApproval(proofProposal, approver, requesterProofSignature) {
248
+ const hashApproval = await this.#hashProofProposalApproval(
249
+ proofProposal,
250
+ approver,
251
+ requesterProofSignature
252
+ );
253
+
254
+ return {
255
+ approver,
256
+ approval_sig: this.#wallet.sign(hashApproval),
257
+ };
258
+ }
259
+
260
+ async #signProofProposalResponse(resultCode, proofProposalApproval) {
261
+ return this.#wallet.sign(
262
+ await this.#hashProofProposalResponse(resultCode, proofProposalApproval)
263
+ );
264
+ }
265
+
266
+ #setPayload(payloadKey, body) {
267
+ this.#payloadKey = payloadKey;
268
+ this.#body = body;
269
+ }
270
+
271
+ async #buildProofProposalPayload() {
272
+ const proofProposal = this.#buildUnsignedProofProposal();
273
+ const signature = await this.#signProofProposal(proofProposal);
274
+ this.#setPayload('proof_proposal', {
275
+ ...proofProposal,
276
+ signature: signature,
277
+ });
278
+ }
279
+
280
+ async #buildProofProposalResponsePayload() {
281
+ const resultCode = this.#getResultCode();
282
+ let proofProposalApproval = null;
283
+
284
+ if (resultCode === ConsensusResultCode.OK) {
285
+ const proofProposal = this.#buildUnsignedProofProposal();
286
+ const approver = this.#getApprover();
287
+ const requesterProofSignature = this.#getRequesterProofSignature();
288
+ proofProposalApproval = await this.#buildProofProposalApproval(
289
+ proofProposal,
290
+ approver,
291
+ requesterProofSignature
292
+ );
293
+ }
294
+
295
+ const responseSig = await this.#signProofProposalResponse(resultCode, proofProposalApproval);
296
+
297
+ const response = {
298
+ result: resultCode,
299
+ response_sig: responseSig,
300
+ };
301
+
302
+ if (proofProposalApproval) {
303
+ response.approval = proofProposalApproval;
304
+ }
305
+
306
+ this.#setPayload('proof_proposal_response', response);
307
+ }
308
+
309
+
310
+ async buildPayload() {
311
+ this.#setHeader();
312
+
313
+ switch (this.#type) {
314
+ case ConsensusOperationType.REQUEST:
315
+ case ConsensusOperationType.REPLY: {
316
+ await this.#buildAppendPayload();
317
+ break;
318
+ }
319
+ case ConsensusOperationType.PROOF_PROPOSAL: {
320
+ await this.#buildProofProposalPayload();
321
+ break;
322
+ }
323
+ case ConsensusOperationType.PROOF_PROPOSAL_APPROVAL: {
324
+ await this.#buildProofProposalResponsePayload();
325
+ break;
326
+ }
327
+ default:
328
+ throw new Error(`Unsupported consensus type ${this.#type}`);
329
+ }
330
+ }
331
+
332
+ getResult() {
333
+ if (!this.#header || !this.#payloadKey || !this.#body) {
334
+ throw new Error('Header or payload not set before getResult');
335
+ }
336
+
337
+ return {
338
+ ...this.#header,
339
+ [this.#payloadKey]: this.#body
340
+ };
341
+ }
342
+
343
+ setAppendIntent(request) {
344
+ this.#appendIntent = toAppendIntent(request);
345
+ this.#appendVote = request.vote ? {
346
+ vote_status: request.voteStatus,
347
+ observed_counter: uint64ToBuffer(request.observedCounter),
348
+ vote: toAppendIntent(request.vote),
349
+ } : null;
350
+ return this;
351
+ }
352
+
353
+ async #buildAppendPayload() {
354
+ const payloadKey = this.#type === ConsensusOperationType.REQUEST ? 'request' : 'reply';
355
+ const body = { intent: this.#appendIntent };
356
+ if (this.#type === ConsensusOperationType.REPLY) {
357
+ if (!this.#appendVote) throw new Error('An appender reply requires a persisted vote decision');
358
+ body.responder = tracCryptoApi.address.decode(this.#wallet.address);
359
+ Object.assign(body, this.#appendVote);
360
+ }
361
+ const signatureData = appendSignatureData({ ...this.#header, [payloadKey]: body });
362
+ const hash = await tracCryptoApi.hash.blake3(signatureData);
363
+ body.signature = this.#wallet.sign(hash);
364
+ this.#setPayload(payloadKey, body);
365
+ }
366
+ }
367
+
368
+ export default ConsensusMessageBuilder;
@@ -0,0 +1,142 @@
1
+ import {ConsensusOperationType} from '../../../utils/constants.js';
2
+
3
+ /**
4
+ * Director for v1 consensus protocol messages.
5
+ */
6
+
7
+ class ConsensusMessageDirector {
8
+ #builder;
9
+
10
+ /**
11
+ * @param {ConsensusMessageBuilder} builderInstance
12
+ */
13
+ constructor(builderInstance) {
14
+ this.#builder = builderInstance;
15
+ }
16
+
17
+
18
+ /**
19
+ * Build a proof proposal message.
20
+ * @param {string} sessionId
21
+ * @param {number} networkId
22
+ * @param {number} epoch
23
+ * @param {Buffer} previousEpochRecordHash
24
+ * @param {string} proposer
25
+ * @param {Buffer} difficulty
26
+ * @param {Buffer} discriminantBitSize
27
+ * @param {Buffer} proof
28
+ * @returns {Promise<object>}
29
+ */
30
+ async buildProofProposal(
31
+ sessionId,
32
+ networkId,
33
+ epoch,
34
+ previousEpochRecordHash,
35
+ proposer,
36
+ difficulty,
37
+ discriminantBitSize,
38
+ proof
39
+ ) {
40
+ await this.#builder
41
+ .setType(ConsensusOperationType.PROOF_PROPOSAL)
42
+ .setSessionId(sessionId)
43
+ .setTimestamp()
44
+ .setNetworkId(networkId)
45
+ .setEpoch(epoch)
46
+ .setPreviousEpochRecordHash(previousEpochRecordHash)
47
+ .setProposer(proposer)
48
+ .setDifficulty(difficulty)
49
+ .setDiscriminantBitSize(discriminantBitSize)
50
+ .setProof(proof)
51
+ .buildPayload();
52
+
53
+ return this.#builder.getResult();
54
+ }
55
+
56
+ /**
57
+ * Build a proof proposal response message.
58
+ * @param {string} sessionId
59
+ * @param {number} networkId
60
+ * @param {number} epoch
61
+ * @param {Buffer} previousEpochRecordHash
62
+ * @param {string} proposer
63
+ * @param {Buffer} difficulty
64
+ * @param {Buffer} discriminantBitSize
65
+ * @param {Buffer} proof
66
+ * @param {Buffer} requesterProofSignature
67
+ * @param {number} consensusResultCode
68
+ * @param {string} approver
69
+ * @returns {Promise<object>}
70
+ */
71
+ async buildProofProposalResponse(
72
+ sessionId,
73
+ networkId,
74
+ epoch,
75
+ previousEpochRecordHash,
76
+ proposer,
77
+ difficulty,
78
+ discriminantBitSize,
79
+ proof,
80
+ requesterProofSignature,
81
+ consensusResultCode,
82
+ approver
83
+ ) {
84
+ await this.#builder
85
+ .setType(ConsensusOperationType.PROOF_PROPOSAL_APPROVAL)
86
+ .setSessionId(sessionId)
87
+ .setTimestamp()
88
+ .setNetworkId(networkId)
89
+ .setEpoch(epoch)
90
+ .setPreviousEpochRecordHash(previousEpochRecordHash)
91
+ .setProposer(proposer)
92
+ .setDifficulty(difficulty)
93
+ .setDiscriminantBitSize(discriminantBitSize)
94
+ .setProof(proof)
95
+ .setRequesterProofSignature(requesterProofSignature)
96
+ .setResultCode(consensusResultCode)
97
+ .setApprover(approver)
98
+ .buildPayload();
99
+
100
+ return this.#builder.getResult();
101
+ }
102
+
103
+ /**
104
+ * Build a non-OK proof proposal response without requiring proof proposal fields.
105
+ * @param {string} sessionId
106
+ * @param {number} consensusResultCode
107
+ * @returns {Promise<object>}
108
+ */
109
+ async buildProofProposalRejectionResponse(sessionId, consensusResultCode) {
110
+ await this.#builder
111
+ .setType(ConsensusOperationType.PROOF_PROPOSAL_APPROVAL)
112
+ .setSessionId(sessionId)
113
+ .setTimestamp()
114
+ .setResultCode(consensusResultCode)
115
+ .buildPayload();
116
+
117
+ return this.#builder.getResult();
118
+ }
119
+
120
+
121
+ /** Build a signed arbitration REQUEST for the exact publication intent. */
122
+ async buildRequest(sessionId, request) {
123
+ return this.#buildAppendMessage(ConsensusOperationType.REQUEST, sessionId, request);
124
+ }
125
+
126
+ /** Build a signed REPLY bound to the original request and RPC session. */
127
+ async buildReply(sessionId, request) {
128
+ return this.#buildAppendMessage(ConsensusOperationType.REPLY, sessionId, request);
129
+ }
130
+
131
+ async #buildAppendMessage(type, sessionId, request) {
132
+ await this.#builder
133
+ .setType(type)
134
+ .setSessionId(sessionId)
135
+ .setTimestamp()
136
+ .setAppendIntent(request)
137
+ .buildPayload();
138
+ return this.#builder.getResult();
139
+ }
140
+ }
141
+
142
+ export default ConsensusMessageDirector;
@@ -0,0 +1,12 @@
1
+ import ConsensusMessageDirector from "./ConsensusMessageDirector.js";
2
+ import ConsensusMessageBuilder from "./ConsensusMessageBuilder.js";
3
+
4
+ /**
5
+ * Factory helper to create a director with a fresh builder instance.
6
+ * @param {IWallet} wallet
7
+ * @param {object} config
8
+ * @returns {ConsensusMessageDirector}
9
+ */
10
+ export const consensusMessageFactory = (wallet, config) => {
11
+ return new ConsensusMessageDirector(new ConsensusMessageBuilder(wallet, config))
12
+ }
@@ -0,0 +1,58 @@
1
+ import b4a from 'b4a';
2
+ import { encodeEpochAppendIntent } from '../../../codecs/consensus/v1/consensusV1OperationCodec.js';
3
+ import { uint16ToBuffer, uint32ToBuffer, uint64ToBuffer } from '../../../utils/buffer.js';
4
+ import { ConsensusOperationType } from '../../../utils/constants.js';
5
+ import { EPOCH_APPENDER_SELECTION_VERSION } from '../../../core/consensus/epochAppenderSelection/epochAppenderSelectionState.js';
6
+
7
+ /** Convert the signed request identity to protobuf fields without serializing local membership. */
8
+ export function toAppendIntent(request) {
9
+ return {
10
+ network_id: uint16ToBuffer(request.networkId),
11
+ bootstrap: b4a.from(request.bootstrap, 'hex'),
12
+ epoch: uint64ToBuffer(BigInt(request.targetEpoch)),
13
+ parent_hash: b4a.from(request.parentHash, 'hex'),
14
+ membership_context: b4a.from(request.membershipContext, 'hex'),
15
+ config_hash: b4a.from(request.configHash, 'hex'),
16
+ request_id: request.requestId,
17
+ payload_hash: b4a.from(request.payloadHash, 'hex'),
18
+ logical_counter: uint64ToBuffer(request.logicalCounter),
19
+ requester: b4a.from(request.requester, 'hex'),
20
+ selection_version: request.selectionVersion ?? EPOCH_APPENDER_SELECTION_VERSION,
21
+ };
22
+ }
23
+
24
+ /** Restore the wire identity; validators resolve membership separately from signed state. */
25
+ export function fromAppendIntent(intent) {
26
+ return {
27
+ networkId: intent.network_id.readUInt16BE(0),
28
+ bootstrap: b4a.toString(intent.bootstrap, 'hex'),
29
+ targetEpoch: intent.epoch.readBigUInt64BE(0).toString(),
30
+ parentHash: b4a.toString(intent.parent_hash, 'hex'),
31
+ membershipContext: b4a.toString(intent.membership_context, 'hex'),
32
+ configHash: b4a.toString(intent.config_hash, 'hex'),
33
+ requestId: intent.request_id,
34
+ payloadHash: b4a.toString(intent.payload_hash, 'hex'),
35
+ logicalCounter: intent.logical_counter.readBigUInt64BE(0),
36
+ requester: b4a.toString(intent.requester, 'hex'),
37
+ selectionVersion: intent.selection_version,
38
+ };
39
+ }
40
+
41
+ /** Domain separation and length framing bind the entire header, intent and responder. */
42
+ export function appendSignatureData(message) {
43
+ const body = message.type === ConsensusOperationType.REQUEST ? message.request : message.reply;
44
+ const session = b4a.from(message.session_id);
45
+ const parts = [
46
+ b4a.from('msb/epoch-appender-election/v2\0'),
47
+ uint32ToBuffer(message.type),
48
+ uint32ToBuffer(session.length),
49
+ session,
50
+ uint64ToBuffer(BigInt(message.timestamp)),
51
+ encodeEpochAppendIntent(body.intent),
52
+ body.responder ?? b4a.alloc(0),
53
+ ];
54
+ if (message.type === ConsensusOperationType.REPLY) {
55
+ parts.push(uint32ToBuffer(body.vote_status), body.observed_counter, encodeEpochAppendIntent(body.vote));
56
+ }
57
+ return b4a.concat(parts);
58
+ }
@@ -1,7 +1,12 @@
1
1
  import tracCryptoApi from 'trac-crypto-api';
2
2
  import b4a from 'b4a';
3
- import {createMessage, safeWriteUInt32BE, idToBuffer, timestampToBuffer} from "../../../utils/buffer.js";
4
- import {NetworkOperationType, ResultCode} from '../../../utils/constants.js';
3
+ import {
4
+ createMessage,
5
+ idToBuffer,
6
+ timestampToBuffer,
7
+ uint32ToBuffer
8
+ } from "../../../utils/buffer.js";
9
+ import {NetworkOperationType, NetworkResultCode} from '../../../utils/constants.js';
5
10
  import {isAddressValid} from "../../../core/state/utils/address.js";
6
11
  import {encodeCapabilities} from "../../../utils/buffer.js";
7
12
 
@@ -42,6 +47,9 @@ class NetworkMessageBuilder {
42
47
  }
43
48
 
44
49
  setId(id) {
50
+ if (typeof id !== 'string' || id.length === 0) {
51
+ throw new Error('Session id must be a non-empty string.');
52
+ }
45
53
  this.#id = id;
46
54
  return this;
47
55
  }
@@ -69,7 +77,7 @@ class NetworkMessageBuilder {
69
77
  }
70
78
 
71
79
  setResultCode(code) {
72
- if (!Object.values(ResultCode).includes(code)) {
80
+ if (!Object.values(NetworkResultCode).includes(code)) {
73
81
  throw new Error(`Invalid network result code: ${code}`);
74
82
  }
75
83
 
@@ -161,7 +169,7 @@ class NetworkMessageBuilder {
161
169
  idBuf,
162
170
  tsBuf,
163
171
  nonce,
164
- safeWriteUInt32BE(this.#resultCode, 0),
172
+ uint32ToBuffer(this.#resultCode),
165
173
  encodeCapabilities(this.#capabilities),
166
174
  );
167
175
  const hash = await tracCryptoApi.hash.blake3(message);
@@ -213,11 +221,11 @@ class NetworkMessageBuilder {
213
221
  const timestamp = Number.isSafeInteger(this.#timestamp_ledger) ? this.#timestamp_ledger : 0;
214
222
  const hasTimestamp = timestamp > 0;
215
223
 
216
- if (this.#resultCode === ResultCode.OK) {
224
+ if (this.#resultCode === NetworkResultCode.OK) {
217
225
  if (!hasProof || !hasTimestamp) {
218
226
  throw new Error('Result code OK requires non-empty proof and timestamp > 0.');
219
227
  }
220
- } else if (this.#resultCode === ResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE) {
228
+ } else if (this.#resultCode === NetworkResultCode.TX_ACCEPTED_PROOF_UNAVAILABLE) {
221
229
  if (hasProof) {
222
230
  throw new Error('Result code TX_ACCEPTED_PROOF_UNAVAILABLE requires empty proof.');
223
231
  }
@@ -240,7 +248,7 @@ class NetworkMessageBuilder {
240
248
  nonce,
241
249
  proof,
242
250
  timestampToBuffer(timestamp),
243
- safeWriteUInt32BE(this.#resultCode, 0),
251
+ uint32ToBuffer(this.#resultCode),
244
252
  encodeCapabilities(this.#capabilities),
245
253
  );
246
254