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,152 @@
|
|
|
1
|
+
import V1BaseConsensusOperation from "./V1BaseConsensusOperation.js";
|
|
2
|
+
import { V1ConsensusProtocolError } from "../V1ConsensusProtocolError.js";
|
|
3
|
+
import { ConsensusResultCode } from "../../../../../../utils/constants.js";
|
|
4
|
+
import { uint16ToBuffer } from "../../../../../../utils/buffer.js";
|
|
5
|
+
import b4a from "b4a";
|
|
6
|
+
import { verifyWesolowski } from '@tracsystems/trac-vdf';
|
|
7
|
+
|
|
8
|
+
class V1EpochProofProposalRequest extends V1BaseConsensusOperation {
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates the proof proposal request validator.
|
|
12
|
+
*
|
|
13
|
+
* @param {Config} config Application configuration used by base validation
|
|
14
|
+
* and network identity checks.
|
|
15
|
+
* @param {State} state Ledger state. It must expose `requireCurrentEpoch()`,
|
|
16
|
+
* `requireEpoch(epoch)`, `requireSignedConsensusConfig()`, and `isIndexerAddress(address)`.
|
|
17
|
+
* @throws {Error} When consensus schemas cannot be initialized from the configuration.
|
|
18
|
+
*/
|
|
19
|
+
constructor(config, state) {
|
|
20
|
+
super(config, state);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates a complete incoming epoch proof proposal request.
|
|
25
|
+
*
|
|
26
|
+
* Checks the payload schema, active consensus version, network id, proposer identity,
|
|
27
|
+
* proposal signature, proposer indexer membership, next epoch number,
|
|
28
|
+
* previous epoch record hash, consensus configuration, and VDF proof.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} payload Decoded proof proposal request payload.
|
|
31
|
+
* @param {object} connection Peer connection containing `remotePublicKey`.
|
|
32
|
+
* @returns {Promise<boolean>} Resolves to `true` when every check succeeds.
|
|
33
|
+
* @throws {V1ConsensusProtocolError} When any proposal validation check fails.
|
|
34
|
+
*/
|
|
35
|
+
async validate(payload, connection) {
|
|
36
|
+
return await this.validateAsProtocolError(async () => {
|
|
37
|
+
this.isPayloadSchemaValid(payload);
|
|
38
|
+
this.validateProofProposalNetworkId(payload.proof_proposal);
|
|
39
|
+
this.assertAddressWithRemotePublicKey(
|
|
40
|
+
payload.proof_proposal.proposer,
|
|
41
|
+
connection.remotePublicKey
|
|
42
|
+
);
|
|
43
|
+
await this.validateProofProposalConfig(payload.proof_proposal);
|
|
44
|
+
await this.validateSignature(payload, connection.remotePublicKey, undefined, ConsensusResultCode.PROPOSAL_SIGNATURE_INVALID);
|
|
45
|
+
await this.validateAddressIsIndexer(connection.remotePublicKey);
|
|
46
|
+
this.validateLocalNodeIsIndexer();
|
|
47
|
+
const currentEpoch = await this._state.requireCurrentEpoch();
|
|
48
|
+
this.validateIncomingEpoch(payload.proof_proposal, currentEpoch);
|
|
49
|
+
await this.validatePreviousEpochRecordHash(payload.proof_proposal, currentEpoch);
|
|
50
|
+
await this.validateProofProposalVdfProof(payload.proof_proposal);
|
|
51
|
+
|
|
52
|
+
// The signed config may have changed during VDF verification.
|
|
53
|
+
await this.validateProofProposalConfig(payload.proof_proposal);
|
|
54
|
+
return true;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Verifies the proof proposal VDF proof against its challenge data.
|
|
60
|
+
*
|
|
61
|
+
* Uses the canonical challenge fields and signed-state VDF difficulty and
|
|
62
|
+
* discriminant size for Wesolowski proof verification.
|
|
63
|
+
*
|
|
64
|
+
* @param {object} proofProposal Decoded proof proposal.
|
|
65
|
+
* @returns {Promise<void>}
|
|
66
|
+
* @throws {V1ConsensusProtocolError} When VDF verification fails or returns false.
|
|
67
|
+
*/
|
|
68
|
+
async validateProofProposalVdfProof(proofProposal) {
|
|
69
|
+
const challengeData = this.buildProofProposalChallengeData(proofProposal);
|
|
70
|
+
const difficulty = proofProposal.difficulty.readUInt32BE(0);
|
|
71
|
+
const discriminantBitSize = proofProposal.discriminant_bit_size.readUInt16BE(0);
|
|
72
|
+
|
|
73
|
+
let verified = false;
|
|
74
|
+
try {
|
|
75
|
+
verified = await verifyWesolowski(
|
|
76
|
+
challengeData,
|
|
77
|
+
difficulty,
|
|
78
|
+
proofProposal.proof,
|
|
79
|
+
discriminantBitSize
|
|
80
|
+
);
|
|
81
|
+
} catch {
|
|
82
|
+
verified = false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!verified) {
|
|
86
|
+
throw new V1ConsensusProtocolError(
|
|
87
|
+
ConsensusResultCode.VDF_PROOF_INVALID,
|
|
88
|
+
'VDF proof is invalid.'
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Validates that the proof proposal targets the configured network.
|
|
95
|
+
*
|
|
96
|
+
* @param {object} proofProposal Decoded proof proposal.
|
|
97
|
+
* @returns {void}
|
|
98
|
+
* @throws {V1ConsensusProtocolError} When `network_id` differs from the local network id.
|
|
99
|
+
*/
|
|
100
|
+
validateProofProposalNetworkId(proofProposal) {
|
|
101
|
+
const expectedNetworkId = uint16ToBuffer(this._config.networkId);
|
|
102
|
+
if (!b4a.equals(proofProposal.network_id, expectedNetworkId)) {
|
|
103
|
+
throw new V1ConsensusProtocolError(
|
|
104
|
+
ConsensusResultCode.WRONG_NETWORK_ID,
|
|
105
|
+
'Invalid proof proposal network id.'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Validates that the proposal advances the current epoch by exactly one.
|
|
112
|
+
*
|
|
113
|
+
* @param {object} proofProposal Decoded proof proposal containing an uint64 epoch buffer.
|
|
114
|
+
* @param {bigint} currentEpoch Current epoch snapshot used by all epoch-related checks.
|
|
115
|
+
* @returns {void}
|
|
116
|
+
* @throws {V1ConsensusProtocolError} When the proposed epoch is not `currentEpoch + 1`.
|
|
117
|
+
*/
|
|
118
|
+
validateIncomingEpoch(proofProposal, currentEpoch) {
|
|
119
|
+
const proofProposalEpoch = proofProposal.epoch.readBigUInt64BE(0);
|
|
120
|
+
if (proofProposalEpoch !== currentEpoch + 1n) {
|
|
121
|
+
throw new V1ConsensusProtocolError(
|
|
122
|
+
ConsensusResultCode.EPOCH_INVALID,
|
|
123
|
+
`Unexpected epoch. Proof proposal must be ${currentEpoch + 1n} but got ${proofProposalEpoch}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Validates that the proposal references the current epoch record hash.
|
|
130
|
+
*
|
|
131
|
+
* Reads the current epoch id and its stored hash, then compares that hash
|
|
132
|
+
* with `previous_epoch_record_hash` from the proposal.
|
|
133
|
+
*
|
|
134
|
+
* @param {object} proofProposal Decoded proof proposal.
|
|
135
|
+
* @param {bigint} currentEpoch Current epoch snapshot used by all epoch-related checks.
|
|
136
|
+
* @returns {Promise<void>}
|
|
137
|
+
* @throws {V1ConsensusProtocolError} When the previous epoch record hash does not match state.
|
|
138
|
+
*/
|
|
139
|
+
async validatePreviousEpochRecordHash(proofProposal, currentEpoch) {
|
|
140
|
+
const currentEpochHash = await this._state.requireEpoch(currentEpoch);
|
|
141
|
+
const epochHashInProofProposal = proofProposal.previous_epoch_record_hash;
|
|
142
|
+
if (!b4a.equals(currentEpochHash, epochHashInProofProposal)) {
|
|
143
|
+
throw new V1ConsensusProtocolError(
|
|
144
|
+
ConsensusResultCode.PREVIOUS_EPOCH_RECORD_HASH_INVALID,
|
|
145
|
+
`Previous epoch record hash mismatch for epoch ${currentEpoch}: expected ${currentEpochHash.toString('hex')}, got ${epochHashInProofProposal.toString('hex')}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default V1EpochProofProposalRequest;
|
package/src/core/network/protocols/consensus/v1/validators/consensusValidationSchemaHelpers.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import {
|
|
3
|
+
ConsensusResultCode,
|
|
4
|
+
EPOCH_APPENDER_SELECTION_COUNTER_LIMIT,
|
|
5
|
+
EPOCH_BYTE_LENGTH,
|
|
6
|
+
VDF_DISCRIMINANT_SIZE,
|
|
7
|
+
VDF_PROOF_BYTE_LENGTHS,
|
|
8
|
+
} from '../../../../../../utils/constants.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks that the proof length matches the discriminant size in the proposal.
|
|
12
|
+
*
|
|
13
|
+
* @param {object} proofProposal Proposal checked by the compiled schema.
|
|
14
|
+
* @param {object[]} errors Validation errors collected by the schema.
|
|
15
|
+
* @returns {object} Original proposal.
|
|
16
|
+
*/
|
|
17
|
+
export function validateProofProposalLength(proofProposal, errors) {
|
|
18
|
+
const discriminant = proofProposal?.discriminant_bit_size;
|
|
19
|
+
const proof = proofProposal?.proof;
|
|
20
|
+
if (!b4a.isBuffer(discriminant) || discriminant.length !== VDF_DISCRIMINANT_SIZE) {
|
|
21
|
+
return proofProposal;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const discriminantBitSize = discriminant.readUInt16BE(0);
|
|
25
|
+
const expectedProofLength = VDF_PROOF_BYTE_LENGTHS[discriminantBitSize];
|
|
26
|
+
if (!Number.isInteger(expectedProofLength)) {
|
|
27
|
+
errors.push({
|
|
28
|
+
type: 'unsupportedDiscriminantBitSize',
|
|
29
|
+
field: 'proof_proposal.discriminant_bit_size',
|
|
30
|
+
actual: discriminantBitSize,
|
|
31
|
+
expected: Object.keys(VDF_PROOF_BYTE_LENGTHS).map(Number),
|
|
32
|
+
message: 'Discriminant bit size must be one of: 1024, 2048, 4096.'
|
|
33
|
+
});
|
|
34
|
+
return proofProposal;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (b4a.isBuffer(proof) && proof.length !== expectedProofLength) {
|
|
38
|
+
errors.push({
|
|
39
|
+
type: 'bufferLength',
|
|
40
|
+
field: 'proof_proposal.proof',
|
|
41
|
+
actual: proof.length,
|
|
42
|
+
expected: expectedProofLength,
|
|
43
|
+
message: `Proof must be a Buffer with length ${expectedProofLength}.`
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return proofProposal;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Requires an approval for OK responses and forbids it for other results.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} response Response checked by the compiled schema.
|
|
53
|
+
* @param {object[]} errors Validation errors collected by the schema.
|
|
54
|
+
* @returns {object} Original response.
|
|
55
|
+
*/
|
|
56
|
+
export function validateProofProposalApprovalPresence(response, errors) {
|
|
57
|
+
if (!response || typeof response !== 'object') {
|
|
58
|
+
return response;
|
|
59
|
+
}
|
|
60
|
+
const requiresApproval = response.result === ConsensusResultCode.OK;
|
|
61
|
+
const hasApproval = response.approval !== undefined && response.approval !== null;
|
|
62
|
+
if (requiresApproval !== hasApproval) {
|
|
63
|
+
errors.push({
|
|
64
|
+
type: 'conditionalDependency',
|
|
65
|
+
field: 'proof_proposal_response.approval',
|
|
66
|
+
message: requiresApproval
|
|
67
|
+
? 'Approval is required when the result is OK.'
|
|
68
|
+
: 'Approval is only allowed when the result is OK.'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks the allowed counter range after the buffer rule checks its shape.
|
|
76
|
+
*
|
|
77
|
+
* @param {Buffer} counter Encoded election counter.
|
|
78
|
+
* @param {object[]} errors Validation errors collected by the schema.
|
|
79
|
+
* @returns {Buffer} Original counter.
|
|
80
|
+
*/
|
|
81
|
+
export function validateSelectionCounter(counter, errors) {
|
|
82
|
+
if (!b4a.isBuffer(counter) || counter.length !== EPOCH_BYTE_LENGTH) {
|
|
83
|
+
return counter;
|
|
84
|
+
}
|
|
85
|
+
const counterValue = counter.readBigUInt64BE(0);
|
|
86
|
+
if (counterValue === 0n || counterValue > EPOCH_APPENDER_SELECTION_COUNTER_LIMIT) {
|
|
87
|
+
errors.push({
|
|
88
|
+
type: 'selectionCounter',
|
|
89
|
+
actual: counterValue.toString(),
|
|
90
|
+
message: 'Selection counter is outside the allowed range.'
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return counter;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds the buffer check during schema compilation. The validator provides this.makeError.
|
|
98
|
+
*
|
|
99
|
+
* @this {import('fastest-validator').default}
|
|
100
|
+
* @param {object} rule Buffer field definition and error messages.
|
|
101
|
+
* @returns {{source: string}} Code used by the compiled validator.
|
|
102
|
+
*/
|
|
103
|
+
export function compileBufferRule({ schema, messages }) {
|
|
104
|
+
const isBuffer = b4a.isBuffer;
|
|
105
|
+
const allowEmpty = schema.allowEmpty === true;
|
|
106
|
+
const exactLength = Number.isInteger(schema.length) ? schema.length : null;
|
|
107
|
+
const minLength = Number.isInteger(schema.min) ? schema.min : null;
|
|
108
|
+
const maxLength = Number.isInteger(schema.max) ? schema.max : null;
|
|
109
|
+
return {
|
|
110
|
+
source: `
|
|
111
|
+
if (!${isBuffer}(value)) {
|
|
112
|
+
${this.makeError({ type: "buffer", actual: "value", messages })}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
const len = value.length;
|
|
116
|
+
${exactLength === null ? '' : `
|
|
117
|
+
if (len !== ${exactLength}) {
|
|
118
|
+
${this.makeError({ type: "bufferLength", expected: exactLength, actual: "len", messages })}
|
|
119
|
+
}`}
|
|
120
|
+
${minLength === null ? '' : `
|
|
121
|
+
if (len < ${minLength}) {
|
|
122
|
+
${this.makeError({ type: "bufferMinLength", expected: minLength, actual: "len", messages })}
|
|
123
|
+
}`}
|
|
124
|
+
${maxLength === null ? '' : `
|
|
125
|
+
if (len > ${maxLength}) {
|
|
126
|
+
${this.makeError({ type: "bufferMaxLength", expected: maxLength, actual: "len", messages })}
|
|
127
|
+
}`}
|
|
128
|
+
if (len === 0 && !${allowEmpty}) {
|
|
129
|
+
${this.makeError({ type: "emptyBuffer", actual: "len", messages })}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
let isZeroFilled = true;
|
|
133
|
+
for (let i = 0; i < len; i++) {
|
|
134
|
+
if (value[i] !== 0) {
|
|
135
|
+
isZeroFilled = false;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (isZeroFilled) {
|
|
140
|
+
${this.makeError({ type: "nonZeroBuffer", actual: "value", messages })}
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {publicKeyToAddress} from "../../../../utils/helpers.js";
|
|
2
|
+
|
|
3
|
+
export default class ConnectionOperationHandler {
|
|
4
|
+
#config;
|
|
5
|
+
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.#config = config;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get config() {
|
|
11
|
+
return this.#config;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async sendResponseAndMaybeClose(protocolSession, connection, response, endConnection = true) {
|
|
15
|
+
protocolSession.sendAndForget(response);
|
|
16
|
+
if (!endConnection) return;
|
|
17
|
+
|
|
18
|
+
await connection.flush();
|
|
19
|
+
connection.end();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
displayError(step = "undefined step", senderPublicKey, error) {
|
|
23
|
+
const errorMessage = error?.message ?? 'Unexpected error';
|
|
24
|
+
console.error(`${this.constructor.name}: ${step} ${publicKeyToAddress(senderPublicKey, this.#config)}: ${errorMessage}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import ValidatorLegacyMessageRouter from './legacy/ValidatorLegacyMessageRouter.js';
|
|
2
|
+
import ValidatorV1MessageRouter from './v1/ValidatorV1MessageRouter.js';
|
|
3
|
+
import ValidatorProtocolSession from './ValidatorProtocolSession.js';
|
|
4
|
+
import ValidatorLegacyProtocol from './legacy/ValidatorLegacyProtocol.js';
|
|
5
|
+
import ValidatorV1Protocol from './v1/ValidatorV1Protocol.js';
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class ValidatorMessages {
|
|
8
8
|
#legacyMessageRouter;
|
|
9
9
|
#v1MessageRouter;
|
|
10
10
|
#config;
|
|
@@ -23,7 +23,7 @@ class NetworkMessages {
|
|
|
23
23
|
this.#config = config;
|
|
24
24
|
this.#wallet = wallet;
|
|
25
25
|
this.#pendingRequestsService = pendingRequestsService;
|
|
26
|
-
this.#legacyMessageRouter = new
|
|
26
|
+
this.#legacyMessageRouter = new ValidatorLegacyMessageRouter(
|
|
27
27
|
state,
|
|
28
28
|
wallet,
|
|
29
29
|
rateLimiterService,
|
|
@@ -31,7 +31,7 @@ class NetworkMessages {
|
|
|
31
31
|
this.#config
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
-
this.#v1MessageRouter = new
|
|
34
|
+
this.#v1MessageRouter = new ValidatorV1MessageRouter(
|
|
35
35
|
state,
|
|
36
36
|
wallet,
|
|
37
37
|
rateLimiterService,
|
|
@@ -42,28 +42,38 @@ class NetworkMessages {
|
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
createProtocolSession(connection) {
|
|
46
46
|
// Attach a Protomux instance to this Hyperswarm connection.
|
|
47
47
|
// Protomux multiplexes multiple logical protocol channels over a single encrypted stream.
|
|
48
48
|
|
|
49
|
-
const legacyProtocol = new
|
|
49
|
+
const legacyProtocol = new ValidatorLegacyProtocol(
|
|
50
50
|
this.#legacyMessageRouter,
|
|
51
51
|
connection,
|
|
52
52
|
null,
|
|
53
53
|
this.#config
|
|
54
54
|
);
|
|
55
55
|
|
|
56
|
-
const v1Protocol = new
|
|
56
|
+
const v1Protocol = new ValidatorV1Protocol(
|
|
57
57
|
this.#v1MessageRouter,
|
|
58
58
|
connection,
|
|
59
59
|
this.#pendingRequestsService,
|
|
60
60
|
this.#config
|
|
61
61
|
);
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// ValidatorProtocolSession is attached to the Hyperswarm connection so other parts of the system (e.g. tryConnect)
|
|
64
64
|
// can send messages without knowing how Protomux was initialized.
|
|
65
|
-
|
|
65
|
+
return new ValidatorProtocolSession(legacyProtocol, v1Protocol, this.#wallet, this.#config);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
attachChannel(connection) {
|
|
69
|
+
connection.protocolSessions ??= {};
|
|
70
|
+
if (connection.protocolSessions.validator) return;
|
|
71
|
+
connection.protocolSessions.validator = this.createProtocolSession(connection);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
prepareConnection(connection) {
|
|
75
|
+
this.attachChannel(connection);
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
export default
|
|
79
|
+
export default ValidatorMessages;
|
package/src/core/network/protocols/{ProtocolSession.js → validators/ValidatorProtocolSession.js}
RENAMED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
//
|
|
1
|
+
// ValidatorProtocolSession is a per-peer (per Hyperswarm connection) bridge that exposes the available
|
|
2
2
|
// protocol messengers (legacy JSON, v1 binary) in one place.
|
|
3
3
|
//
|
|
4
4
|
// Why it exists:
|
|
5
5
|
// - `setupProtomuxMessages(connection)` creates Protomux channels/messages for a specific peer.
|
|
6
6
|
|
|
7
|
-
import { networkMessageFactory } from '
|
|
8
|
-
import { generateUUID } from '
|
|
9
|
-
import { NETWORK_CAPABILITIES, ResultCode } from '
|
|
10
|
-
import { Logger } from '
|
|
7
|
+
import { networkMessageFactory } from '../../../../messages/network/v1/networkMessageFactory.js';
|
|
8
|
+
import { generateUUID } from '../../../../utils/helpers.js';
|
|
9
|
+
import { NETWORK_CAPABILITIES, ResultCode } from '../../../../utils/constants.js';
|
|
10
|
+
import { Logger } from '../../../../utils/logger.js';
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class ValidatorProtocolSession {
|
|
13
13
|
#legacyProtocol;
|
|
14
14
|
#v1Protocol;
|
|
15
15
|
#preferredProtocol = null;
|
|
@@ -51,24 +51,24 @@ class ProtocolSession {
|
|
|
51
51
|
setLegacyAsPreferredProtocol() {
|
|
52
52
|
if (this.isProbed()) {
|
|
53
53
|
// TODO: SOMETIMES WE ARE PROBING NODE FOR MULTIPLE AMOUNT OF TIME, THIS IS BAD. WE NEED TO INVESTIGATE THIS.
|
|
54
|
-
//this.#logger.warn(`
|
|
54
|
+
//this.#logger.warn(`ValidatorProtocolSession: preferred protocol is already set and cannot be changed to LEGACY. Current preferred protocol: ${this.#preferredProtocol}`);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
this.#preferredProtocol = this.#supportedProtocols.LEGACY;
|
|
58
58
|
this.#activeProtocol = this.#legacyProtocol;
|
|
59
|
-
this.#logger.debug('
|
|
59
|
+
this.#logger.debug('ValidatorProtocolSession: set preferred protocol to LEGACY');
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
setV1AsPreferredProtocol() {
|
|
63
63
|
if (this.isProbed()) {
|
|
64
64
|
// TODO: SOMETIMES WE ARE PROBING NODE FOR MULTIPLE AMOUNT OF TIME, THIS IS BAD. WE NEED TO INVESTIGATE THIS.
|
|
65
|
-
//this.#logger.warn(`
|
|
65
|
+
//this.#logger.warn(`ValidatorProtocolSession: preferred protocol is already set and cannot be changed to V1. Current preferred protocol: ${this.#preferredProtocol}`);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
this.#preferredProtocol = this.#supportedProtocols.V1;
|
|
70
70
|
this.#activeProtocol = this.#v1Protocol;
|
|
71
|
-
this.#logger.debug('
|
|
71
|
+
this.#logger.debug('ValidatorProtocolSession: set preferred protocol to V1');
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -83,25 +83,25 @@ class ProtocolSession {
|
|
|
83
83
|
*/
|
|
84
84
|
async probe() {
|
|
85
85
|
if (this.isProbed()) {
|
|
86
|
-
this.#logger.warn(`
|
|
86
|
+
this.#logger.warn(`ValidatorProtocolSession: preferred protocol is already set. Skipping probe. Current preferred protocol: ${this.#preferredProtocol}`);
|
|
87
87
|
return; // TODO: Consider not returning silently
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
try {
|
|
91
91
|
const message = await this.#buildLivenessRequest();
|
|
92
92
|
if (!this.#v1Protocol) {
|
|
93
|
-
throw new Error('
|
|
93
|
+
throw new Error('ValidatorProtocolSession: v1 protocol not available for probing');
|
|
94
94
|
}
|
|
95
95
|
const result = await this.#v1Protocol.send(message);
|
|
96
96
|
if (result !== ResultCode.OK) {
|
|
97
97
|
// TODO: Think about how to handle failure result codes after legacy protocol is retired
|
|
98
|
-
this.#logger.warn(`
|
|
98
|
+
this.#logger.warn(`ValidatorProtocolSession: v1 protocol probe failed with non-OK result code: ${result}`);
|
|
99
99
|
this.setLegacyAsPreferredProtocol();
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
this.setV1AsPreferredProtocol();
|
|
103
103
|
} catch (err) {
|
|
104
|
-
this.#logger.debug(`
|
|
104
|
+
this.#logger.debug(`ValidatorProtocolSession: v1 protocol probe failed, falling back to legacy. Details: ${err?.message ?? err}`);
|
|
105
105
|
this.setLegacyAsPreferredProtocol();
|
|
106
106
|
}
|
|
107
107
|
}
|
|
@@ -119,14 +119,14 @@ class ProtocolSession {
|
|
|
119
119
|
return await this.#v1Protocol.send(message);
|
|
120
120
|
}
|
|
121
121
|
catch (err) {
|
|
122
|
-
this.#logger.error(`
|
|
122
|
+
this.#logger.error(`ValidatorProtocolSession: v1 health check failed: ${err?.message ?? err}`);
|
|
123
123
|
return ResultCode.UNEXPECTED_ERROR; // TODO: Consider just propagating the error instead
|
|
124
124
|
}
|
|
125
125
|
case this.#supportedProtocols.LEGACY:
|
|
126
|
-
this.#logger.warn('
|
|
126
|
+
this.#logger.warn('ValidatorProtocolSession: health check not supported on LEGACY protocol');
|
|
127
127
|
return ResultCode.OK; // TODO: Consider implementing a new result code (e.g. NOT_SUPPORTED) instead of returning OK
|
|
128
128
|
default:
|
|
129
|
-
this.#logger.warn('
|
|
129
|
+
this.#logger.warn('ValidatorProtocolSession: preferred protocol not set. Call probe() first.');
|
|
130
130
|
return ResultCode.UNSPECIFIED; // TODO: Define a more specific result code.
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -136,10 +136,7 @@ class ProtocolSession {
|
|
|
136
136
|
* @returns {Boolean} True if health checks are supported in the preferred protocol, false otherwise.
|
|
137
137
|
*/
|
|
138
138
|
isHealthCheckSupported() {
|
|
139
|
-
|
|
140
|
-
throw new Error('ProtocolSession: preferred protocol not set. Call probe() first.');
|
|
141
|
-
}
|
|
142
|
-
return this.#preferredProtocol === this.#supportedProtocols.V1;
|
|
139
|
+
return this.isProbed() && this.#preferredProtocol === this.#supportedProtocols.V1;
|
|
143
140
|
}
|
|
144
141
|
|
|
145
142
|
// TODO: Consider moving this method to be used only in V1 internally, just like 'encode'
|
|
@@ -160,7 +157,7 @@ class ProtocolSession {
|
|
|
160
157
|
try {
|
|
161
158
|
this.#legacyProtocol.close();
|
|
162
159
|
} catch (e) {
|
|
163
|
-
this.#logger.error(`
|
|
160
|
+
this.#logger.error(`ValidatorProtocolSession: failed to close legacy channel: ${e?.message ?? e}`); // TODO: Think about throwing instead
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
163
|
|
|
@@ -168,14 +165,14 @@ class ProtocolSession {
|
|
|
168
165
|
try {
|
|
169
166
|
this.#v1Protocol.close();
|
|
170
167
|
} catch (e) {
|
|
171
|
-
this.#logger.error(`
|
|
168
|
+
this.#logger.error(`ValidatorProtocolSession: failed to close v1 channel: ${e?.message ?? e}`); // TODO: Think about throwing instead
|
|
172
169
|
}
|
|
173
170
|
}
|
|
174
171
|
}
|
|
175
172
|
|
|
176
173
|
async #buildLivenessRequest() {
|
|
177
174
|
if (!this.#wallet || !this.#config) {
|
|
178
|
-
throw new Error('
|
|
175
|
+
throw new Error('ValidatorProtocolSession: wallet/config not set for liveness request');
|
|
179
176
|
}
|
|
180
177
|
const requestId = generateUUID();
|
|
181
178
|
return await networkMessageFactory(this.#wallet, this.#config)
|
|
@@ -183,4 +180,4 @@ class ProtocolSession {
|
|
|
183
180
|
}
|
|
184
181
|
}
|
|
185
182
|
|
|
186
|
-
export default
|
|
183
|
+
export default ValidatorProtocolSession;
|
|
@@ -5,10 +5,10 @@ import LegacyResponseHandler from "./handlers/LegacyResponseHandler.js";
|
|
|
5
5
|
import LegacyRoleOperationHandler from "./handlers/LegacyRoleOperationHandler.js";
|
|
6
6
|
import LegacySubnetworkOperationHandler from "./handlers/LegacySubnetworkOperationHandler.js";
|
|
7
7
|
import LegacyTransferOperationHandler from "./handlers/LegacyTransferOperationHandler.js";
|
|
8
|
-
import { NETWORK_MESSAGE_TYPES } from '
|
|
9
|
-
import * as operation from '
|
|
8
|
+
import { NETWORK_MESSAGE_TYPES } from '../../../../../utils/constants.js';
|
|
9
|
+
import * as operation from '../../../../../utils/applyOperations.js';
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class ValidatorLegacyMessageRouter {
|
|
12
12
|
#handlers;
|
|
13
13
|
#config;
|
|
14
14
|
|
|
@@ -37,9 +37,9 @@ class NetworkMessageRouter {
|
|
|
37
37
|
const channelString = b4a.toString(this.#config.channel, 'utf8');
|
|
38
38
|
|
|
39
39
|
// We received a legacy message, so we set the connection protocol accordingly
|
|
40
|
-
connection.
|
|
40
|
+
connection.protocolSessions.validator.setLegacyAsPreferredProtocol();
|
|
41
41
|
if (this.#isGetRequest(incomingMessage)) {
|
|
42
|
-
await this.#handlers.get.handle(incomingMessage, connection, channelString);
|
|
42
|
+
await this.#handlers.get.handle(incomingMessage, connection, connection.protocolSessions.validator, channelString);
|
|
43
43
|
}
|
|
44
44
|
else if (this.#isResponse(incomingMessage)) {
|
|
45
45
|
await this.#handlers.response.handle(incomingMessage, channelString);
|
|
@@ -87,4 +87,4 @@ class NetworkMessageRouter {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
export default
|
|
90
|
+
export default ValidatorLegacyMessageRouter;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import Protomux from 'protomux';
|
|
2
|
-
import
|
|
2
|
+
import ValidatorProtocolInterface from '../shared/ValidatorProtocolInterface.js';
|
|
3
3
|
import b4a from 'b4a';
|
|
4
4
|
import c from 'compact-encoding';
|
|
5
5
|
|
|
6
|
-
class
|
|
6
|
+
class ValidatorLegacyProtocol extends ValidatorProtocolInterface {
|
|
7
7
|
#channel;
|
|
8
8
|
#session;
|
|
9
9
|
#config;
|
|
@@ -35,6 +35,8 @@ class LegacyProtocol extends ProtocolInterface {
|
|
|
35
35
|
onclose() { }
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
if (!this.#channel) return; // connection already destroyed before protocol setup completed
|
|
39
|
+
|
|
38
40
|
this.#channel.open();
|
|
39
41
|
|
|
40
42
|
// Todo: Abstract in a separate function
|
|
@@ -42,7 +44,7 @@ class LegacyProtocol extends ProtocolInterface {
|
|
|
42
44
|
encoding: c.json,
|
|
43
45
|
onmessage: async (incomingMessage) => {
|
|
44
46
|
this.#router.route(incomingMessage, connection).catch((err) => {
|
|
45
|
-
console.error(`
|
|
47
|
+
console.error(`ValidatorLegacyProtocol: unhandled router error: ${err.message}`);
|
|
46
48
|
try {
|
|
47
49
|
connection.end();
|
|
48
50
|
} catch {
|
|
@@ -61,17 +63,17 @@ class LegacyProtocol extends ProtocolInterface {
|
|
|
61
63
|
async send(message) {
|
|
62
64
|
this.sendAndForget(message);
|
|
63
65
|
// TODO: Change 'null' to an appropriate response if needed in the future
|
|
64
|
-
return Promise.resolve(null); // This is to maintain consistency with the
|
|
66
|
+
return Promise.resolve(null); // This is to maintain consistency with the ValidatorProtocolInterface and v1 protocol.
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
sendAndForget(message) {
|
|
68
|
-
this.#session
|
|
70
|
+
this.#session?.send(message);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
close() {
|
|
72
|
-
this.#channel
|
|
74
|
+
this.#channel?.close();
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
export default
|
|
79
|
+
export default ValidatorLegacyProtocol;
|