trac-msb 0.2.24 → 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 (137) 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 +46 -7
  23. package/src/config/env.js +33 -6
  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/ConfirmationWatch.js +82 -0
  102. package/src/core/state/State.js +1794 -173
  103. package/src/core/state/StateEventQueue.js +69 -0
  104. package/src/core/state/utils/address.js +10 -6
  105. package/src/core/state/utils/epochProof.js +109 -0
  106. package/src/core/state/utils/escrowEntry.js +178 -0
  107. package/src/core/state/validators/StateValidationSchema.js +757 -0
  108. package/src/core/state/validators/stateValidationRules.js +312 -0
  109. package/src/index.js +255 -65
  110. package/src/messages/consensus/v1/ConsensusMessageBuilder.js +368 -0
  111. package/src/messages/consensus/v1/ConsensusMessageDirector.js +142 -0
  112. package/src/messages/consensus/v1/consensusMessageFactory.js +12 -0
  113. package/src/messages/consensus/v1/epochAppendMessage.js +58 -0
  114. package/src/messages/network/v1/NetworkMessageBuilder.js +15 -7
  115. package/src/messages/state/ApplyStateMessageBuilder.js +499 -13
  116. package/src/messages/state/ApplyStateMessageDirector.js +261 -2
  117. package/src/utils/StateMachine.js +125 -0
  118. package/src/utils/applyOperations.js +45 -2
  119. package/src/utils/buffer.js +239 -59
  120. package/src/utils/consensusConfig.js +76 -0
  121. package/src/utils/constants.js +174 -73
  122. package/src/utils/helpers.js +0 -25
  123. package/src/utils/htlcLock.js +122 -0
  124. package/src/utils/logger.js +10 -4
  125. package/src/utils/migrationUtils.js +2 -1
  126. package/src/utils/normalizers.js +113 -0
  127. package/src/utils/scheduler/SchedulableService.js +107 -0
  128. package/src/utils/{Scheduler.js → scheduler/Scheduler.js} +56 -17
  129. package/src/core/network/services/ConnectionManager.js +0 -406
  130. package/src/utils/check.js +0 -525
  131. package/src/utils/protobuf/applyOperations.cjs +0 -1373
  132. package/src/utils/protobuf/operationHelpers.js +0 -81
  133. /package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyResponseHandler.js +0 -0
  134. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/ValidatorResponse.js +0 -0
  135. /package/src/core/network/protocols/{legacy → validators/legacy}/validators/base/BaseResponse.js +0 -0
  136. /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessRequest.js +0 -0
  137. /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessResponse.js +0 -0
@@ -1,21 +1,41 @@
1
1
  import b4a from 'b4a';
2
2
  import tracCryptoApi from 'trac-crypto-api';
3
3
 
4
- import { createMessage } from '../../utils/buffer.js';
5
- import { OperationType } from '../../utils/constants.js';
6
- import { addressToBuffer, bufferToAddress } from '../../core/state/utils/address.js';
7
- import { isAddressValid } from "../../core/state/utils/address.js";
4
+ import { createMessage, isZeroBuffer, toHex, uint8ToBuffer } from '../../utils/buffer.js';
5
+ import {
6
+ AMOUNT_BYTE_LENGTH,
7
+ EPOCH_BYTE_LENGTH,
8
+ HASH_BYTE_LENGTH,
9
+ HTLC_LOCK_ID_BYTE_LENGTH,
10
+ HTLC_MAX_SIGNERS,
11
+ HTLC_METADATA_MAX_BYTE_LENGTH,
12
+ HTLC_PREIMAGE_BYTE_LENGTH,
13
+ HTLC_THRESHOLD_BYTE_LENGTH,
14
+ NONCE_BYTE_LENGTH,
15
+ OperationType,
16
+ PUBLIC_KEY_LENGTH,
17
+ SIGNATURE_BYTE_LENGTH,
18
+ } from '../../utils/constants.js';
19
+ import { addressToBuffer, bufferToAddress, isAddressValid } from '../../core/state/utils/address.js';
8
20
  import {
9
21
  isAdminControl,
10
22
  isBalanceInitialization,
23
+ isHtlc,
11
24
  isBootstrapDeployment,
25
+ isConsensusControl,
12
26
  isCoreAdmin,
13
27
  isRoleAccess,
28
+ isSetEpoch,
14
29
  isTransaction,
15
30
  isTransfer,
16
31
  operationToPayload
17
32
  } from '../../utils/applyOperations.js';
