trac-msb 0.2.26 → 0.3.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +103 -0
- package/rpc/routes/v1.js +8 -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 +1789 -173
- 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 +255 -65
- 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,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IMPORTANT
|
|
3
|
+
* Manages events created by network contracts in the apply function.
|
|
4
|
+
*
|
|
5
|
+
* When a valid operation creates an event, the event is stored in this queue
|
|
6
|
+
* together with the signed length required to publish it. On every Autobase
|
|
7
|
+
* update, the current signed length is compared with the required signed length.
|
|
8
|
+
* The event is emitted only when the indexers have signed the related state.
|
|
9
|
+
*
|
|
10
|
+
* This makes sure that an event created during apply is not emitted before
|
|
11
|
+
* its state changes are confirmed by the indexers.
|
|
12
|
+
*
|
|
13
|
+
* Currently, only configuration-change events are supported, which is safe
|
|
14
|
+
* with the expected event volume.
|
|
15
|
+
*
|
|
16
|
+
* TODO: Investigate whether other types of events can be safely supported.
|
|
17
|
+
* Verify that bursts or frequent events (for example, many nodes removing a
|
|
18
|
+
* writer at the same time) cannot cause excessive memory usage while queued.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export class StateEventQueue {
|
|
22
|
+
#pendingBatches = [];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Adds events created by a successful apply batch.
|
|
26
|
+
*
|
|
27
|
+
* @param {Array<{type: string, args?: unknown[]}>} events events to publish
|
|
28
|
+
* @param {number} requiredSignedLength view length containing the applied changes
|
|
29
|
+
*/
|
|
30
|
+
enqueue(events, requiredSignedLength) {
|
|
31
|
+
if (events.length === 0) return;
|
|
32
|
+
|
|
33
|
+
const batch = {
|
|
34
|
+
events,
|
|
35
|
+
requiredSignedLength,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.#pendingBatches.push(batch);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Publishes batches which are already present in the signed view.
|
|
43
|
+
*
|
|
44
|
+
* @param {number} signedLength current signed view length
|
|
45
|
+
* @param {(type: string, ...args: unknown[]) => void} emit event emitter
|
|
46
|
+
*/
|
|
47
|
+
flush(signedLength, emit) {
|
|
48
|
+
const waitingBatches = [];
|
|
49
|
+
|
|
50
|
+
for (const batch of this.#pendingBatches) {
|
|
51
|
+
if (batch.requiredSignedLength > signedLength) {
|
|
52
|
+
waitingBatches.push(batch);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const event of batch.events) {
|
|
57
|
+
const args = event.args || [];
|
|
58
|
+
emit(event.type, ...args);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.#pendingBatches = waitingBatches;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Removes events which have not been published yet. */
|
|
66
|
+
clear() {
|
|
67
|
+
this.#pendingBatches = [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -4,26 +4,28 @@ import _ from 'lodash'
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Checks if a given address is a valid TRAC bech32m address.
|
|
7
|
-
*
|
|
8
|
-
* So, it is possible that even if an address is considered valid,
|
|
9
|
-
* it may not be a real address on the network.
|
|
7
|
+
* Checks the format, length, and bech32m checksum.
|
|
10
8
|
* @param {string | Buffer} address - The address to validate.
|
|
11
9
|
* @param {string} hrp - The HRP of the bech32m address.
|
|
12
10
|
* @returns {boolean} True if the address is valid, false otherwise.
|
|
13
11
|
*/
|
|
14
12
|
export function isAddressValid(address, hrp) {
|
|
15
13
|
if (b4a.isBuffer(address)) {
|
|
16
|
-
|
|
14
|
+
const decodedAddress = address.toString('ascii');
|
|
15
|
+
if (!b4a.equals(address, b4a.from(decodedAddress, 'ascii'))) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
address = decodedAddress;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const res = _.attempt(() =>
|
|
20
|
-
addressApi.size(hrp) === address.length && address.startsWith(`${hrp}1`) &&
|
|
22
|
+
addressApi.size(hrp) === address.length && address.startsWith(`${hrp}1`) &&
|
|
23
|
+
addressApi.isValid(address) && addressApi.canDecode(address)
|
|
21
24
|
);
|
|
22
25
|
|
|
23
26
|
return !_.isError(res) && res
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
|
|
27
29
|
/**
|
|
28
30
|
* Converts a valid bech32m address string to a buffer.
|
|
29
31
|
* @param {string} bech32mAddress - The bech32m address to convert.
|
|
@@ -52,7 +54,9 @@ export function addressToBuffer(bech32mAddress, hrp) {
|
|
|
52
54
|
// TODO: Do we really need to try-catch here? Maybe we should only validate the input buffer.
|
|
53
55
|
export function bufferToAddress(dataBuffer, hrp) {
|
|
54
56
|
try {
|
|
57
|
+
if (!b4a.isBuffer(dataBuffer)) return null;
|
|
55
58
|
const address = dataBuffer.toString('ascii');
|
|
59
|
+
if (!b4a.equals(dataBuffer, b4a.from(address, 'ascii'))) return null;
|
|
56
60
|
if (!isAddressValid(address, hrp)) return null;
|
|
57
61
|
return address;
|
|
58
62
|
} catch (error) {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
HASH_BYTE_LENGTH,
|
|
5
|
+
SIGNATURE_BYTE_LENGTH,
|
|
6
|
+
VDF_PROOF_BYTE_LENGTHS,
|
|
7
|
+
ConsensusVersion
|
|
8
|
+
} from '../../../utils/constants.js';
|
|
9
|
+
import {
|
|
10
|
+
safeDecodeConsensusConfig,
|
|
11
|
+
safeEncodeEpochProofV1,
|
|
12
|
+
safeEncodeEpochRecord
|
|
13
|
+
} from '../../../codecs/apply/applyOperationCodec.js';
|
|
14
|
+
import {safeDecodeVdfConfig} from '../../../codecs/consensus/v1/vdfConfigCodec.js';
|
|
15
|
+
import {safeEncodeProofProposal} from '../../../codecs/consensus/v1/consensusV1OperationCodec.js';
|
|
16
|
+
import {addressToBuffer} from './address.js';
|
|
17
|
+
import {safeUint16ToBuffer} from '../../../utils/buffer.js';
|
|
18
|
+
|
|
19
|
+
// Apply-only genesis formats. Preserve existing factories for historical replay.
|
|
20
|
+
const GENESIS_EPOCH_FACTORIES = Object.freeze({
|
|
21
|
+
[ConsensusVersion.VDF_V1]: createVdfV1GenesisEpochProof,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates epoch zero using the initial config carried by SET_GENESIS_EPOCH,
|
|
26
|
+
* not the latest signed config. SET_CONSENSUS_CONFIG must not recreate genesis.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} proposerAddress Genesis proposer address.
|
|
29
|
+
* @param {Buffer} encodedConsensusConfig Encoded initial consensus config.
|
|
30
|
+
* @param {Config} config Application configuration.
|
|
31
|
+
* @returns {Promise<Buffer|null>} Encoded { sv, data } genesis epoch record or null on failure.
|
|
32
|
+
*/
|
|
33
|
+
export async function createGenesisEpochProof(proposerAddress, encodedConsensusConfig, config) {
|
|
34
|
+
const consensusConfig = safeDecodeConsensusConfig(encodedConsensusConfig);
|
|
35
|
+
if (consensusConfig === null) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const createForSchema = GENESIS_EPOCH_FACTORIES[consensusConfig.sv.readUInt8(0)];
|
|
40
|
+
if (typeof createForSchema !== 'function') {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const genesisData = await createForSchema(config, proposerAddress, consensusConfig.cd);
|
|
45
|
+
if (!b4a.isBuffer(genesisData) || genesisData.length === 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const genesisRecord = safeEncodeEpochRecord({ sv: consensusConfig.sv, data: genesisData });
|
|
50
|
+
return genesisRecord.length > 0 ? genesisRecord : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates the VDF v1 representation of epoch zero.
|
|
56
|
+
* The caller wraps these bytes in the versioned epoch record before hashing.
|
|
57
|
+
*
|
|
58
|
+
* @param {Config} config Application configuration.
|
|
59
|
+
* @param {string} proposerAddress Genesis proposer address.
|
|
60
|
+
* @param {Buffer} encodedConfigData Encoded VDF v1 config.
|
|
61
|
+
* @returns {Promise<Buffer|null>} Encoded epoch proof or null on validation failure.
|
|
62
|
+
*/
|
|
63
|
+
export async function createVdfV1GenesisEpochProof(config, proposerAddress, encodedConfigData) {
|
|
64
|
+
const configData = safeDecodeVdfConfig(encodedConfigData);
|
|
65
|
+
if (configData === null) return null;
|
|
66
|
+
|
|
67
|
+
const proposer = addressToBuffer(proposerAddress, config.addressPrefix);
|
|
68
|
+
if (proposer.length === 0) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const networkId = safeUint16ToBuffer(config.networkId);
|
|
73
|
+
const { difficulty, discriminantBitSize } = configData;
|
|
74
|
+
const proofByteLength = VDF_PROOF_BYTE_LENGTHS[discriminantBitSize.readUInt16BE(0)];
|
|
75
|
+
if (
|
|
76
|
+
networkId.length === 0 ||
|
|
77
|
+
!Number.isInteger(proofByteLength)
|
|
78
|
+
) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const proofData = {
|
|
83
|
+
network_id: networkId,
|
|
84
|
+
epoch: b4a.alloc(8, 0),
|
|
85
|
+
previous_epoch_record_hash: b4a.alloc(HASH_BYTE_LENGTH, 0),
|
|
86
|
+
proposer,
|
|
87
|
+
difficulty,
|
|
88
|
+
discriminant_bit_size: discriminantBitSize,
|
|
89
|
+
proof: b4a.alloc(proofByteLength, 0),
|
|
90
|
+
signature: b4a.alloc(SIGNATURE_BYTE_LENGTH, 0),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const encodedProof = safeEncodeProofProposal(proofData);
|
|
94
|
+
if (encodedProof.length === 0) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const genesisEpochProof = {
|
|
99
|
+
pd: encodedProof,
|
|
100
|
+
app: []
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const encodedEpochProof = safeEncodeEpochProofV1(genesisEpochProof);
|
|
104
|
+
if (encodedEpochProof.length === 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return encodedEpochProof;
|
|
109
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import tracCryptoApi from 'trac-crypto-api';
|
|
3
|
+
import stateGenerated from '../../../codecs/state/state.generated.cjs';
|
|
4
|
+
import {safeDecodeEscrowEntry, safeEncodeEscrowEntry} from '../../../codecs/state/escrowEntryCodec.js';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
BALANCE_BYTE_LENGTH,
|
|
8
|
+
EPOCH_BYTE_LENGTH,
|
|
9
|
+
HASH_BYTE_LENGTH,
|
|
10
|
+
HTLC_LOCK_ID_BYTE_LENGTH,
|
|
11
|
+
HTLC_METADATA_MAX_BYTE_LENGTH,
|
|
12
|
+
HTLC_PREIMAGE_BYTE_LENGTH,
|
|
13
|
+
PUBLIC_KEY_LENGTH,
|
|
14
|
+
} from '../../../utils/constants.js';
|
|
15
|
+
import {isBufferValid, isZeroBuffer, NULL_BUFFER} from '../../../utils/buffer.js';
|
|
16
|
+
import {toBalance} from './balance.js';
|
|
17
|
+
import {bufferToAddress} from './address.js';
|
|
18
|
+
|
|
19
|
+
const {EscrowStatus} = stateGenerated.state;
|
|
20
|
+
|
|
21
|
+
export const ESCROW_ENTRY_VERSION = 3;
|
|
22
|
+
|
|
23
|
+
export const Status = Object.freeze({
|
|
24
|
+
PENDING: EscrowStatus.PENDING,
|
|
25
|
+
CLAIMED: EscrowStatus.CLAIMED,
|
|
26
|
+
REFUNDED: EscrowStatus.REFUNDED,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function isRequiredBuffer(value, size) {
|
|
30
|
+
return isBufferValid(value, size) && !isZeroBuffer(value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isSettlementAddress(value, hrp) {
|
|
34
|
+
const address = bufferToAddress(value, hrp);
|
|
35
|
+
const publicKey = address && tracCryptoApi.address.decodeSafe(address);
|
|
36
|
+
return isRequiredBuffer(publicKey, PUBLIC_KEY_LENGTH);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hasValidSettlementFields({
|
|
40
|
+
lockId,
|
|
41
|
+
lockerAddress,
|
|
42
|
+
claimRecipientAddress,
|
|
43
|
+
refundRecipientAddress,
|
|
44
|
+
amount,
|
|
45
|
+
additionalFeeAmount,
|
|
46
|
+
additionalFeeRecipientAddress,
|
|
47
|
+
hashLock,
|
|
48
|
+
refundEpoch,
|
|
49
|
+
metadata,
|
|
50
|
+
}, hrp) {
|
|
51
|
+
const feeIsZero = isBufferValid(additionalFeeAmount, BALANCE_BYTE_LENGTH) && isZeroBuffer(additionalFeeAmount);
|
|
52
|
+
const requiredFieldsAreValid =
|
|
53
|
+
isRequiredBuffer(lockId, HTLC_LOCK_ID_BYTE_LENGTH) &&
|
|
54
|
+
isSettlementAddress(lockerAddress, hrp) &&
|
|
55
|
+
isSettlementAddress(claimRecipientAddress, hrp) &&
|
|
56
|
+
isSettlementAddress(refundRecipientAddress, hrp) &&
|
|
57
|
+
isRequiredBuffer(amount, BALANCE_BYTE_LENGTH) &&
|
|
58
|
+
isBufferValid(additionalFeeAmount, BALANCE_BYTE_LENGTH) &&
|
|
59
|
+
isRequiredBuffer(hashLock, HASH_BYTE_LENGTH) &&
|
|
60
|
+
isRequiredBuffer(refundEpoch, EPOCH_BYTE_LENGTH);
|
|
61
|
+
const feeFieldsAreValid = feeIsZero
|
|
62
|
+
? additionalFeeRecipientAddress === undefined
|
|
63
|
+
: isSettlementAddress(additionalFeeRecipientAddress, hrp);
|
|
64
|
+
const metadataIsValid = metadata === undefined || (
|
|
65
|
+
b4a.isBuffer(metadata) && metadata.length > 0 && metadata.length <= HTLC_METADATA_MAX_BYTE_LENGTH
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (!requiredFieldsAreValid || !feeFieldsAreValid || !metadataIsValid) return false;
|
|
69
|
+
|
|
70
|
+
const totalLockedAmount = toBalance(amount).add(toBalance(additionalFeeAmount));
|
|
71
|
+
return totalLockedAmount !== null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Encodes settlement state using canonical addresses for the configured prefix.
|
|
76
|
+
* Nonce, signer set, threshold, and signatures remain in the lock transaction.
|
|
77
|
+
*/
|
|
78
|
+
export function init({
|
|
79
|
+
lockId,
|
|
80
|
+
lockerAddress,
|
|
81
|
+
claimRecipientAddress,
|
|
82
|
+
refundRecipientAddress,
|
|
83
|
+
amount,
|
|
84
|
+
additionalFeeAmount,
|
|
85
|
+
additionalFeeRecipientAddress,
|
|
86
|
+
hashLock,
|
|
87
|
+
refundEpoch,
|
|
88
|
+
metadata,
|
|
89
|
+
} = {}, hrp) {
|
|
90
|
+
const entry = {
|
|
91
|
+
version: ESCROW_ENTRY_VERSION,
|
|
92
|
+
status: Status.PENDING,
|
|
93
|
+
lockId,
|
|
94
|
+
lockerAddress,
|
|
95
|
+
claimRecipientAddress,
|
|
96
|
+
refundRecipientAddress,
|
|
97
|
+
amount,
|
|
98
|
+
additionalFeeAmount,
|
|
99
|
+
additionalFeeRecipientAddress,
|
|
100
|
+
hashLock,
|
|
101
|
+
refundEpoch,
|
|
102
|
+
metadata,
|
|
103
|
+
};
|
|
104
|
+
if (!hasValidSettlementFields(entry, hrp)) return NULL_BUFFER;
|
|
105
|
+
return safeEncodeEscrowEntry(entry);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function decode(entry, hrp) {
|
|
109
|
+
try {
|
|
110
|
+
const decoded = safeDecodeEscrowEntry(entry);
|
|
111
|
+
if (
|
|
112
|
+
!decoded ||
|
|
113
|
+
decoded.version !== ESCROW_ENTRY_VERSION ||
|
|
114
|
+
!Object.values(Status).includes(decoded.status) ||
|
|
115
|
+
!hasValidSettlementFields(decoded, hrp) ||
|
|
116
|
+
(decoded.preimage !== undefined && !isRequiredBuffer(decoded.preimage, HTLC_PREIMAGE_BYTE_LENGTH)) ||
|
|
117
|
+
((decoded.status === Status.CLAIMED) !== (decoded.preimage !== undefined))
|
|
118
|
+
) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
decoded.additionalFeeRecipientAddress ??= null;
|
|
123
|
+
decoded.preimage ??= null;
|
|
124
|
+
decoded.metadata ??= null;
|
|
125
|
+
return decoded;
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Pure escrow transitions; network claim admission and refund application are not wired here.
|
|
132
|
+
export function makeClaim(escrowBuffer, preimage, currentEpoch, hrp) {
|
|
133
|
+
const escrow = decode(escrowBuffer, hrp);
|
|
134
|
+
if (
|
|
135
|
+
!escrow ||
|
|
136
|
+
escrow.status !== Status.PENDING ||
|
|
137
|
+
!isRequiredBuffer(preimage, HTLC_PREIMAGE_BYTE_LENGTH) ||
|
|
138
|
+
!isBufferValid(currentEpoch, EPOCH_BYTE_LENGTH) ||
|
|
139
|
+
b4a.compare(currentEpoch, escrow.refundEpoch) >= 0 ||
|
|
140
|
+
!b4a.equals(tracCryptoApi.hash.sha256(preimage), escrow.hashLock)
|
|
141
|
+
) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const claimedEntry = safeEncodeEscrowEntry({...escrow, status: Status.CLAIMED, preimage});
|
|
146
|
+
if (claimedEntry.length === 0) return null;
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
address: escrow.claimRecipientAddress,
|
|
150
|
+
amount: toBalance(escrow.amount),
|
|
151
|
+
additionalFeeRecipientAddress: escrow.additionalFeeRecipientAddress,
|
|
152
|
+
additionalFeeAmount: toBalance(escrow.additionalFeeAmount),
|
|
153
|
+
claimedEscrowEntry: claimedEntry,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function makeRefund(escrowBuffer, currentEpoch, hrp) {
|
|
158
|
+
const escrow = decode(escrowBuffer, hrp);
|
|
159
|
+
if (
|
|
160
|
+
!escrow ||
|
|
161
|
+
escrow.status !== Status.PENDING ||
|
|
162
|
+
!isBufferValid(currentEpoch, EPOCH_BYTE_LENGTH) ||
|
|
163
|
+
b4a.compare(currentEpoch, escrow.refundEpoch) < 0
|
|
164
|
+
) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const refundAmount = toBalance(escrow.amount)?.add(toBalance(escrow.additionalFeeAmount));
|
|
169
|
+
if (!refundAmount) return null;
|
|
170
|
+
const refundedEscrowEntry = safeEncodeEscrowEntry({...escrow, status: Status.REFUNDED});
|
|
171
|
+
if (refundedEscrowEntry.length === 0) return null;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
address: escrow.refundRecipientAddress,
|
|
175
|
+
amount: refundAmount,
|
|
176
|
+
refundedEscrowEntry,
|
|
177
|
+
};
|
|
178
|
+
}
|