trac-msb 0.2.26 → 0.3.0-beta.2

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 +121 -0
  10. package/rpc/routes/v1.js +12 -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 +1803 -159
  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 +280 -67
  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,23 @@
1
+ import tracCryptoApi from 'trac-crypto-api';
2
+ import { toHex } from '../../../utils/buffer.js';
3
+ import { EpochAppenderSelector } from './EpochAppenderSelector.js';
4
+ import { EpochAppenderSelectionOperations } from './EpochAppenderSelectionOperations.js';
5
+
6
+ /**
7
+ * Creates the selector shared by protocol handlers and all local epoch rounds. Enables background
8
+ * checks so pending appends are still observed after a round is cancelled.
9
+ *
10
+ * @param {import('../../state/State.js').default} state Local metadata and signed ledger reads.
11
+ * @param {object} wallet Local signing wallet.
12
+ * @param {object} config Election timeout and retry configuration.
13
+ * @param {function(): object} getIndexerConnectionManager Returns the peer session manager after Network is ready.
14
+ * @param {object} [logger] Background error logger.
15
+ * @param {object} [pendingRequestService] Service used to cancel pending peer requests.
16
+ * @returns {EpochAppenderSelector} Selector that Network will open and close.
17
+ */
18
+ export function createEpochAppenderSelector(state, wallet, config, getIndexerConnectionManager, logger, pendingRequestService) {
19
+ const localPublicKey = toHex(tracCryptoApi.address.decode(wallet.address));
20
+ const retryIntervalMs = Math.max(100, Math.min(config.epochBackoffDelay, 1000));
21
+ const selectionOperations = new EpochAppenderSelectionOperations(state, wallet, config, getIndexerConnectionManager, logger, pendingRequestService);
22
+ return new EpochAppenderSelector(localPublicKey, selectionOperations, retryIntervalMs, config.epochSignatureTimeout, true, retryIntervalMs);
23
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Reads signed Autobase data for appender selection. The application view and writer clock come from
3
+ * the same checkpoint. This allows the selector to check whether an operation was processed even when
4
+ * it did not change the application view.
5
+ */
6
+ import SystemView from 'autobase/lib/system.js';
7
+ import b4a from 'b4a';
8
+ import tracCryptoApi from 'trac-crypto-api';
9
+ import { EntryType } from '../../../utils/constants.js';
10
+ import { toHex } from '../../../utils/buffer.js';
11
+ import { bufferToAddress } from '../../state/utils/address.js';
12
+ import { EpochAppendOutcome } from './epochAppenderSelectionState.js';
13
+ import {
14
+ EpochAppenderSelectionError,
15
+ EpochAppenderSelectionErrorCode,
16
+ } from './EpochAppenderSelectionError.js';
17
+
18
+ const SIGNED_READ_OPTIONS = Object.freeze({ wait: false, update: false });
19
+ const WRITER_READ_TIMEOUT_MS = 2000;
20
+ const MEMBERSHIP_HASH_DOMAIN = 'msb/epoch-append/membership/v2';
21
+
22
+ /**
23
+ * Opens the application and system views at the same signed checkpoint. Closes them after the callback
24
+ * finishes. The application length alone cannot show whether an operation was processed without
25
+ * changing the view. Uses the installed Autobase SystemView API.
26
+ *
27
+ * @template T
28
+ * @param {object} autobase Ready local Autobase instance.
29
+ * @param {function({system: SystemView, view: object, length: number}): Promise<T>} readSignedCheckpoint Reads from the opened views and signed system length.
30
+ * @returns {Promise<T>} Result returned by the callback after the views are closed.
31
+ * @throws {EpochAppenderSelectionError} If the signed checkpoint is missing or invalid.
32
+ */
33
+ export async function withSignedEpochView(autobase, readSignedCheckpoint) {
34
+ const liveSystemView = autobase.system;
35
+ const signedSystemLength = autobase.signedLength;
36
+ if (!liveSystemView || !signedSystemLength || !b4a.equals(liveSystemView.core.key, autobase.core.key)) {
37
+ throw new EpochAppenderSelectionError(
38
+ EpochAppenderSelectionErrorCode.SIGNED_CHECKPOINT_UNAVAILABLE,
39
+ 'Signed Autobase system checkpoint is unavailable'
40
+ );
41
+ }
42
+ // Construct the checkout explicitly so it can also be closed if ready() fails.
43
+ const signedSystemView = new SystemView(liveSystemView.core.session(), { checkout: signedSystemLength });
44
+ let signedApplicationView;
45
+ let checkpointReadError;
46
+ try {
47
+ await signedSystemView.ready();
48
+ const viewEntry = requireApplicationView(signedSystemView.views);
49
+ // Autobase moves the application history to a new core on membership changes.
50
+ // Its live view retains that history; opening an old checkpoint key directly
51
+ // can require blocks that this node no longer has. Pin the logical view to
52
+ // the length authenticated by this signed system checkpoint.
53
+ signedApplicationView = autobase.view.checkout(viewEntry.length);
54
+ await signedApplicationView.ready();
55
+ return await readSignedCheckpoint({ system: signedSystemView, view: signedApplicationView, length: signedSystemLength });
56
+ } catch (error) {
57
+ checkpointReadError = error;
58
+ throw error;
59
+ } finally {
60
+ await closeReadResources([signedApplicationView, signedSystemView], checkpointReadError);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Reads the epoch, consensus config and indexers from the same signed checkpoint. A membership change
66
+ * invalidates the previous election. A lost connection does not.
67
+ *
68
+ * @param {object} autobase Ready local Autobase instance.
69
+ * @param {object} config Network ID and wallet address prefix.
70
+ * @returns {Promise<import('./epochAppenderSelectionState.js').EpochAppenderSelectionContext>}
71
+ */
72
+ export async function readEpochAppenderSelectionContext(autobase, config) {
73
+ return withSignedEpochView(autobase, async ({ system: signedSystemView, view: signedApplicationView }) => {
74
+ const { currentEpoch, parentEpochEntry } = await readSignedParentEpoch(signedApplicationView);
75
+ const members = await readSignedIndexerMembership(signedSystemView, signedApplicationView, config.addressPrefix);
76
+ const bootstrap = toHex(autobase.key);
77
+ const parentHash = toHex(parentEpochEntry.value);
78
+ // Unrelated signed writes and core migrations must not restart an election.
79
+ // Bind the hash to the signed voter identities, not to a core key or head length.
80
+ const membershipData = b4a.from(JSON.stringify([
81
+ MEMBERSHIP_HASH_DOMAIN,
82
+ config.networkId,
83
+ bootstrap,
84
+ currentEpoch.toString(),
85
+ parentHash,
86
+ members,
87
+ ]));
88
+ const membershipContext = toHex(await tracCryptoApi.hash.blake3(membershipData));
89
+ const configHash = await readConsensusConfigHash(signedApplicationView);
90
+ return {
91
+ networkId: config.networkId,
92
+ bootstrap,
93
+ targetEpoch: (currentEpoch + 1n).toString(),
94
+ parentHash,
95
+ membershipContext,
96
+ configHash,
97
+ members,
98
+ currentWriterKeys: signedSystemView.indexers.map(({ key }) => toHex(key)).sort(),
99
+ };
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Checks signed epoch progress and looks for the payload in the local writer log, also after restart.
105
+ * Another indexer may have advanced the epoch. UNKNOWN and local append errors do not prove rejection.
106
+ *
107
+ * @param {object} autobase Ready local Autobase instance.
108
+ * @param {import('./epochAppenderSelectionState.js').EpochAppendPublication|null} publication Saved writer position, payload hash and target epoch.
109
+ * @returns {Promise<import('./epochAppenderSelectionState.js').EpochAppendOutcomeResult>}
110
+ * @throws {Error} If a required block or signed checkpoint cannot be read.
111
+ */
112
+ export async function readEpochAppendOutcome(autobase, publication) {
113
+ if (!publication || toHex(autobase.local.key) !== publication.writerKey) {
114
+ return { status: EpochAppendOutcome.UNKNOWN };
115
+ }
116
+ return withSignedEpochView(autobase, async ({ system: signedSystemView, view: signedApplicationView }) => {
117
+ const epochEntry = await signedApplicationView.get(EntryType.EPOCH_CURRENT, { ...SIGNED_READ_OPTIONS });
118
+ if (!epochEntry) {
119
+ return { status: EpochAppendOutcome.UNKNOWN };
120
+ }
121
+ const currentEpoch = epochEntry.value.readBigUInt64BE(0);
122
+ if (currentEpoch >= BigInt(publication.targetEpoch)) {
123
+ return { status: EpochAppendOutcome.EPOCH_ADVANCED };
124
+ }
125
+ const publicationPosition = await findAppendedWriterLength(autobase.local, publication);
126
+ if (publicationPosition === null) {
127
+ return { status: EpochAppendOutcome.UNKNOWN };
128
+ }
129
+ const signedWriterClock = await signedSystemView.get(autobase.local.key, { timeout: WRITER_READ_TIMEOUT_MS });
130
+ if (!signedWriterClock || signedWriterClock.length < publicationPosition) {
131
+ return { status: EpochAppendOutcome.UNKNOWN };
132
+ }
133
+ return { status: EpochAppendOutcome.REJECTED };
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Returns the MSB application view from the signed checkpoint. Exactly one view is expected.
139
+ *
140
+ * @param {Array<{key: Buffer, length: number}>} views Signed system view entries.
141
+ * @returns {{key: Buffer, length: number}} Application view checkpoint.
142
+ * @throws {EpochAppenderSelectionError} If the checkpoint does not contain exactly one application view.
143
+ */
144
+ function requireApplicationView(views) {
145
+ if (views.length !== 1) {
146
+ throw new EpochAppenderSelectionError(EpochAppenderSelectionErrorCode.SIGNED_VIEW_INVALID, 'Expected one MSB application view');
147
+ }
148
+ return views[0];
149
+ }
150
+
151
+ /**
152
+ * Closes the opened views. If reading and closing both fail, adds the cleanup errors to the original
153
+ * read error.
154
+ *
155
+ * @param {Array<object|undefined>} resources Views opened before the read finished or failed.
156
+ * @param {Error|undefined} checkpointReadError Original read failure, if any.
157
+ * @returns {Promise<void>}
158
+ */
159
+ async function closeReadResources(resources, checkpointReadError) {
160
+ const results = await Promise.allSettled(resources.filter(Boolean).map(async resource => {
161
+ await resource.close();
162
+ }));
163
+ const failures = results.filter(result => result.status === 'rejected').map(result => result.reason);
164
+ if (!failures.length) {
165
+ return;
166
+ }
167
+ if (checkpointReadError) {
168
+ checkpointReadError.cleanupErrors = failures;
169
+ return;
170
+ }
171
+ throw failures[0];
172
+ }
173
+
174
+ /**
175
+ * Reads the signed epoch that the next proposal will use as its parent.
176
+ *
177
+ * @param {object} signedApplicationView Hyperbee view at the signed system checkpoint.
178
+ * @returns {Promise<{currentEpoch: bigint, parentEpochEntry: {value: Buffer}}>}
179
+ */
180
+ async function readSignedParentEpoch(signedApplicationView) {
181
+ const epochEntry = await signedApplicationView.get(EntryType.EPOCH_CURRENT, { ...SIGNED_READ_OPTIONS });
182
+ if (!epochEntry) {
183
+ throw new EpochAppenderSelectionError(EpochAppenderSelectionErrorCode.EPOCH_NOT_INITIALIZED, 'Epoch is not initialized');
184
+ }
185
+ const currentEpoch = epochEntry.value.readBigUInt64BE(0);
186
+ const parentEpochEntry = await signedApplicationView.get(EntryType.EPOCH + currentEpoch, { ...SIGNED_READ_OPTIONS });
187
+ if (!parentEpochEntry) {
188
+ throw new EpochAppenderSelectionError(EpochAppenderSelectionErrorCode.PARENT_MISSING, 'Signed epoch parent is missing');
189
+ }
190
+ return { currentEpoch, parentEpochEntry };
191
+ }
192
+
193
+ /**
194
+ * Finds the wallet public key for each signed indexer and sorts the members by writer key. All nodes
195
+ * need the same order when calculating the membership hash.
196
+ *
197
+ * @param {SystemView} signedSystemView Signed system checkout.
198
+ * @param {object} signedApplicationView Application view from the same checkpoint.
199
+ * @param {string} addressPrefix Wallet address prefix for this network.
200
+ * @returns {Promise<import('./epochAppenderSelectionState.js').EpochAppenderSelectionMember[]>}
201
+ */
202
+ async function readSignedIndexerMembership(signedSystemView, signedApplicationView, addressPrefix) {
203
+ const members = [];
204
+ for (const { key } of signedSystemView.indexers) {
205
+ const writerKey = toHex(key);
206
+ const addressEntry = await signedApplicationView.get(EntryType.WRITER_ADDRESS + writerKey, { ...SIGNED_READ_OPTIONS });
207
+ const indexerAddress = addressEntry && bufferToAddress(addressEntry.value, addressPrefix);
208
+ if (!indexerAddress) {
209
+ throw new EpochAppenderSelectionError(
210
+ EpochAppenderSelectionErrorCode.MEMBER_IDENTITY_MISSING,
211
+ 'Epoch indexer identity is missing',
212
+ { details: { writerKey } }
213
+ );
214
+ }
215
+ members.push({
216
+ writerKey,
217
+ publicKey: toHex(tracCryptoApi.address.decode(indexerAddress)),
218
+ });
219
+ }
220
+ members.sort((leftMember, rightMember) => {
221
+ if (leftMember.writerKey === rightMember.writerKey) {
222
+ return 0;
223
+ }
224
+ return leftMember.writerKey < rightMember.writerKey ? -1 : 1;
225
+ });
226
+ const uniquePublicKeys = new Set(members.map(member => member.publicKey));
227
+ if (!members.length || uniquePublicKeys.size !== members.length) {
228
+ throw new EpochAppenderSelectionError(EpochAppenderSelectionErrorCode.MEMBERSHIP_INVALID, 'Invalid epoch coordination membership');
229
+ }
230
+ return members;
231
+ }
232
+
233
+ /**
234
+ * Reads the consensus config selected by the signed view and returns its hash.
235
+ *
236
+ * @param {object} signedApplicationView Application checkout at the signed checkpoint.
237
+ * @returns {Promise<string>} BLAKE3 hash encoded as hex.
238
+ */
239
+ async function readConsensusConfigHash(signedApplicationView) {
240
+ const configPointer = await signedApplicationView.get(EntryType.CONSENSUS_CONFIG_CURRENT, { ...SIGNED_READ_OPTIONS });
241
+ if (!configPointer) {
242
+ throw new EpochAppenderSelectionError(EpochAppenderSelectionErrorCode.CONFIG_MISSING, 'Signed consensus configuration is missing');
243
+ }
244
+ const configKey = EntryType.CONSENSUS_CONFIG_RECORD + configPointer.value.readUInt32BE(0);
245
+ const consensusConfigEntry = await signedApplicationView.get(configKey, { ...SIGNED_READ_OPTIONS });
246
+ if (!consensusConfigEntry) {
247
+ throw new EpochAppenderSelectionError(EpochAppenderSelectionErrorCode.CONFIG_MISSING, 'Signed consensus configuration is missing');
248
+ }
249
+ return toHex(await tracCryptoApi.hash.blake3(consensusConfigEntry.value));
250
+ }
251
+
252
+ /**
253
+ * Searches the local writer log from the saved start position for the matching payload hash. Returns
254
+ * the block index plus one because the writer clock stores a length.
255
+ *
256
+ * @param {object} writerCore Local Autobase writer core.
257
+ * @param {import('./epochAppenderSelectionState.js').EpochAppendPublication} publication Saved writer position and payload hash for this append.
258
+ * @returns {Promise<number|null>} Writer length including the operation, or null when not found.
259
+ */
260
+ async function findAppendedWriterLength(writerCore, publication) {
261
+ const writerLength = writerCore.length;
262
+ for (let blockIndex = publication.from; blockIndex < writerLength; blockIndex++) {
263
+ const writerMessage = await writerCore.get(blockIndex, { wait: false });
264
+ const operationPayload = writerMessage?.node?.value;
265
+ if (!operationPayload) {
266
+ continue;
267
+ }
268
+ const payloadHash = toHex(await tracCryptoApi.hash.blake3(operationPayload));
269
+ if (payloadHash === publication.payloadHash) {
270
+ return blockIndex + 1;
271
+ }
272
+ }
273
+ return null;
274
+ }