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.
- package/SECURITY.md +2 -2
- package/cli/commandHandler.js +317 -20
- package/cli/handlers.js +9 -1
- package/cli/interactive.js +4 -3
- package/cli/tokenSupplyAudit.js +90 -0
- package/msb.mjs +1 -0
- package/package.json +15 -6
- package/rpc/create_server.js +12 -3
- package/rpc/handlers.js +121 -0
- package/rpc/routes/v1.js +12 -0
- package/src/codecs/apply/applyOperationCodec.js +334 -0
- package/src/codecs/apply/applyOperations.generated.cjs +6380 -0
- package/src/codecs/consensus/v1/consensusV1.generated.cjs +3508 -0
- package/src/codecs/consensus/v1/consensusV1OperationCodec.js +161 -0
- package/src/codecs/consensus/v1/vdfConfigCodec.js +83 -0
- package/src/{utils/protobuf → codecs/network/v1}/networkV1.generated.cjs +664 -393
- package/src/codecs/network/v1/networkV1OperationCodec.js +25 -0
- package/src/codecs/protobufRuntime.cjs +95 -0
- package/src/codecs/state/escrowEntryCodec.js +45 -0
- package/src/codecs/state/state.generated.cjs +659 -0
- package/src/config/args.js +0 -1
- package/src/config/config.js +41 -7
- package/src/config/env.js +33 -9
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionError.js +55 -0
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelectionOperations.js +118 -0
- package/src/core/consensus/epochAppenderSelection/EpochAppenderSelector.js +1042 -0
- package/src/core/consensus/epochAppenderSelection/createEpochAppenderSelector.js +23 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionLedgerReader.js +274 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionState.js +488 -0
- package/src/core/consensus/epochAppenderSelection/epochAppenderSelectionStorage.js +93 -0
- package/src/core/consensus/quorum.js +11 -0
- package/src/core/consensus/services/EpochCoordinationRound.js +839 -0
- package/src/core/consensus/services/EpochCoordinatorOperations.js +211 -0
- package/src/core/consensus/services/EpochCoordinatorService.js +335 -0
- package/src/core/consensus/services/EpochRoundListeners.js +116 -0
- package/src/core/consensus/services/EpochStateMachine.js +141 -0
- package/src/core/consensus/services/VDFBare.js +291 -0
- package/src/core/consensus/services/VDFNode.js +111 -0
- package/src/core/consensus/services/VDFProgressWatchdog.js +79 -0
- package/src/core/consensus/services/VDFService.js +87 -0
- package/src/core/consensus/services/VDFServiceManager.js +147 -0
- package/src/core/consensus/services/createVDFService.js +16 -0
- package/src/core/consensus/services/vdf-guardian.js +32 -0
- package/src/core/consensus/services/vdf-worker.js +90 -0
- package/src/core/network/Network.js +327 -238
- package/src/core/network/protocols/consensus/IndexerMessages.js +72 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1ConnectionPolicy.js +57 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1MessageRouter.js +147 -0
- package/src/core/network/protocols/consensus/v1/ConsensusV1Protocol.js +72 -0
- package/src/core/network/protocols/consensus/v1/IndexerPendingRequestService.js +108 -0
- package/src/core/network/protocols/consensus/v1/V1ConsensusProtocolError.js +29 -0
- package/src/core/network/protocols/consensus/v1/handlers/V1EpochAppendOperationHandler.js +68 -0
- package/src/core/network/protocols/consensus/v1/handlers/V1EpochProofProposalOperationHandler.js +226 -0
- package/src/core/network/protocols/consensus/v1/validators/ConsensusValidationSchema.js +192 -0
- package/src/core/network/protocols/consensus/v1/validators/V1BaseConsensusOperation.js +428 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendReply.js +47 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochAppendRequest.js +17 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalApproval.js +116 -0
- package/src/core/network/protocols/consensus/v1/validators/V1EpochProofProposalRequest.js +152 -0
- package/src/core/network/protocols/consensus/v1/validators/consensusValidationSchemaHelpers.js +145 -0
- package/src/core/network/protocols/shared/ConnectionOperationHandler.js +27 -0
- package/src/core/network/protocols/{NetworkMessages.js → validators/ValidatorMessages.js} +24 -14
- package/src/core/network/protocols/{ProtocolSession.js → validators/ValidatorProtocolSession.js} +22 -25
- package/src/core/network/protocols/{legacy/NetworkMessageRouter.js → validators/legacy/ValidatorLegacyMessageRouter.js} +6 -6
- package/src/core/network/protocols/{LegacyProtocol.js → validators/legacy/ValidatorLegacyProtocol.js} +9 -7
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/BaseStateOperationHandler.js +1 -1
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyGetRequestHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyRoleOperationHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacySubnetworkOperationHandler.js +5 -5
- package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyTransferOperationHandler.js +5 -5
- package/src/core/network/protocols/{ProtocolInterface.js → validators/shared/ValidatorProtocolInterface.js} +5 -5
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialBootstrapDeploymentValidator.js +1 -1
- package/src/core/network/protocols/validators/shared/validators/PartialHtlcValidator.js +325 -0
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialOperationValidator.js +44 -21
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialRoleAccessValidator.js +3 -3
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransactionValidator.js +3 -3
- package/src/core/network/protocols/{shared → validators/shared}/validators/PartialTransferValidator.js +4 -4
- package/src/core/network/protocols/{v1 → validators/v1}/V1ProtocolError.js +1 -1
- package/src/core/network/{services/PendingRequestService.js → protocols/validators/v1/ValidatorPendingRequestService.js} +19 -82
- package/src/core/network/protocols/{v1/NetworkMessageRouter.js → validators/v1/ValidatorV1MessageRouter.js} +9 -9
- package/src/core/network/protocols/{V1Protocol.js → validators/v1/ValidatorV1Protocol.js} +10 -8
- package/src/core/network/protocols/{connectionPolicies.js → validators/v1/connectionPolicies.js} +1 -1
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BaseOperationHandler.js +6 -23
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1BroadcastTransactionOperationHandler.js +14 -9
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/V1LivenessOperationHandler.js +5 -4
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionErrorMapper.js +3 -3
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionOperationStrategies.js +44 -1
- package/src/core/network/protocols/{v1 → validators/v1}/handlers/broadcastTransaction/BroadcastTransactionPayloadUtils.js +1 -1
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BaseOperation.js +19 -15
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionRequest.js +2 -2
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1BroadcastTransactionResponse.js +34 -18
- package/src/core/network/protocols/{v1 → validators/v1}/validators/V1ValidationSchema.js +3 -3
- package/src/core/network/services/IndexerConnectionManager.js +88 -0
- package/src/core/network/services/TransactionPoolService.js +11 -18
- package/src/core/network/services/TransactionRateLimiterService.js +1 -1
- package/src/core/network/services/ValidatorConnectionManager.js +163 -0
- package/src/core/network/services/{MessageOrchestrator.js → ValidatorMessageOrchestrator.js} +34 -42
- package/src/core/network/services/ValidatorObserverService.js +21 -36
- package/src/core/network/shared/BasePendingRequestService.js +67 -0
- package/src/core/network/shared/PeerConnectionManager.js +185 -0
- package/src/core/state/State.js +1803 -159
- package/src/core/state/StateEventQueue.js +69 -0
- package/src/core/state/utils/address.js +10 -6
- package/src/core/state/utils/epochProof.js +109 -0
- package/src/core/state/utils/escrowEntry.js +178 -0
- package/src/core/state/validators/StateValidationSchema.js +757 -0
- package/src/core/state/validators/stateValidationRules.js +312 -0
- package/src/index.js +280 -67
- package/src/messages/consensus/v1/ConsensusMessageBuilder.js +368 -0
- package/src/messages/consensus/v1/ConsensusMessageDirector.js +142 -0
- package/src/messages/consensus/v1/consensusMessageFactory.js +12 -0
- package/src/messages/consensus/v1/epochAppendMessage.js +58 -0
- package/src/messages/network/v1/NetworkMessageBuilder.js +15 -7
- package/src/messages/state/ApplyStateMessageBuilder.js +499 -13
- package/src/messages/state/ApplyStateMessageDirector.js +261 -2
- package/src/utils/StateMachine.js +125 -0
- package/src/utils/applyOperations.js +45 -2
- package/src/utils/buffer.js +239 -59
- package/src/utils/consensusConfig.js +76 -0
- package/src/utils/constants.js +174 -73
- package/src/utils/helpers.js +0 -25
- package/src/utils/htlcLock.js +122 -0
- package/src/utils/logger.js +10 -4
- package/src/utils/migrationUtils.js +2 -1
- package/src/utils/normalizers.js +113 -0
- package/src/utils/scheduler/SchedulableService.js +107 -0
- package/src/utils/{Scheduler.js → scheduler/Scheduler.js} +56 -17
- package/src/core/network/services/ConnectionManager.js +0 -406
- package/src/utils/check.js +0 -525
- package/src/utils/protobuf/applyOperations.cjs +0 -1373
- package/src/utils/protobuf/operationHelpers.js +0 -81
- /package/src/core/network/protocols/{legacy → validators/legacy}/handlers/LegacyResponseHandler.js +0 -0
- /package/src/core/network/protocols/{legacy → validators/legacy}/validators/ValidatorResponse.js +0 -0
- /package/src/core/network/protocols/{legacy → validators/legacy}/validators/base/BaseResponse.js +0 -0
- /package/src/core/network/protocols/{v1 → validators/v1}/validators/V1LivenessRequest.js +0 -0
- /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 {
|
|
4
|
-
|
|
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(
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
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
|
-
|
|
251
|
+
uint32ToBuffer(this.#resultCode),
|
|
244
252
|
encodeCapabilities(this.#capabilities),
|
|
245
253
|
);
|
|
246
254
|
|