33
+ import { decodeConsensusConfig } from '../../codecs/apply/applyOperationCodec.js';
18
34
  import { isHexString } from '../../utils/helpers.js';
35
+ import {
36
+ createHtlcLockSigningMessage,
37
+ verifyOrderedHtlcCosignerSignatures
38
+ } from '../../utils/htlcLock.js';
19
39
 
20
40
  // Single use per transaction: reuse of this instance needs mutex/queue or fail-fast and can delay validation or break validation rule.
21
41
  // A fresh instance is effectively zero-cost, so no reset() is provided.
@@ -28,25 +48,42 @@ import { isHexString } from '../../utils/helpers.js';
28
48
  class ApplyStateMessageBuilder {
29
49
  #address;
30
50
  #amount;
51
+ #built = false;
31
52
  #channel;
32
- #config
33
53
  #contentHash;
54
+ #config;
55
+ #consensusConfig;
56
+ #encodedConsensusConfig;
34
57
  #externalBootstrap;
58
+ #htlcLockId;
59
+ #htlcPreimage;
35
60
  #incomingAddress;
36
61
  #incomingNonce;
37
62
  #incomingSignature;
38
63
  #incomingWriterKey;
64
+ #htlcCosignerSignatures = [];
65
+ #htlcClaimAddress;
66
+ #htlcFeeAmount;
67
+ #htlcFeeRecipient;
68
+ #htlcHashLock;
69
+ #htlcMetadata;
70
+ #htlcRefundAddress;
71
+ #htlcRefundEpoch;
72
+ #htlcSignerSet;
73
+ #htlcThreshold;
39
74
  #msbBootstrap;
75
+ #nonce;
40
76
  #operationType;
77
+ #output;
41
78
  #payload;
79
+ #payloadKey;
80
+ #phase;
42
81
  #txHash;
43
82
  #txValidity;
44
83
  #wallet;
45
84
  #writingKey;
46
- #phase;
47
- #output;
48
- #payloadKey;
49
- #built=false;
85
+ #epochData;
86
+ #epochSchemaVersion;
50
87
 
51
88
  constructor(wallet, config) {
52
89
  this.#config = config;
@@ -154,8 +191,127 @@ class ApplyStateMessageBuilder {
154
191
  return this;
155
192
  }
156
193
 
194
+ setHtlcLockId(lockId) {
195
+ this.#htlcLockId = this.#normalizeHexBuffer(
196
+ lockId,
197
+ HTLC_LOCK_ID_BYTE_LENGTH,
198
+ 'HTLC lock ID'
199
+ );
200
+ return this;
201
+ }
202
+
203
+ setHtlcPreimage(preimage) {
204
+ this.#htlcPreimage = this.#normalizeHexBuffer(
205
+ preimage,
206
+ HTLC_PREIMAGE_BYTE_LENGTH,
207
+ 'HTLC preimage'
208
+ );
209
+ return this;
210
+ }
211
+
157
212
  setAmount(amount) {
158
- this.#amount = this.#normalizeHexBuffer(amount, 16, 'Amount');
213
+ this.#amount = this.#normalizeHexBuffer(amount, AMOUNT_BYTE_LENGTH, 'Amount');
214
+ return this;
215
+ }
216
+
217
+ setEpochData(schemaVersion, data) {
218
+ this.#epochSchemaVersion = this.#normalizeHexBuffer(schemaVersion, 1, 'Epoch schema version');
219
+ this.#epochData = this.#normalizeBytesBuffer(data, 'Epoch data');
220
+ return this;
221
+ }
222
+
223
+ setNonce(nonce) {
224
+ this.#nonce = this.#normalizeHexBuffer(nonce, NONCE_BYTE_LENGTH, 'Nonce');
225
+ return this;
226
+ }
227
+
228
+ setHtlcClaimAddress(address) {
229
+ const addressBuffer = this.#normalizeAddress(address);
230
+ if (!addressBuffer) throw new Error('HTLC claim address must be a valid TRAC address.');
231
+ this.#htlcClaimAddress = addressBuffer;
232
+ return this;
233
+ }
234
+
235
+ setHtlcRefundAddress(address) {
236
+ const addressBuffer = this.#normalizeAddress(address);
237
+ if (!addressBuffer) throw new Error('HTLC refund address must be a valid TRAC address.');
238
+ this.#htlcRefundAddress = addressBuffer;
239
+ return this;
240
+ }
241
+
242
+ setHtlcFeeAmount(amount) {
243
+ this.#htlcFeeAmount = this.#normalizeHexBuffer(amount, AMOUNT_BYTE_LENGTH, 'HTLC fee amount');
244
+ return this;
245
+ }
246
+
247
+ setHtlcFeeRecipient(address) {
248
+ if (address === undefined || address === null) {
249
+ this.#htlcFeeRecipient = undefined;
250
+ return this;
251
+ }
252
+ const addressBuffer = this.#normalizeAddress(address);
253
+ if (!addressBuffer) throw new Error('HTLC fee recipient must be a valid TRAC address.');
254
+ this.#htlcFeeRecipient = addressBuffer;
255
+ return this;
256
+ }
257
+
258
+ setHtlcHashLock(hashLock) {
259
+ this.#htlcHashLock = this.#normalizeHexBuffer(hashLock, HASH_BYTE_LENGTH, 'HTLC hashlock');
260
+ return this;
261
+ }
262
+
263
+ setHtlcRefundEpoch(refundEpoch) {
264
+ this.#htlcRefundEpoch = this.#normalizeHexBuffer(refundEpoch, EPOCH_BYTE_LENGTH, 'HTLC refund epoch');
265
+ return this;
266
+ }
267
+
268
+ setHtlcMetadata(metadata) {
269
+ if (metadata === undefined) {
270
+ this.#htlcMetadata = undefined;
271
+ return this;
272
+ }
273
+ const bytes = this.#normalizeBytesBuffer(metadata, 'HTLC metadata');
274
+ if (bytes.length > HTLC_METADATA_MAX_BYTE_LENGTH) {
275
+ throw new Error(`HTLC metadata must not exceed ${HTLC_METADATA_MAX_BYTE_LENGTH} bytes.`);
276
+ }
277
+ this.#htlcMetadata = b4a.from(bytes);
278
+ return this;
279
+ }
280
+
281
+ setHtlcSignerSet(signerSet) {
282
+ if (!Array.isArray(signerSet) || signerSet.length < 1 || signerSet.length > HTLC_MAX_SIGNERS) {
283
+ throw new Error(`HTLC signer set must contain between 1 and ${HTLC_MAX_SIGNERS} public keys.`);
284
+ }
285
+ this.#htlcSignerSet = signerSet.map((signer, index) => {
286
+ const publicKey = this.#normalizeHexBuffer(signer, PUBLIC_KEY_LENGTH, `HTLC signer ${index}`);
287
+ if (isZeroBuffer(publicKey)) throw new Error(`HTLC signer ${index} must not be zero-filled.`);
288
+ return publicKey;
289
+ });
290
+ return this;
291
+ }
292
+
293
+ setHtlcThreshold(threshold) {
294
+ this.#htlcThreshold = Number.isInteger(threshold)
295
+ ? uint8ToBuffer(threshold)
296
+ : this.#normalizeHexBuffer(threshold, HTLC_THRESHOLD_BYTE_LENGTH, 'HTLC threshold');
297
+ return this;
298
+ }
299
+
300
+ setHtlcCosignerSignatures(signatures = []) {
301
+ if (!Array.isArray(signatures) || signatures.length > HTLC_MAX_SIGNERS - 1) {
302
+ throw new Error(`HTLC cosigner signatures must be an array with at most ${HTLC_MAX_SIGNERS - 1} entries.`);
303
+ }
304
+ this.#htlcCosignerSignatures = signatures.map((signature, index) => this.#normalizeHexBuffer(
305
+ signature,
306
+ SIGNATURE_BYTE_LENGTH,
307
+ `HTLC cosigner signature ${index}`
308
+ ));
309
+ return this;
310
+ }
311
+
312
+ setConsensusConfig(encodedConsensusConfig) {
313
+ this.#consensusConfig = decodeConsensusConfig(encodedConsensusConfig);
314
+ this.#encodedConsensusConfig = encodedConsensusConfig;
159
315
  return this;
160
316
  }
