trac-msb 0.2.8 → 0.2.9

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 (123) hide show
  1. package/msb.mjs +3 -3
  2. package/package.json +8 -3
  3. package/proto/network.proto +74 -0
  4. package/rpc/create_server.js +2 -2
  5. package/rpc/handlers.js +2 -2
  6. package/rpc/rpc_server.js +2 -2
  7. package/rpc/rpc_services.js +44 -3
  8. package/rpc/utils/helpers.js +1 -1
  9. package/src/config/env.js +2 -0
  10. package/src/core/network/Network.js +29 -61
  11. package/src/core/network/identity/NetworkWalletFactory.js +2 -2
  12. package/src/core/network/protocols/LegacyProtocol.js +67 -0
  13. package/src/core/network/protocols/NetworkMessages.js +48 -0
  14. package/src/core/network/protocols/ProtocolInterface.js +31 -0
  15. package/src/core/network/protocols/ProtocolSession.js +59 -0
  16. package/src/core/network/protocols/V1Protocol.js +64 -0
  17. package/src/core/network/protocols/legacy/NetworkMessageRouter.js +84 -0
  18. package/src/core/network/protocols/legacy/handlers/GetRequestHandler.js +53 -0
  19. package/src/core/network/protocols/legacy/handlers/ResponseHandler.js +37 -0
  20. package/src/core/network/{messaging → protocols/legacy}/validators/base/BaseResponse.js +1 -2
  21. package/src/core/network/protocols/shared/handlers/RoleOperationHandler.js +88 -0
  22. package/src/core/network/protocols/shared/handlers/SubnetworkOperationHandler.js +93 -0
  23. package/src/core/network/{messaging → protocols/shared}/handlers/TransferOperationHandler.js +16 -15
  24. package/src/core/network/{messaging → protocols/shared}/handlers/base/BaseOperationHandler.js +7 -11
  25. package/src/core/network/{messaging → protocols/shared}/validators/PartialBootstrapDeployment.js +2 -2
  26. package/src/core/network/{messaging → protocols/shared}/validators/PartialRoleAccess.js +5 -5
  27. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransaction.js +4 -4
  28. package/src/core/network/{messaging → protocols/shared}/validators/PartialTransfer.js +4 -4
  29. package/src/core/network/{messaging → protocols/shared}/validators/base/PartialOperation.js +14 -12
  30. package/src/core/network/protocols/v1/NetworkMessageRouter.js +15 -0
  31. package/src/core/network/services/ConnectionManager.js +4 -4
  32. package/src/core/network/services/MessageOrchestrator.js +1 -1
  33. package/src/core/network/services/TransactionPoolService.js +1 -2
  34. package/src/core/network/services/TransactionRateLimiterService.js +5 -3
  35. package/src/core/state/State.js +1 -2
  36. package/src/index.js +153 -180
  37. package/src/messages/network/v1/NetworkMessageBuilder.js +325 -0
  38. package/src/messages/network/v1/NetworkMessageDirector.js +137 -0
  39. package/src/messages/network/v1/networkMessageFactory.js +12 -0
  40. package/src/messages/state/ApplyStateMessageBuilder.js +661 -0
  41. package/src/messages/state/ApplyStateMessageDirector.js +516 -0
  42. package/src/messages/state/applyStateMessageFactory.js +12 -0
  43. package/src/utils/buffer.js +53 -1
  44. package/src/utils/cli.js +0 -8
  45. package/src/utils/constants.js +34 -14
  46. package/src/utils/normalizers.js +84 -2
  47. package/src/utils/protobuf/network.cjs +840 -0
  48. package/src/utils/protobuf/operationHelpers.js +10 -0
  49. package/tests/acceptance/v1/rpc.test.mjs +1 -1
  50. package/tests/fixtures/networkV1.fixtures.js +84 -0
  51. package/tests/fixtures/protobuf.fixtures.js +83 -0
  52. package/tests/helpers/config.js +1 -1
  53. package/tests/helpers/setupApplyTests.js +53 -46
  54. package/tests/unit/messages/messages.test.js +12 -0
  55. package/tests/unit/messages/network/NetworkMessageBuilder.test.js +276 -0
  56. package/tests/unit/messages/network/NetworkMessageDirector.test.js +203 -0
  57. package/tests/unit/messages/state/applyStateMessageBuilder.complete.test.js +521 -0
  58. package/tests/unit/messages/state/applyStateMessageBuilder.partial.test.js +233 -0
  59. package/tests/unit/network/ConnectionManager.test.js +6 -5
  60. package/tests/unit/network/networkModule.test.js +3 -2
  61. package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +10 -6
  62. package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +9 -6
  63. package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +10 -7
  64. package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +18 -21
  65. package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +53 -38
  66. package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +46 -35
  67. package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +13 -16
  68. package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +17 -11
  69. package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +11 -12
  70. package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +9 -7
  71. package/tests/unit/state/apply/common/commonScenarioHelper.js +15 -14
  72. package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +9 -4
  73. package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +17 -11
  74. package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +19 -14
  75. package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +37 -29
  76. package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +9 -7
  77. package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +11 -9
  78. package/tests/unit/unit.test.js +1 -1
  79. package/tests/unit/utils/buffer/buffer.test.js +62 -1
  80. package/tests/unit/utils/normalizers/normalizers.test.js +469 -0
  81. package/tests/unit/utils/protobuf/operationHelpers.test.js +120 -2
  82. package/docs/networking-dualstack-plan.md +0 -75
  83. package/docs/networking-layer-redesign.md +0 -155
  84. package/src/core/network/messaging/NetworkMessages.js +0 -64
  85. package/src/core/network/messaging/handlers/GetRequestHandler.js +0 -113
  86. package/src/core/network/messaging/handlers/ResponseHandler.js +0 -107
  87. package/src/core/network/messaging/handlers/RoleOperationHandler.js +0 -114
  88. package/src/core/network/messaging/handlers/SubnetworkOperationHandler.js +0 -149
  89. package/src/core/network/messaging/routes/NetworkMessageRouter.js +0 -98
  90. package/src/core/network/messaging/validators/AdminResponse.js +0 -58
  91. package/src/core/network/messaging/validators/CustomNodeResponse.js +0 -46
  92. package/src/messages/base/StateBuilder.js +0 -25
  93. package/src/messages/completeStateMessages/CompleteStateMessageBuilder.js +0 -425
  94. package/src/messages/completeStateMessages/CompleteStateMessageDirector.js +0 -252
  95. package/src/messages/completeStateMessages/CompleteStateMessageOperations.js +0 -296
  96. package/src/messages/partialStateMessages/PartialStateMessageBuilder.js +0 -272
  97. package/src/messages/partialStateMessages/PartialStateMessageDirector.js +0 -137
  98. package/src/messages/partialStateMessages/PartialStateMessageOperations.js +0 -138
  99. package/tests/integration/apply/addAdmin/addAdminBasic.test.js +0 -69
  100. package/tests/integration/apply/addAdmin/addAdminRecovery.test.js +0 -126
  101. package/tests/integration/apply/addIndexer.test.js +0 -239
  102. package/tests/integration/apply/addWhitelist.test.js +0 -53
  103. package/tests/integration/apply/addWriter.test.js +0 -245
  104. package/tests/integration/apply/apply.test.js +0 -19
  105. package/tests/integration/apply/banValidator.test.js +0 -116
  106. package/tests/integration/apply/postTx/invalidSubValues.test.js +0 -103
  107. package/tests/integration/apply/postTx/postTx.test.js +0 -196
  108. package/tests/integration/apply/removeIndexer.test.js +0 -132
  109. package/tests/integration/apply/removeWriter.test.js +0 -168
  110. package/tests/integration/apply/transfer.test.js +0 -83
  111. package/tests/integration/integration.test.js +0 -9
  112. package/tests/unit/messageOperations/assembleAddIndexerMessage.test.js +0 -21
  113. package/tests/unit/messageOperations/assembleAddWriterMessage.test.js +0 -17
  114. package/tests/unit/messageOperations/assembleAdminMessage.test.js +0 -68
  115. package/tests/unit/messageOperations/assembleBanWriterMessage.test.js +0 -17
  116. package/tests/unit/messageOperations/assemblePostTransaction.test.js +0 -424
  117. package/tests/unit/messageOperations/assembleRemoveIndexerMessage.test.js +0 -19
  118. package/tests/unit/messageOperations/assembleRemoveWriterMessage.test.js +0 -17
  119. package/tests/unit/messageOperations/assembleWhitelistMessages.test.js +0 -59
  120. package/tests/unit/messageOperations/commonsStateMessageOperationsTest.js +0 -278
  121. package/tests/unit/messageOperations/stateMessageOperations.test.js +0 -19
  122. /package/src/core/network/{messaging → protocols/legacy}/validators/ValidatorResponse.js +0 -0
  123. /package/src/utils/{operations.js → applyOperations.js} +0 -0
@@ -1,6 +1,5 @@
1
1
  import b4a from 'b4a';
2
- import PartialStateMessageOperations from '../../../../../src/messages/partialStateMessages/PartialStateMessageOperations.js';
3
- import CompleteStateMessageOperations from '../../../../../src/messages/completeStateMessages/CompleteStateMessageOperations.js';
2
+ import { applyStateMessageFactory } from '../../../../../src/messages/state/applyStateMessageFactory.js';
4
3
  import {
5
4
  deriveIndexerSequenceState,
6
5
  eventFlush
@@ -139,20 +138,22 @@ export async function buildTxOperationPayload(
139
138
  writerKeyBuffer = broadcasterPeer.base.local.key
140
139
  } = {}
141
140
  ) {
142
- const resolvedTxValidity = txValidity ?? (await deriveIndexerSequenceState(validatorPeer.base));
141
+ const resolvedTxValidity = txValidity ?? (await deriveIndexerSequenceState(validatorPeer.base));
143
142
 
144
- const partial = await new PartialStateMessageOperations(broadcasterPeer.wallet, config)
145
- .assembleTransactionOperationMessage(
143
+ const partial = await applyStateMessageFactory(broadcasterPeer.wallet, config)
144
+ .buildPartialTransactionOperationMessage(
145
+ broadcasterPeer.wallet.address,
146
146
  writerKeyBuffer.toString('hex'),
147
147
  resolvedTxValidity.toString('hex'),
148
148
  contentHash.toString('hex'),
149
149
  externalBootstrap.toString('hex'),
150
- msbBootstrap.toString('hex')
150
+ msbBootstrap.toString('hex'),
151
+ 'json'
151
152
  );
152
153
 
153
- return new CompleteStateMessageOperations(validatorPeer.wallet, config)
154
- .assembleCompleteTransactionOperationMessage(
155
- partial.address,
154
+ const payload = await applyStateMessageFactory(validatorPeer.wallet, config)
155
+ .buildCompleteTransactionOperationMessage(
156
+ partial.address,
156
157
  b4a.from(partial.txo.tx, 'hex'),
157
158
  b4a.from(partial.txo.txv, 'hex'),
158
159
  b4a.from(partial.txo.iw, 'hex'),
@@ -162,6 +163,7 @@ export async function buildTxOperationPayload(
162
163
  b4a.from(partial.txo.bs, 'hex'),
163
164
  b4a.from(partial.txo.mbs, 'hex')
164
165
  );
166
+ return safeEncodeApplyOperation(payload);
165
167
  }
166
168
 
167
169
  export async function buildTxOperationPayloadWithTxValidity(context, txValidity, options = {}) {
@@ -7,7 +7,7 @@ async function runTests() {
7
7
  await import('./network/networkModule.test.js')
8
8
  await import('./state/stateModule.test.js');
9
9
  await import('./utils/utils.test.js');
10
- // await import('./messageOperations/stateMessageOperations.test.js'); // Broken test - needs fixing
10
+ await import('./messages/messages.test.js');
11
11
  test.resume();
12
12
  }
13
13
 
@@ -1,6 +1,7 @@
1
1
  import test from 'brittle';
2
2
  import b4a from 'b4a';
3
- import {createMessage, isBufferValid, safeWriteUInt32BE, deepCopyBuffer} from '../../../../src/utils/buffer.js';
3
+ import { createMessage, isBufferValid, safeWriteUInt32BE, deepCopyBuffer, encodeCapabilities, timestampToBuffer, idToBuffer } from '../../../../src/utils/buffer.js';
4
+ import { errorMessageIncludes } from "../../../helpers/regexHelper.js";
4
5
 
5
6
  const invalidDataTypes = [
6
7
  null,
@@ -188,3 +189,63 @@ test('deepCopyBuffer - modifying copy does not affect original (is not a referen
188
189
  t.is(buf[0], 9, 'original unchanged');
189
190
  t.is(copy[0], 1, 'copy modified independently');
190
191
  });
192
+
193
+ test('encodeCapabilities - deterministic ordering and encoding', t => {
194
+ const caps = ['cap-b', 'cap-a'];
195
+ const result = encodeCapabilities(caps);
196
+
197
+ const capA = b4a.from('cap-a', 'utf8');
198
+ const capB = b4a.from('cap-b', 'utf8');
199
+
200
+ const expected = b4a.concat([
201
+ b4a.from([0x00, capA.length]),
202
+ capA,
203
+ b4a.from([0x00, capB.length]),
204
+ capB
205
+ ]);
206
+
207
+ t.is(result.length, expected.length, 'length matches');
208
+ t.ok(b4a.equals(result, expected), 'ordering is sorted and encoding matches');
209
+ });
210
+
211
+ test('encodeCapabilities - empty array yields empty buffer', t => {
212
+ const result = encodeCapabilities([]);
213
+ t.ok(b4a.isBuffer(result), 'returns a buffer');
214
+ t.is(result.length, 0, 'empty buffer for no caps');
215
+ });
216
+
217
+ test('encodeCapabilities - throws on invalid input', t => {
218
+ t.exception(
219
+ () => encodeCapabilities('not-array'),
220
+ errorMessageIncludes('Capabilities must be an array')
221
+ );
222
+
223
+ t.exception(
224
+ () => encodeCapabilities([1, 2]),
225
+ errorMessageIncludes('must contain only strings')
226
+ );
227
+ });
228
+
229
+ test('timestampToBuffer - encodes uint64 BE', t => {
230
+ const ts = 2n ** 53n; // beyond uint32
231
+
232
+ const tsBuf = timestampToBuffer(ts);
233
+
234
+ t.is(tsBuf.length, 8);
235
+ t.is(tsBuf.readBigUInt64BE(0), ts);
236
+ });
237
+
238
+ test('idToBuffer - encodes utf8 string', t => {
239
+ const id = 'test-id';
240
+ const idBuf = idToBuffer(id);
241
+ t.ok(b4a.isBuffer(idBuf));
242
+ t.ok(b4a.equals(idBuf, b4a.from(id, 'utf8')));
243
+ });
244
+
245
+ test('timestampToBuffer and idToBuffer - reject invalid input', t => {
246
+ t.exception(() => timestampToBuffer(-1), errorMessageIncludes('timestamp'));
247
+ t.exception(() => timestampToBuffer(1.5), errorMessageIncludes('timestamp'));
248
+ t.exception(() => timestampToBuffer('1'), errorMessageIncludes('timestamp'));
249
+ t.exception.all(() => idToBuffer(1));
250
+ t.exception.all(() => idToBuffer(null));
251
+ });
@@ -0,0 +1,469 @@
1
+ import { test } from 'brittle';
2
+ import b4a from 'b4a';
3
+
4
+ import { config } from '../../../helpers/config.js';
5
+ import { randomAddress } from '../../state/stateTestUtils.js';
6
+ import { errorMessageIncludes } from '../../../helpers/regexHelper.js';
7
+ import { OperationType } from '../../../../src/utils/constants.js';
8
+ import {
9
+ normalizeBootstrapDeploymentOperation,
10
+ normalizeDecodedPayloadForJson,
11
+ normalizeRoleAccessOperation,
12
+ normalizeTransactionOperation,
13
+ normalizeTransferOperation
14
+ } from '../../../../src/utils/normalizers.js';
15
+ import { addressToBuffer } from '../../../../src/core/state/utils/address.js';
16
+ import { bigIntTo16ByteBuffer } from '../../../../src/utils/amountSerialization.js';
17
+
18
+ const hex = (value, bytes) => value.repeat(bytes);
19
+ const toBuffer = value => b4a.from(value, 'hex');
20
+
21
+ test('normalizeTransferOperation normalizes hex strings and addresses', t => {
22
+ const sender = randomAddress(config.addressPrefix);
23
+ const recipient = randomAddress(config.addressPrefix);
24
+ const tx = hex('11', 32);
25
+ const txv = hex('22', 32);
26
+ const nonce = hex('33', 32);
27
+ const amount = hex('44', 16);
28
+ const signature = hex('55', 64);
29
+
30
+ const payload = {
31
+ type: OperationType.TRANSFER,
32
+ address: sender,
33
+ tro: {
34
+ tx,
35
+ txv,
36
+ in: nonce,
37
+ to: recipient,
38
+ am: amount,
39
+ is: signature
40
+ }
41
+ };
42
+
43
+ const normalized = normalizeTransferOperation(payload, config);
44
+
45
+ t.is(normalized.type, OperationType.TRANSFER);
46
+ t.ok(b4a.equals(normalized.address, addressToBuffer(sender, config.addressPrefix)));
47
+ t.ok(b4a.equals(normalized.tro.tx, toBuffer(tx)));
48
+ t.ok(b4a.equals(normalized.tro.txv, toBuffer(txv)));
49
+ t.ok(b4a.equals(normalized.tro.in, toBuffer(nonce)));
50
+ t.ok(b4a.equals(normalized.tro.to, addressToBuffer(recipient, config.addressPrefix)));
51
+ t.ok(b4a.equals(normalized.tro.am, toBuffer(amount)));
52
+ t.ok(b4a.equals(normalized.tro.is, toBuffer(signature)));
53
+ });
54
+
55
+ test('normalizeTransferOperation accepts buffer inputs', t => {
56
+ const sender = randomAddress(config.addressPrefix);
57
+ const recipient = randomAddress(config.addressPrefix);
58
+ const tx = toBuffer(hex('aa', 32));
59
+ const txv = toBuffer(hex('bb', 32));
60
+ const nonce = toBuffer(hex('cc', 32));
61
+ const amount = toBuffer(hex('dd', 16));
62
+ const signature = toBuffer(hex('ee', 64));
63
+
64
+ const payload = {
65
+ type: OperationType.TRANSFER,
66
+ address: sender,
67
+ tro: {
68
+ tx,
69
+ txv,
70
+ in: nonce,
71
+ to: recipient,
72
+ am: amount,
73
+ is: signature
74
+ }
75
+ };
76
+
77
+ const normalized = normalizeTransferOperation(payload, config);
78
+ t.ok(b4a.equals(normalized.tro.tx, tx));
79
+ t.ok(b4a.equals(normalized.tro.txv, txv));
80
+ t.ok(b4a.equals(normalized.tro.in, nonce));
81
+ t.ok(b4a.equals(normalized.tro.am, amount));
82
+ t.ok(b4a.equals(normalized.tro.is, signature));
83
+ });
84
+
85
+ test('normalizeTransferOperation throws on missing payload fields', t => {
86
+ t.exception(
87
+ () => normalizeTransferOperation({ type: OperationType.TRANSFER }, config),
88
+ errorMessageIncludes('Invalid payload for transfer operation normalization.')
89
+ );
90
+
91
+ t.exception(
92
+ () => normalizeTransferOperation({ type: OperationType.TX, address: 'x', tro: {} }, config),
93
+ errorMessageIncludes('Missing required fields in transfer operation payload.')
94
+ );
95
+
96
+ const sender = randomAddress(config.addressPrefix);
97
+ const payload = {
98
+ type: OperationType.TRANSFER,
99
+ address: sender,
100
+ tro: {
101
+ tx: hex('11', 32),
102
+ txv: hex('22', 32),
103
+ in: hex('33', 32),
104
+ to: randomAddress(config.addressPrefix),
105
+ am: hex('44', 16)
106
+ }
107
+ };
108
+ t.exception(
109
+ () => normalizeTransferOperation(payload, config),
110
+ errorMessageIncludes('Missing required fields in transfer operation payload.')
111
+ );
112
+ });
113
+
114
+ test('normalizeTransferOperation throws on invalid hex string', t => {
115
+ const sender = randomAddress(config.addressPrefix);
116
+ const payload = {
117
+ type: OperationType.TRANSFER,
118
+ address: sender,
119
+ tro: {
120
+ tx: 'zz',
121
+ txv: hex('22', 32),
122
+ in: hex('33', 32),
123
+ to: randomAddress(config.addressPrefix),
124
+ am: hex('44', 16),
125
+ is: hex('55', 64)
126
+ }
127
+ };
128
+ t.exception(
129
+ () => normalizeTransferOperation(payload, config),
130
+ errorMessageIncludes('Invalid hex string')
131
+ );
132
+ });
133
+
134
+ test('normalizeTransactionOperation normalizes hex strings and addresses', t => {
135
+ const sender = randomAddress(config.addressPrefix);
136
+ const tx = hex('11', 32);
137
+ const txv = hex('22', 32);
138
+ const writerKey = hex('33', 32);
139
+ const contentHash = hex('44', 32);
140
+ const bootstrap = hex('55', 32);
141
+ const msbBootstrap = hex('66', 32);
142
+ const nonce = hex('77', 32);
143
+ const signature = hex('88', 64);
144
+
145
+ const payload = {
146
+ type: OperationType.TX,
147
+ address: sender,
148
+ txo: {
149
+ tx,
150
+ txv,
151
+ iw: writerKey,
152
+ ch: contentHash,
153
+ bs: bootstrap,
154
+ mbs: msbBootstrap,
155
+ in: nonce,
156
+ is: signature
157
+ }
158
+ };
159
+
160
+ const normalized = normalizeTransactionOperation(payload, config);
161
+ t.is(normalized.type, OperationType.TX);
162
+ t.ok(b4a.equals(normalized.address, addressToBuffer(sender, config.addressPrefix)));
163
+ t.ok(b4a.equals(normalized.txo.tx, toBuffer(tx)));
164
+ t.ok(b4a.equals(normalized.txo.txv, toBuffer(txv)));
165
+ t.ok(b4a.equals(normalized.txo.iw, toBuffer(writerKey)));
166
+ t.ok(b4a.equals(normalized.txo.ch, toBuffer(contentHash)));
167
+ t.ok(b4a.equals(normalized.txo.bs, toBuffer(bootstrap)));
168
+ t.ok(b4a.equals(normalized.txo.mbs, toBuffer(msbBootstrap)));
169
+ t.ok(b4a.equals(normalized.txo.in, toBuffer(nonce)));
170
+ t.ok(b4a.equals(normalized.txo.is, toBuffer(signature)));
171
+ });
172
+
173
+ test('normalizeTransactionOperation accepts buffer inputs', t => {
174
+ const sender = randomAddress(config.addressPrefix);
175
+ const tx = toBuffer(hex('aa', 32));
176
+ const txv = toBuffer(hex('bb', 32));
177
+ const writerKey = toBuffer(hex('cc', 32));
178
+ const contentHash = toBuffer(hex('dd', 32));
179
+ const bootstrap = toBuffer(hex('ee', 32));
180
+ const msbBootstrap = toBuffer(hex('ff', 32));
181
+ const nonce = toBuffer(hex('12', 32));
182
+ const signature = toBuffer(hex('34', 64));
183
+
184
+ const payload = {
185
+ type: OperationType.TX,
186
+ address: sender,
187
+ txo: {
188
+ tx,
189
+ txv,
190
+ iw: writerKey,
191
+ ch: contentHash,
192
+ bs: bootstrap,
193
+ mbs: msbBootstrap,
194
+ in: nonce,
195
+ is: signature
196
+ }
197
+ };
198
+
199
+ const normalized = normalizeTransactionOperation(payload, config);
200
+ t.ok(b4a.equals(normalized.txo.tx, tx));
201
+ t.ok(b4a.equals(normalized.txo.txv, txv));
202
+ t.ok(b4a.equals(normalized.txo.iw, writerKey));
203
+ t.ok(b4a.equals(normalized.txo.ch, contentHash));
204
+ t.ok(b4a.equals(normalized.txo.bs, bootstrap));
205
+ t.ok(b4a.equals(normalized.txo.mbs, msbBootstrap));
206
+ t.ok(b4a.equals(normalized.txo.in, nonce));
207
+ t.ok(b4a.equals(normalized.txo.is, signature));
208
+ });
209
+
210
+ test('normalizeTransactionOperation throws on missing payload fields', t => {
211
+ t.exception(
212
+ () => normalizeTransactionOperation({ type: OperationType.TX }, config),
213
+ errorMessageIncludes('Invalid payload for transaction operation normalization.')
214
+ );
215
+
216
+ t.exception(
217
+ () => normalizeTransactionOperation({ type: OperationType.TRANSFER, address: 'x', txo: {} }, config),
218
+ errorMessageIncludes('Missing required fields in transaction operation payload.')
219
+ );
220
+
221
+ const sender = randomAddress(config.addressPrefix);
222
+ const payload = {
223
+ type: OperationType.TX,
224
+ address: sender,
225
+ txo: {
226
+ tx: hex('11', 32),
227
+ txv: hex('22', 32),
228
+ iw: hex('33', 32),
229
+ ch: hex('44', 32),
230
+ bs: hex('55', 32),
231
+ mbs: hex('66', 32),
232
+ in: hex('77', 32)
233
+ }
234
+ };
235
+ t.exception(
236
+ () => normalizeTransactionOperation(payload, config),
237
+ errorMessageIncludes('Missing required fields in transaction operation payload.')
238
+ );
239
+ });
240
+
241
+ test('normalizeTransactionOperation throws on invalid hex string', t => {
242
+ const sender = randomAddress(config.addressPrefix);
243
+ const payload = {
244
+ type: OperationType.TX,
245
+ address: sender,
246
+ txo: {
247
+ tx: 'zz',
248
+ txv: hex('22', 32),
249
+ iw: hex('33', 32),
250
+ ch: hex('44', 32),
251
+ bs: hex('55', 32),
252
+ mbs: hex('66', 32),
253
+ in: hex('77', 32),
254
+ is: hex('88', 64)
255
+ }
256
+ };
257
+ t.exception(
258
+ () => normalizeTransactionOperation(payload, config),
259
+ errorMessageIncludes('Invalid hex string')
260
+ );
261
+ });
262
+
263
+ test('normalizeRoleAccessOperation normalizes hex strings and addresses', t => {
264
+ const sender = randomAddress(config.addressPrefix);
265
+ const payload = {
266
+ type: OperationType.ADD_WRITER,
267
+ address: sender,
268
+ rao: {
269
+ tx: hex('11', 32),
270
+ txv: hex('22', 32),
271
+ iw: hex('33', 32),
272
+ in: hex('44', 32),
273
+ is: hex('55', 64)
274
+ }
275
+ };
276
+
277
+ const normalized = normalizeRoleAccessOperation(payload, config);
278
+ t.is(normalized.type, OperationType.ADD_WRITER);
279
+ t.ok(b4a.equals(normalized.address, addressToBuffer(sender, config.addressPrefix)));
280
+ t.ok(b4a.equals(normalized.rao.tx, toBuffer(hex('11', 32))));
281
+ t.ok(b4a.equals(normalized.rao.txv, toBuffer(hex('22', 32))));
282
+ t.ok(b4a.equals(normalized.rao.iw, toBuffer(hex('33', 32))));
283
+ t.ok(b4a.equals(normalized.rao.in, toBuffer(hex('44', 32))));
284
+ t.ok(b4a.equals(normalized.rao.is, toBuffer(hex('55', 64))));
285
+ });
286
+
287
+ test('normalizeRoleAccessOperation accepts buffer inputs', t => {
288
+ const sender = randomAddress(config.addressPrefix);
289
+ const tx = toBuffer(hex('aa', 32));
290
+ const txv = toBuffer(hex('bb', 32));
291
+ const writerKey = toBuffer(hex('cc', 32));
292
+ const nonce = toBuffer(hex('dd', 32));
293
+ const signature = toBuffer(hex('ee', 64));
294
+
295
+ const payload = {
296
+ type: OperationType.REMOVE_WRITER,
297
+ address: sender,
298
+ rao: {
299
+ tx,
300
+ txv,
301
+ iw: writerKey,
302
+ in: nonce,
303
+ is: signature
304
+ }
305
+ };
306
+
307
+ const normalized = normalizeRoleAccessOperation(payload, config);
308
+ t.ok(b4a.equals(normalized.rao.tx, tx));
309
+ t.ok(b4a.equals(normalized.rao.txv, txv));
310
+ t.ok(b4a.equals(normalized.rao.iw, writerKey));
311
+ t.ok(b4a.equals(normalized.rao.in, nonce));
312
+ t.ok(b4a.equals(normalized.rao.is, signature));
313
+ });
314
+
315
+ test('normalizeRoleAccessOperation throws on missing payload fields', t => {
316
+ t.exception(
317
+ () => normalizeRoleAccessOperation({ type: OperationType.ADD_WRITER }, config),
318
+ errorMessageIncludes('Invalid payload for role access normalization.')
319
+ );
320
+
321
+ t.exception(
322
+ () => normalizeRoleAccessOperation({ type: OperationType.ADD_WRITER, address: 'x', rao: {} }, config),
323
+ errorMessageIncludes('Missing required fields in role access payload.')
324
+ );
325
+ });
326
+
327
+ test('normalizeRoleAccessOperation throws on invalid hex string', t => {
328
+ const sender = randomAddress(config.addressPrefix);
329
+ const payload = {
330
+ type: OperationType.ADD_WRITER,
331
+ address: sender,
332
+ rao: {
333
+ tx: 'zz',
334
+ txv: hex('22', 32),
335
+ iw: hex('33', 32),
336
+ in: hex('44', 32),
337
+ is: hex('55', 64)
338
+ }
339
+ };
340
+ t.exception(
341
+ () => normalizeRoleAccessOperation(payload, config),
342
+ errorMessageIncludes('Invalid hex string')
343
+ );
344
+ });
345
+
346
+ test('normalizeBootstrapDeploymentOperation normalizes hex strings and addresses', t => {
347
+ const sender = randomAddress(config.addressPrefix);
348
+ const payload = {
349
+ type: OperationType.BOOTSTRAP_DEPLOYMENT,
350
+ address: sender,
351
+ bdo: {
352
+ tx: hex('11', 32),
353
+ txv: hex('22', 32),
354
+ bs: hex('33', 32),
355
+ ic: hex('44', 32),
356
+ in: hex('55', 32),
357
+ is: hex('66', 64)
358
+ }
359
+ };
360
+
361
+ const normalized = normalizeBootstrapDeploymentOperation(payload, config);
362
+ t.is(normalized.type, OperationType.BOOTSTRAP_DEPLOYMENT);
363
+ t.ok(b4a.equals(normalized.address, addressToBuffer(sender, config.addressPrefix)));
364
+ t.ok(b4a.equals(normalized.bdo.tx, toBuffer(hex('11', 32))));
365
+ t.ok(b4a.equals(normalized.bdo.txv, toBuffer(hex('22', 32))));
366
+ t.ok(b4a.equals(normalized.bdo.bs, toBuffer(hex('33', 32))));
367
+ t.ok(b4a.equals(normalized.bdo.ic, toBuffer(hex('44', 32))));
368
+ t.ok(b4a.equals(normalized.bdo.in, toBuffer(hex('55', 32))));
369
+ t.ok(b4a.equals(normalized.bdo.is, toBuffer(hex('66', 64))));
370
+ });
371
+
372
+ test('normalizeBootstrapDeploymentOperation accepts buffer inputs', t => {
373
+ const sender = randomAddress(config.addressPrefix);
374
+ const tx = toBuffer(hex('aa', 32));
375
+ const txv = toBuffer(hex('bb', 32));
376
+ const bootstrap = toBuffer(hex('cc', 32));
377
+ const channel = toBuffer(hex('dd', 32));
378
+ const nonce = toBuffer(hex('ee', 32));
379
+ const signature = toBuffer(hex('ff', 64));
380
+
381
+ const payload = {
382
+ type: OperationType.BOOTSTRAP_DEPLOYMENT,
383
+ address: sender,
384
+ bdo: {
385
+ tx,
386
+ txv,
387
+ bs: bootstrap,
388
+ ic: channel,
389
+ in: nonce,
390
+ is: signature
391
+ }
392
+ };
393
+
394
+ const normalized = normalizeBootstrapDeploymentOperation(payload, config);
395
+ t.ok(b4a.equals(normalized.bdo.tx, tx));
396
+ t.ok(b4a.equals(normalized.bdo.txv, txv));
397
+ t.ok(b4a.equals(normalized.bdo.bs, bootstrap));
398
+ t.ok(b4a.equals(normalized.bdo.ic, channel));
399
+ t.ok(b4a.equals(normalized.bdo.in, nonce));
400
+ t.ok(b4a.equals(normalized.bdo.is, signature));
401
+ });
402
+
403
+ test('normalizeBootstrapDeploymentOperation throws on missing payload fields', t => {
404
+ t.exception(
405
+ () => normalizeBootstrapDeploymentOperation({ type: OperationType.BOOTSTRAP_DEPLOYMENT }, config),
406
+ errorMessageIncludes('Invalid payload for bootstrap deployment normalization.')
407
+ );
408
+
409
+ t.exception(
410
+ () => normalizeBootstrapDeploymentOperation({ type: OperationType.TX, address: 'x', bdo: {} }, config),
411
+ errorMessageIncludes('Missing required fields in bootstrap deployment payload.')
412
+ );
413
+ });
414
+
415
+ test('normalizeBootstrapDeploymentOperation throws on invalid hex string', t => {
416
+ const sender = randomAddress(config.addressPrefix);
417
+ const payload = {
418
+ type: OperationType.BOOTSTRAP_DEPLOYMENT,
419
+ address: sender,
420
+ bdo: {
421
+ tx: 'zz',
422
+ txv: hex('22', 32),
423
+ bs: hex('33', 32),
424
+ ic: hex('44', 32),
425
+ in: hex('55', 32),
426
+ is: hex('66', 64)
427
+ }
428
+ };
429
+ t.exception(
430
+ () => normalizeBootstrapDeploymentOperation(payload, config),
431
+ errorMessageIncludes('Invalid hex string')
432
+ );
433
+ });
434
+
435
+ test('normalizeDecodedPayloadForJson converts buffers to strings', t => {
436
+ const address = randomAddress(config.addressPrefix);
437
+ const addressBuf = addressToBuffer(address, config.addressPrefix);
438
+ const amountBuf = bigIntTo16ByteBuffer(1234n);
439
+ const otherBuf = b4a.from('abcd', 'hex');
440
+
441
+ const payload = {
442
+ address: addressBuf,
443
+ am: amountBuf,
444
+ nested: {
445
+ to: addressBuf,
446
+ amount: amountBuf,
447
+ other: otherBuf
448
+ }
449
+ };
450
+
451
+ const normalized = normalizeDecodedPayloadForJson(payload, config);
452
+ t.is(normalized.address, address);
453
+ t.is(normalized.am, '1234');
454
+ t.is(normalized.nested.to, address);
455
+ t.is(normalized.nested.amount, '1234');
456
+ t.is(normalized.nested.other, 'abcd');
457
+ });
458
+
459
+ test('normalizeDecodedPayloadForJson falls back to hex for invalid address buffers', t => {
460
+ const bad = b4a.from('deadbeef', 'hex');
461
+ const payload = { address: bad };
462
+ const normalized = normalizeDecodedPayloadForJson(payload, config);
463
+ t.is(normalized.address, b4a.toString(bad, 'hex'));
464
+ });
465
+
466
+ test('normalizeDecodedPayloadForJson returns input for non-objects', t => {
467
+ t.is(normalizeDecodedPayloadForJson(null, config), null);
468
+ t.is(normalizeDecodedPayloadForJson('value', config), 'value');
469
+ });