161
317
 
@@ -240,6 +396,24 @@ class ApplyStateMessageBuilder {
240
396
  throw new Error(`${fieldName} must be a ${expectedBytes}-byte buffer or ${expectedBytes * 2}-length hexstring.`);
241
397
  }
242
398
 
399
+ #normalizeBytesBuffer(value, fieldName) {
400
+ if (b4a.isBuffer(value)) {
401
+ if (value.length === 0) {
402
+ throw new Error(`${fieldName} must be a non-empty buffer.`);
403
+ }
404
+ return value;
405
+ }
406
+
407
+ if (typeof value === 'string') {
408
+ if (!isHexString(value)) {
409
+ throw new Error(`${fieldName} must be a non-empty hexstring.`);
410
+ }
411
+ return b4a.from(value, 'hex');
412
+ }
413
+
414
+ throw new Error(`${fieldName} must be a non-empty buffer or hexstring.`);
415
+ }
416
+
243
417
  #normalizeAddress(address) {
244
418
  if (b4a.isBuffer(address)) {
245
419
  const addr = bufferToAddress(address, this.#config.addressPrefix);
@@ -251,16 +425,136 @@ class ApplyStateMessageBuilder {
251
425
  return addressToBuffer(address, this.#config.addressPrefix);
252
426
  }
253
427
 
428
+ #getHtlcLockFields() {
429
+ this.#requireFields([
430
+ [this.#txValidity, 'Transaction validity'],
431
+ [this.#htlcClaimAddress, 'HTLC claim address'],
432
+ [this.#htlcRefundAddress, 'HTLC refund address'],
433
+ [this.#amount, 'Amount'],
434
+ [this.#htlcFeeAmount, 'HTLC fee amount'],
435
+ [this.#htlcHashLock, 'HTLC hashlock'],
436
+ [this.#htlcRefundEpoch, 'HTLC refund epoch'],
437
+ [this.#htlcSignerSet, 'HTLC signer set'],
438
+ [this.#htlcThreshold, 'HTLC threshold']
439
+ ]);
440
+
441
+ const feeIsZero = this.#htlcFeeAmount.every(byte => byte === 0);
442
+ if (feeIsZero && this.#htlcFeeRecipient) {
443
+ throw new Error('HTLC fee recipient must be omitted when the fee amount is zero.');
444
+ }
445
+ if (!feeIsZero && !this.#htlcFeeRecipient) {
446
+ throw new Error('HTLC fee recipient must be set when the fee amount is non-zero.');
447
+ }
448
+
449
+ const lockerAddress = bufferToAddress(this.#address, this.#config.addressPrefix);
450
+ const lockerPublicKey = tracCryptoApi.address.decodeSafe(lockerAddress);
451
+ if (!b4a.isBuffer(lockerPublicKey) || !b4a.equals(lockerPublicKey, this.#htlcSignerSet[0])) {
452
+ throw new Error('HTLC signer zero must be the locker identified by the operation address.');
453
+ }
454
+
455
+ const signerKeys = new Set();
456
+ for (let index = 0; index < this.#htlcSignerSet.length; index++) {
457
+ const signerKey = toHex(this.#htlcSignerSet[index]);
458
+ if (signerKeys.has(signerKey)) {
459
+ throw new Error('HTLC signer set must not contain duplicate public keys.');
460
+ }
461
+ signerKeys.add(signerKey);
462
+
463
+ if (index > 1 && b4a.compare(this.#htlcSignerSet[index - 1], this.#htlcSignerSet[index]) >= 0) {
464
+ throw new Error('HTLC cosigner public keys must be sorted by raw bytes.');
465
+ }
466
+ }
467
+
468
+ const threshold = this.#htlcThreshold.readUInt8(0);
469
+ if (threshold < 1 || threshold > this.#htlcSignerSet.length) {
470
+ throw new Error('HTLC threshold must be between one and the signer-set size.');
471
+ }
472
+
473
+ if (this.#htlcCosignerSignatures.length > this.#htlcSignerSet.length - 1) {
474
+ throw new Error('HTLC cosigner signatures cannot outnumber the cosigner keys.');
475
+ }
476
+
477
+ if (1 + this.#htlcCosignerSignatures.length < threshold) {
478
+ throw new Error('HTLC signatures do not satisfy the declared signature threshold.');
479
+ }
480
+
481
+ return {
482
+ txv: this.#txValidity,
483
+ ca: this.#htlcClaimAddress,
484
+ ra: this.#htlcRefundAddress,
485
+ am: this.#amount,
486
+ fa: this.#htlcFeeAmount,
487
+ ...(this.#htlcFeeRecipient && {fr: this.#htlcFeeRecipient}),
488
+ hl: this.#htlcHashLock,
489
+ re: this.#htlcRefundEpoch,
490
+ ...(this.#htlcMetadata !== undefined && {md: this.#htlcMetadata}),
491
+ ss: this.#htlcSignerSet,
492
+ th: this.#htlcThreshold,
493
+ cs: this.#htlcCosignerSignatures
494
+ };
495
+ }
496
+
497
+ #validateHtlcLockSignatures(tx, lockerSignature) {
498
+ if (!tracCryptoApi.signature.verify(lockerSignature, tx, this.#htlcSignerSet[0])) {
499
+ throw new Error('HTLC locker signature is invalid.');
500
+ }
501
+
502
+ if (!verifyOrderedHtlcCosignerSignatures(this.#htlcCosignerSignatures, tx, this.#htlcSignerSet)) {
503
+ throw new Error('HTLC cosigner signatures are invalid or not in signer-set order.');
504
+ }
505
+ }
506
+
254
507
  async #buildPartialBody() {
255
508
  if (!isRoleAccess(this.#operationType) && !isTransaction(this.#operationType) &&
256
- !isBootstrapDeployment(this.#operationType) && !isTransfer(this.#operationType)) {
509
+ !isBootstrapDeployment(this.#operationType) && !isTransfer(this.#operationType) &&
510
+ !isHtlc(this.#operationType)) {
257
511
  throw new Error(`Operation type ${this.#operationType} is not supported for partial build.`);
258
512
  }
259
513
 
260
- const nonce = tracCryptoApi.nonce.generate();
514
+ const nonce = this.#nonce ?? tracCryptoApi.nonce.generate();
261
515
  let msg;
516
+ let htlcLockFields;
262
517
 
263
518
  switch (this.#operationType) {
519
+ case OperationType.HTLC_LOCK:
520
+ if (this.#htlcCosignerSignatures.length > 0 && !this.#nonce) {
521
+ throw new Error('HTLC nonce must be supplied when the lock includes cosigner signatures.');
522
+ }
523
+ htlcLockFields = this.#getHtlcLockFields();
524
+ msg = createHtlcLockSigningMessage(
525
+ this.#config.networkId,
526
+ this.#address,
527
+ {...htlcLockFields, in: nonce}
528
+ );
529
+ break;
530
+ case OperationType.HTLC_REFUND:
531
+ this.#requireFields([
532
+ [this.#txValidity, 'Transaction validity'],
533
+ [this.#htlcLockId, 'HTLC lock ID']
534
+ ]);
535
+ msg = createMessage(
536
+ this.#config.networkId,
537
+ this.#txValidity,
538
+ this.#htlcLockId,
539
+ nonce,
540
+ OperationType.HTLC_REFUND
541
+ );
542
+ break;
543
+ case OperationType.HTLC_CLAIM:
544
+ this.#requireFields([
545
+ [this.#txValidity, 'Transaction validity'],
546
+ [this.#htlcLockId, 'HTLC lock ID'],
547
+ [this.#htlcPreimage, 'HTLC preimage']
548
+ ]);
549
+ msg = createMessage(
550
+ this.#config.networkId,
551
+ this.#txValidity,
552
+ this.#htlcLockId,
553
+ this.#htlcPreimage,
554
+ nonce,
555
+ OperationType.HTLC_CLAIM
556
+ );
557
+ break;
264
558
  case OperationType.ADD_WRITER:
265
559
  case OperationType.REMOVE_WRITER:
266
560
  case OperationType.ADMIN_RECOVERY:
@@ -325,6 +619,7 @@ class ApplyStateMessageBuilder {
325
619
  OperationType.TRANSFER
326
620
  );
327
621
  break;
622
+
328
623
  default:
329
624
  throw new Error(`Unsupported operation type: ${this.#operationType}`);
330
625
  }
@@ -332,6 +627,10 @@ class ApplyStateMessageBuilder {
332
627
  const tx = await tracCryptoApi.hash.blake3(msg);
333
628
  const signature = this.#wallet.sign(tx);
334
629
 
630
+ if (this.#operationType === OperationType.HTLC_LOCK) {
631
+ this.#validateHtlcLockSignatures(tx, signature);
632
+ }
633
+
335
634
  if (isBootstrapDeployment(this.#operationType)) {
336
635
  return {
337
636
  tx,
@@ -373,15 +672,111 @@ class ApplyStateMessageBuilder {
373
672
  is: signature
374
673
  };
375
674
  }
675
+ if (this.#operationType === OperationType.HTLC_LOCK) {
676
+ return {
677
+ tx,
678
+ ...htlcLockFields,
679
+ in: nonce,
680
+ is: signature
681
+ };
682
+ }
683
+ if (this.#operationType === OperationType.HTLC_CLAIM) {
684
+ return {
685
+ tx,
686
+ txv: this.#txValidity,
687
+ li: this.#htlcLockId,
688
+ pi: this.#htlcPreimage,
689
+ in: nonce,
690
+ is: signature
691
+ };
692
+ }
693
+ if (this.#operationType === OperationType.HTLC_REFUND) {
694
+ return {
695
+ tx,
696
+ txv: this.#txValidity,
697
+ li: this.#htlcLockId,
698
+ in: nonce,
699
+ is: signature
700
+ };
701
+ }
376
702
 
377
703
  throw new Error(`No corresponding value type for operation: ${this.#operationType}`);
378
704
  }
379
705
 
380
706
  async #buildCompleteBody() {
707
+ if (isSetEpoch(this.#operationType)) {
708
+ this.#requireFields([
709
+ [this.#epochSchemaVersion, 'Epoch schema version'],
710
+ [this.#epochData, 'Epoch data']
711
+ ]);
712
+ return {
713
+ sv: this.#epochSchemaVersion,
714
+ data: this.#epochData
715
+ };
716
+ }
717
+
381
718
  const nonce = tracCryptoApi.nonce.generate();
382
719
  let msg;
720
+ let htlcLockFields;
383
721
 
384
722
  switch (this.#operationType) {
723
+ case OperationType.HTLC_CLAIM:
724
+ this.#requireFields([
725
+ [this.#txHash, 'Transaction hash'],
726
+ [this.#txValidity, 'Transaction validity'],
727
+ [this.#htlcLockId, 'HTLC lock ID'],
728
+ [this.#htlcPreimage, 'HTLC preimage'],
729
+ [this.#incomingNonce, 'Incoming nonce'],
730
+ [this.#incomingSignature, 'Incoming signature']
731
+ ]);
732
+ msg = createMessage(
733
+ this.#config.networkId,
734
+ this.#txHash,
735
+ nonce,
736
+ this.#operationType
737
+ );
738
+ break;
739
+ case OperationType.HTLC_LOCK:
740
+ this.#requireFields([
741
+ [this.#txHash, 'Transaction hash'],
742
+ [this.#incomingNonce, 'Incoming nonce'],
743
+ [this.#incomingSignature, 'Incoming signature']
744
+ ]);
745
+ htlcLockFields = this.#getHtlcLockFields();
746
+ {
747
+ const incomingMessage = createHtlcLockSigningMessage(
748
+ this.#config.networkId,
749
+ this.#address,
750
+ {...htlcLockFields, in: this.#incomingNonce}
751
+ );
752
+ const incomingHash = await tracCryptoApi.hash.blake3(incomingMessage);
753
+ if (!b4a.equals(incomingHash, this.#txHash)) {
754
+ throw new Error('Regenerated HTLC lock transaction does not match the incoming hash.');
755
+ }
756
+ this.#validateHtlcLockSignatures(this.#txHash, this.#incomingSignature);
757
+ }
758
+ msg = createMessage(
759
+ this.#config.networkId,
760
+ this.#txHash,
761
+ nonce,
762
+ this.#operationType
763
+ );
764
+ break;
765
+ case OperationType.HTLC_REFUND:
766
+ this.#requireFields([
767
+ [this.#txHash, 'Transaction hash'],
768
+ [this.#txValidity, 'Transaction validity'],
769
+ [this.#htlcLockId, 'HTLC lock ID'],
770
+ [this.#incomingNonce, 'Incoming nonce'],
771
+ [this.#incomingSignature, 'Incoming signature']
772
+ ]);
773
+ msg = createMessage(
774
+ this.#config.networkId,
775
+ this.#txHash,
776
+ nonce,
777
+ OperationType.HTLC_REFUND
778
+ );
779
+ break;
385
780
  case OperationType.ADD_ADMIN:
386
781
  case OperationType.DISABLE_INITIALIZATION:
387
782
  this.#requireFields([
@@ -499,6 +894,20 @@ class ApplyStateMessageBuilder {
499
894
  this.#operationType
500
895
  );
501
896
  break;
897
+ case OperationType.SET_GENESIS_EPOCH:
898
+ case OperationType.SET_CONSENSUS_CONFIG:
899
+ this.#requireFields([
900
+ [this.#txValidity, 'Transaction validity'],
901
+ [this.#encodedConsensusConfig, 'Consensus config']
902
+ ]);
903
+ msg = createMessage(
904
+ this.#config.networkId,
905
+ this.#txValidity,
906
+ this.#encodedConsensusConfig,
907
+ nonce,
908
+ this.#operationType
909
+ );
910
+ break;
502
911
  default:
503
912
  throw new Error(`Unsupported operation type: ${this.#operationType}`);
504
913
  }
@@ -578,6 +987,42 @@ class ApplyStateMessageBuilder {
578
987
  vs: signature
579
988
  };
580
989
  }
990
+ if (this.#operationType === OperationType.HTLC_CLAIM) {
991
+ return {
992
+ tx: this.#txHash,
993
+ txv: this.#txValidity,
994
+ li: this.#htlcLockId,
995
+ pi: this.#htlcPreimage,
996
+ in: this.#incomingNonce,
997
+ is: this.#incomingSignature,
998
+ va: validatorAddress,
999
+ vn: nonce,
1000
+ vs: signature
1001
+ };
1002
+ }
1003
+ if (this.#operationType === OperationType.HTLC_LOCK) {
1004
+ return {
1005
+ tx: this.#txHash,
1006
+ ...htlcLockFields,
1007
+ in: this.#incomingNonce,
1008
+ is: this.#incomingSignature,
1009
+ va: validatorAddress,
1010
+ vn: nonce,
1011
+ vs: signature
1012
+ };
1013
+ }
1014
+ if (this.#operationType === OperationType.HTLC_REFUND) {
1015
+ return {
1016
+ tx: this.#txHash,
1017
+ txv: this.#txValidity,
1018
+ li: this.#htlcLockId,
1019
+ in: this.#incomingNonce,
1020
+ is: this.#incomingSignature,
1021
+ va: validatorAddress,
1022
+ vn: nonce,
1023
+ vs: signature
1024
+ };
1025
+ }
581
1026
  if (isBalanceInitialization(this.#operationType)) {
582
1027
  return {
583
1028
  tx,
@@ -588,12 +1033,20 @@ class ApplyStateMessageBuilder {
588
1033
  is: signature
589
1034
  };
590
1035
  }
1036
+ if (isConsensusControl(this.#operationType)) {
1037
+ return {
1038
+ tx,
1039
+ txv: this.#txValidity,
1040
+ cc: this.#consensusConfig,
1041
+ in: nonce,
1042
+ is: signature
1043
+ };
1044
+ }
591
1045
 
592
1046
  throw new Error(`No corresponding value type for operation: ${this.#operationType}`);
593
1047
  }
594
1048
 
595
1049
  #encodePayloadJson(payload) {
596
- const toHex = buffer => buffer.toString('hex');
597
1050
  const address = bufferToAddress(payload.address, this.#config.addressPrefix);
598
1051
  if (!address) {
599
1052
  throw new Error('Payload address is invalid.');
@@ -652,6 +1105,39 @@ class ApplyStateMessageBuilder {
652
1105
  is: toHex(body.is)
653
1106
  }
654
1107
  };
1108
+ case 'hlo': {
1109
+ const htlcLock = {
1110
+ tx: toHex(body.tx),
1111
+ txv: toHex(body.txv),
1112
+ ca: bufferToAddress(body.ca, this.#config.addressPrefix),
1113
+ ra: bufferToAddress(body.ra, this.#config.addressPrefix),
1114
+ am: toHex(body.am),
1115
+ fa: toHex(body.fa),
1116
+ hl: toHex(body.hl),
1117
+ re: toHex(body.re),
1118
+ cc: toHex(body.cc),
1119
+ ss: body.ss.map(toHex),
1120
+ th: toHex(body.th),
1121
+ cs: body.cs.map(toHex),
1122
+ in: toHex(body.in),
1123
+ is: toHex(body.is)
1124
+ };
1125
+ if (body.fr) htlcLock.fr = bufferToAddress(body.fr, this.#config.addressPrefix);
1126
+ if (body.md !== undefined) htlcLock.md = toHex(body.md);
1127
+
1128
+ return {...base, hlo: htlcLock};
1129
+ }
1130
+ case 'hro':
1131
+ return {
1132
+ ...base,
1133
+ hro: {
1134
+ tx: toHex(body.tx),
1135
+ txv: toHex(body.txv),
1136
+ li: toHex(body.li),
1137
+ in: toHex(body.in),
1138
+ is: toHex(body.is)
1139
+ }
1140
+ };
655
1141
  default:
656
1142
  throw new Error(`JSON output is not supported for payload ${this.#payloadKey}.`);
657
1143
  }