trac-msb 0.2.4 → 0.2.6
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/.dockerignore +16 -0
- package/.github/workflows/acceptance-tests.yml +7 -0
- package/.github/workflows/publish.yml +40 -0
- package/.github/workflows/{CI.yml → unit-tests.yml} +1 -1
- package/README.md +175 -50
- package/docker-compose.yml +16 -0
- package/dockerfile +41 -0
- package/docs/fee_distribution.md +89 -0
- package/msb.mjs +12 -14
- package/package.json +8 -4
- package/rpc/constants.mjs +4 -1
- package/rpc/handlers.mjs +109 -66
- package/rpc/routes/v1.mjs +3 -1
- package/rpc/rpc_services.js +126 -0
- package/rpc/utils/confirmedParameter.mjs +17 -0
- package/rpc/utils/url.mjs +38 -0
- package/src/core/network/Network.js +27 -10
- package/src/core/network/identity/NetworkWalletFactory.js +78 -0
- package/src/core/network/services/ConnectionManager.js +2 -2
- package/src/core/network/services/ValidatorObserverService.js +7 -4
- package/src/core/state/State.js +28 -22
- package/src/index.js +197 -385
- package/src/utils/cliCommands.js +280 -0
- package/src/utils/constants.js +3 -1
- package/tests/acceptance/v1/account/account.test.mjs +123 -0
- package/tests/acceptance/v1/balance/balance.test.mjs +55 -0
- package/tests/acceptance/v1/broadcast-transaction/broadcast-transaction.test.mjs +111 -0
- package/tests/acceptance/v1/confirmed-length/confirmed-length.test.mjs +19 -0
- package/tests/acceptance/v1/fee/fee.test.mjs +11 -0
- package/tests/acceptance/v1/rpc.test.mjs +62 -291
- package/tests/acceptance/v1/tx/tx.test.mjs +98 -0
- package/tests/acceptance/v1/tx-details/tx-details.test.mjs +195 -0
- package/tests/acceptance/v1/tx-hashes/tx-hashes.test.mjs +72 -0
- package/tests/acceptance/v1/tx-payloads-bulk/tx-payloads-bulk.test.mjs +27 -0
- package/tests/acceptance/v1/txv/txv.test.mjs +11 -0
- package/tests/acceptance/v1/unconfirmed-length/unconfirmed-length.test.mjs +11 -0
- package/tests/helpers/StateNetworkFactory.js +157 -0
- package/tests/helpers/autobaseTestHelpers.js +369 -0
- package/tests/helpers/createTestSignature.js +12 -0
- package/tests/helpers/transactionPayloads.mjs +78 -0
- package/tests/unit/network/NetworkWalletFactory.test.js +156 -0
- package/tests/unit/state/apply/addAdmin/addAdminHappyPathScenario.js +38 -0
- package/tests/unit/state/apply/addAdmin/addAdminScenarioHelpers.js +273 -0
- package/tests/unit/state/apply/addAdmin/adminEntryEncodingFailureScenario.js +30 -0
- package/tests/unit/state/apply/addAdmin/adminEntryExistsScenario.js +78 -0
- package/tests/unit/state/apply/addAdmin/nodeEntryInitializationFailureScenario.js +30 -0
- package/tests/unit/state/apply/addAdmin/nonBootstrapNodeScenario.js +68 -0
- package/tests/unit/state/apply/addAdmin/state.apply.addAdmin.test.js +155 -0
- package/tests/unit/state/apply/addIndexer/addIndexerHappyPathScenario.js +39 -0
- package/tests/unit/state/apply/addIndexer/addIndexerMultipleIndexersInTheNetworkScenario.js +167 -0
- package/tests/unit/state/apply/addIndexer/addIndexerPretenderAlreadyIndexerScenario.js +21 -0
- package/tests/unit/state/apply/addIndexer/addIndexerPretenderNotWriterScenario.js +21 -0
- package/tests/unit/state/apply/addIndexer/addIndexerRemoveAndReAddScenario.js +186 -0
- package/tests/unit/state/apply/addIndexer/addIndexerScenarioHelpers.js +445 -0
- package/tests/unit/state/apply/addIndexer/addIndexerWriterKeyAlreadyRegisteredScenario.js +32 -0
- package/tests/unit/state/apply/addIndexer/state.apply.addIndexer.test.js +297 -0
- package/tests/unit/state/apply/addWriter/addWriterHappyPathScenario.js +41 -0
- package/tests/unit/state/apply/addWriter/addWriterInvalidValidatorSignatureScenario.js +32 -0
- package/tests/unit/state/apply/addWriter/addWriterNewWkScenario.js +149 -0
- package/tests/unit/state/apply/addWriter/addWriterRequesterAlreadyWriterScenario.js +21 -0
- package/tests/unit/state/apply/addWriter/addWriterRequesterBalanceInsufficientScenario.js +21 -0
- package/tests/unit/state/apply/addWriter/addWriterRequesterEntryDecodeFailureScenario.js +19 -0
- package/tests/unit/state/apply/addWriter/addWriterRequesterEntryMissingScenario.js +19 -0
- package/tests/unit/state/apply/addWriter/addWriterRequesterIndexerScenario.js +21 -0
- package/tests/unit/state/apply/addWriter/addWriterRequesterNotWhitelistedScenario.js +21 -0
- package/tests/unit/state/apply/addWriter/addWriterScenarioHelpers.js +757 -0
- package/tests/unit/state/apply/addWriter/addWriterStakeBalanceUpdateFailureScenario.js +50 -0
- package/tests/unit/state/apply/addWriter/addWriterStakeInsufficientBalanceScenario.js +29 -0
- package/tests/unit/state/apply/addWriter/addWriterStakeInvalidBalanceScenario.js +29 -0
- package/tests/unit/state/apply/addWriter/addWriterStakeInvalidEntryScenario.js +21 -0
- package/tests/unit/state/apply/addWriter/addWriterStakeStakedBalanceFailureScenario.js +37 -0
- package/tests/unit/state/apply/addWriter/addWriterStakeSubtractFailureScenario.js +42 -0
- package/tests/unit/state/apply/addWriter/addWriterValidatorRewardScenario.js +105 -0
- package/tests/unit/state/apply/addWriter/addWriterWriterKeyMismatchScenario.js +54 -0
- package/tests/unit/state/apply/addWriter/addWriterWriterKeyOwnershipScenario.js +54 -0
- package/tests/unit/state/apply/addWriter/addWriterZeroWriterKeyScenario.js +29 -0
- package/tests/unit/state/apply/addWriter/state.apply.addWriter.test.js +309 -0
- package/tests/unit/state/apply/adminRecovery/adminRecoveryHappyPathScenario.js +30 -0
- package/tests/unit/state/apply/adminRecovery/adminRecoveryScenarioHelpers.js +866 -0
- package/tests/unit/state/apply/adminRecovery/state.apply.adminRecovery.test.js +439 -0
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistBanAndReapplyScenario.js +78 -0
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistExistingReaderHappyPathScenario.js +98 -0
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistFeeAfterDisableScenario.js +66 -0
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistHappyPathScenario.js +55 -0
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistInsufficientAdminBalanceScenario.js +103 -0
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistNodeAlreadyWhitelistedScenario.js +60 -0
- package/tests/unit/state/apply/appendWhitelist/appendWhitelistScenarioHelpers.js +191 -0
- package/tests/unit/state/apply/appendWhitelist/state.apply.appendWhitelist.test.js +220 -0
- package/tests/unit/state/apply/balanceInitialization/balanceInitializationHappyPathScenario.js +82 -0
- package/tests/unit/state/apply/balanceInitialization/balanceInitializationScenarioHelpers.js +106 -0
- package/tests/unit/state/apply/balanceInitialization/invalidAmountScenario.js +45 -0
- package/tests/unit/state/apply/balanceInitialization/nodeEntryBalanceUpdateFailureScenario.js +81 -0
- package/tests/unit/state/apply/balanceInitialization/state.apply.balanceInitialization.test.js +189 -0
- package/tests/unit/state/apply/banValidator/banValidatorBanAndReWhitelistScenario.js +155 -0
- package/tests/unit/state/apply/banValidator/banValidatorHappyPathScenario.js +36 -0
- package/tests/unit/state/apply/banValidator/banValidatorScenarioHelpers.js +534 -0
- package/tests/unit/state/apply/banValidator/banValidatorSequentialBansScenario.js +74 -0
- package/tests/unit/state/apply/banValidator/banValidatorTargetDecodeFailureScenario.js +19 -0
- package/tests/unit/state/apply/banValidator/banValidatorTargetIndexerScenario.js +32 -0
- package/tests/unit/state/apply/banValidator/banValidatorTargetNodeEntryMissingScenario.js +19 -0
- package/tests/unit/state/apply/banValidator/banValidatorTargetRoleUpdateFailureScenario.js +19 -0
- package/tests/unit/state/apply/banValidator/banValidatorWhitelistedNonWriterScenario.js +38 -0
- package/tests/unit/state/apply/banValidator/banValidatorWhitelistedZeroBalanceScenario.js +91 -0
- package/tests/unit/state/apply/banValidator/banValidatorWithdrawFailureScenario.js +19 -0
- package/tests/unit/state/apply/banValidator/state.apply.banValidator.test.js +266 -0
- package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentDuplicateRegistrationScenario.js +142 -0
- package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentHappyPathScenario.js +26 -0
- package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentIncompleteOperationScenario.js +94 -0
- package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentInvalidDeploymentEntryScenario.js +37 -0
- package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentMultipleBootstrapScenario.js +86 -0
- package/tests/unit/state/apply/bootstrapDeployment/bootstrapDeploymentScenarioHelpers.js +344 -0
- package/tests/unit/state/apply/bootstrapDeployment/invalidValidatorNodeEntryScenario.js +57 -0
- package/tests/unit/state/apply/bootstrapDeployment/state.apply.bootstrapDeployment.test.js +429 -0
- package/tests/unit/state/apply/common/access-control/adminConsistencyMismatchScenario.js +119 -0
- package/tests/unit/state/apply/common/access-control/adminEntryDecodeFailureScenario.js +130 -0
- package/tests/unit/state/apply/common/access-control/adminEntryExistsScenario.js +93 -0
- package/tests/unit/state/apply/common/access-control/adminEntryMissingScenario.js +108 -0
- package/tests/unit/state/apply/common/access-control/adminOnlyGuardScenario.js +126 -0
- package/tests/unit/state/apply/common/access-control/adminPublicKeyDecodeFailureScenario.js +120 -0
- package/tests/unit/state/apply/common/access-control/roleAccessOperationValidationScenario.js +50 -0
- package/tests/unit/state/apply/common/adminControlOperationValidationScenario.js +56 -0
- package/tests/unit/state/apply/common/balances/adminEntryUpdateFailureScenario.js +52 -0
- package/tests/unit/state/apply/common/balances/base/requesterBalanceScenarioBase.js +197 -0
- package/tests/unit/state/apply/common/balances/feeDecodeFailureScenario.js +52 -0
- package/tests/unit/state/apply/common/balances/requesterBalanceDecodeFailureScenario.js +15 -0
- package/tests/unit/state/apply/common/balances/requesterBalanceFeeApplicationFailureScenario.js +11 -0
- package/tests/unit/state/apply/common/balances/requesterBalanceInsufficientScenario.js +15 -0
- package/tests/unit/state/apply/common/balances/requesterBalanceUpdateFailureScenario.js +11 -0
- package/tests/unit/state/apply/common/balances/validatorEntryRewardFailureScenario.js +11 -0
- package/tests/unit/state/apply/common/balances/validatorEntryUpdateFailureScenario.js +11 -0
- package/tests/unit/state/apply/common/balances/validatorNodeEntryDecodeFailureScenario.js +40 -0
- package/tests/unit/state/apply/common/base/OperationValidationScenarioBase.js +114 -0
- package/tests/unit/state/apply/common/commonScenarioHelper.js +103 -0
- package/tests/unit/state/apply/common/indexer/indexerNodeEntryDecodeFailureScenario.js +36 -0
- package/tests/unit/state/apply/common/indexer/indexerNodeEntryMissingScenario.js +36 -0
- package/tests/unit/state/apply/common/indexer/indexerRoleUpdateFailureScenario.js +29 -0
- package/tests/unit/state/apply/common/indexer/indexerSequenceStateInvalidScenario.js +66 -0
- package/tests/unit/state/apply/common/invalidMessageComponentValidationScenario.js +84 -0
- package/tests/unit/state/apply/common/nodeEntryInitializationFailureScenario.js +47 -0
- package/tests/unit/state/apply/common/operationAlreadyAppliedScenario.js +85 -0
- package/tests/unit/state/apply/common/payload-structure/addressWithInvalidPublicKeyScenario.js +52 -0
- package/tests/unit/state/apply/common/payload-structure/initializationDisabledScenario.js +49 -0
- package/tests/unit/state/apply/common/payload-structure/invalidAddressValidationScenario.js +73 -0
- package/tests/unit/state/apply/common/payload-structure/invalidHashValidationScenario.js +71 -0
- package/tests/unit/state/apply/common/payload-structure/invalidPayloadValidationScenario.js +31 -0
- package/tests/unit/state/apply/common/payload-structure/invalidSignatureValidationScenario.js +142 -0
- package/tests/unit/state/apply/common/payload-structure/partialOperationValidationScenario.js +87 -0
- package/tests/unit/state/apply/common/requester/requesterNodeEntryBufferMissingScenario.js +70 -0
- package/tests/unit/state/apply/common/requester/requesterNodeEntryDecodeFailureScenario.js +72 -0
- package/tests/unit/state/apply/common/requester/requesterNodeEntryMissingScenario.js +36 -0
- package/tests/unit/state/apply/common/requesterAddressValidationScenario.js +44 -0
- package/tests/unit/state/apply/common/requesterPublicKeyValidationScenario.js +25 -0
- package/tests/unit/state/apply/common/transactionValidityMismatchScenario.js +98 -0
- package/tests/unit/state/apply/common/validatorConsistency/base/validatorConsistencyScenarioBase.js +201 -0
- package/tests/unit/state/apply/common/validatorConsistency/validatorEntryDecodeFailureScenario.js +17 -0
- package/tests/unit/state/apply/common/validatorConsistency/validatorEntryMissingScenario.js +44 -0
- package/tests/unit/state/apply/common/validatorConsistency/validatorInactiveScenario.js +19 -0
- package/tests/unit/state/apply/common/validatorConsistency/validatorWriterKeyMismatchScenario.js +18 -0
- package/tests/unit/state/apply/common/validatorEntryValidation/base/validatorEntryValidationScenarioBase.js +314 -0
- package/tests/unit/state/apply/common/validatorEntryValidation/validatorEntryInvalidBalanceScenario.js +18 -0
- package/tests/unit/state/apply/common/writerKeyExistsValidationScenario.js +43 -0
- package/tests/unit/state/apply/disableInitialization/disableInitializationAlreadyDisabledScenario.js +53 -0
- package/tests/unit/state/apply/disableInitialization/disableInitializationHappyPathScenario.js +24 -0
- package/tests/unit/state/apply/disableInitialization/disableInitializationScenarioHelpers.js +197 -0
- package/tests/unit/state/apply/disableInitialization/state.apply.disableInitialization.test.js +161 -0
- package/tests/unit/state/apply/missing-tests.md +18 -0
- package/tests/unit/state/apply/removeIndexer/removeIndexerHappyPathScenario.js +58 -0
- package/tests/unit/state/apply/removeIndexer/removeIndexerReAddAndRemoveAgainScenario.js +98 -0
- package/tests/unit/state/apply/removeIndexer/removeIndexerRemoveMultipleIndexersScenario.js +167 -0
- package/tests/unit/state/apply/removeIndexer/removeIndexerScenarioHelpers.js +428 -0
- package/tests/unit/state/apply/removeIndexer/removeIndexerTargetNotIndexerScenario.js +22 -0
- package/tests/unit/state/apply/removeIndexer/removeIndexerWriterKeyMissingScenario.js +20 -0
- package/tests/unit/state/apply/removeIndexer/state.apply.removeIndexer.test.js +291 -0
- package/tests/unit/state/apply/removeWriter/removeWriterAndAddWriterAgainScenario.js +87 -0
- package/tests/unit/state/apply/removeWriter/removeWriterHappyPathScenario.js +38 -0
- package/tests/unit/state/apply/removeWriter/removeWriterInvalidValidatorSignatureScenario.js +32 -0
- package/tests/unit/state/apply/removeWriter/removeWriterRequesterBalanceInsufficientScenario.js +21 -0
- package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryDecodeFailureScenario.js +19 -0
- package/tests/unit/state/apply/removeWriter/removeWriterRequesterEntryMissingScenario.js +19 -0
- package/tests/unit/state/apply/removeWriter/removeWriterRequesterIndexerScenario.js +21 -0
- package/tests/unit/state/apply/removeWriter/removeWriterRequesterNotWriterScenario.js +21 -0
- package/tests/unit/state/apply/removeWriter/removeWriterRequesterRoleUpdateFailureScenario.js +19 -0
- package/tests/unit/state/apply/removeWriter/removeWriterScenarioHelpers.js +344 -0
- package/tests/unit/state/apply/removeWriter/removeWriterThroughWriterValidatorScenario.js +113 -0
- package/tests/unit/state/apply/removeWriter/removeWriterUnstakeFailureScenario.js +33 -0
- package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyMismatchScenario.js +21 -0
- package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyOwnershipScenario.js +26 -0
- package/tests/unit/state/apply/removeWriter/removeWriterWriterKeyRegistryMissingScenario.js +22 -0
- package/tests/unit/state/apply/removeWriter/state.apply.removeWriter.test.js +307 -0
- package/tests/unit/state/apply/state.apply.test.js +24 -0
- package/tests/unit/state/apply/transfer/state.apply.transfer.test.js +819 -0
- package/tests/unit/state/apply/transfer/transferContractSchemaValidationScenario.js +22 -0
- package/tests/unit/state/apply/transfer/transferDoubleSpendAcrossValidatorsScenario.js +137 -0
- package/tests/unit/state/apply/transfer/transferDoubleSpendSameBatchScenario.js +63 -0
- package/tests/unit/state/apply/transfer/transferDoubleSpendSingleValidatorScenario.js +67 -0
- package/tests/unit/state/apply/transfer/transferExistingRecipientAmountScenario.js +31 -0
- package/tests/unit/state/apply/transfer/transferExistingRecipientZeroAmountScenario.js +31 -0
- package/tests/unit/state/apply/transfer/transferHandlerGuardScenarios.js +22 -0
- package/tests/unit/state/apply/transfer/transferHappyPathScenario.js +8 -0
- package/tests/unit/state/apply/transfer/transferInvalidIncomingDataScenario.js +66 -0
- package/tests/unit/state/apply/transfer/transferNewRecipientAmountScenario.js +31 -0
- package/tests/unit/state/apply/transfer/transferNewRecipientZeroAmountScenario.js +31 -0
- package/tests/unit/state/apply/transfer/transferScenarioHelpers.js +1167 -0
- package/tests/unit/state/apply/transfer/transferSelfTransferAmountScenario.js +38 -0
- package/tests/unit/state/apply/transfer/transferSelfTransferZeroAmountScenario.js +38 -0
- package/tests/unit/state/apply/transfer/transferValidatorRecipientAmountScenario.js +38 -0
- package/tests/unit/state/apply/transfer/transferValidatorRecipientZeroAmountScenario.js +38 -0
- package/tests/unit/state/apply/txOperation/state.apply.txOperation.test.js +318 -0
- package/tests/unit/state/apply/txOperation/txOperationBootstrapNotRegisteredScenario.js +70 -0
- package/tests/unit/state/apply/txOperation/txOperationDifferentValidatorCreatorHappyPathScenario.js +23 -0
- package/tests/unit/state/apply/txOperation/txOperationInvalidDeploymentEntryScenario.js +48 -0
- package/tests/unit/state/apply/txOperation/txOperationInvalidFeeAmountScenario.js +39 -0
- package/tests/unit/state/apply/txOperation/txOperationInvalidSubnetCreatorAddressScenario.js +46 -0
- package/tests/unit/state/apply/txOperation/txOperationRequesterCreatorHappyPathScenario.js +21 -0
- package/tests/unit/state/apply/txOperation/txOperationScenarioHelpers.js +429 -0
- package/tests/unit/state/apply/txOperation/txOperationStandardHappyPathScenario.js +21 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddCreatorBalanceFailureScenario.js +26 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBalanceFailureScenario.js +25 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeAddValidatorBonusFailureScenario.js +27 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeCreatorEntryScenario.js +18 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeRequesterEntryScenario.js +17 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeDecodeValidatorEntryScenario.js +31 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardBypassScenario.js +49 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeGuardScenarioFactory.js +92 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeInsufficientRequesterBalanceScenario.js +28 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidCreatorBalanceScenario.js +29 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterBalanceScenario.js +28 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidRequesterEntryScenario.js +17 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeInvalidValidatorBalanceScenario.js +33 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeMissingCreatorEntryScenario.js +18 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeSubtractFailureScenario.js +25 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateCreatorBalanceFailureScenario.js +26 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateFailureScenario.js +25 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBalanceFailureScenario.js +26 -0
- package/tests/unit/state/apply/txOperation/txOperationTransferFeeUpdateValidatorBonusFailureScenario.js +27 -0
- package/tests/unit/state/apply/txOperation/txOperationValidatorCreatorHappyPathScenario.js +21 -0
- package/tests/unit/state/stateModule.test.js +1 -0
- package/tests/unit/state/stateTestUtils.js +5 -1
- package/.env +0 -3
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import b4a from "b4a";
|
|
2
|
+
import tracCrypto from "trac-crypto-api";
|
|
3
|
+
|
|
4
|
+
import { $TNK } from "../../src/core/state/utils/balance.js";
|
|
5
|
+
import { createMessage } from "../../src/utils/buffer.js";
|
|
6
|
+
import { blake3Hash } from "../../src/utils/crypto.js";
|
|
7
|
+
import { OperationType, NETWORK_ID } from "../../src/utils/constants.js";
|
|
8
|
+
import { addressToBuffer } from "../../src/core/state/utils/address.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build a base64-encoded transfer payload and matching tx hash
|
|
12
|
+
* that are compatible with MSB's PartialTransfer validator.
|
|
13
|
+
*
|
|
14
|
+
* This helper mirrors the hashing/signing logic used by
|
|
15
|
+
* PartialOperation.validateSignature, so that tests broadcast
|
|
16
|
+
* transactions the node will accept without touching consensus code.
|
|
17
|
+
*
|
|
18
|
+
* @param {import("trac-wallet").default} wallet - Writer wallet used for signing.
|
|
19
|
+
* @param {import("../../src/core/state/State.js").default} state - MSB state instance.
|
|
20
|
+
* @param {bigint} [amountTnk=1n] - Transfer amount in TNK units.
|
|
21
|
+
* @returns {Promise<{ payload: string, txHashHex: string }>}
|
|
22
|
+
*/
|
|
23
|
+
export async function buildRpcSelfTransferPayload(wallet, state, amountTnk = 1n) {
|
|
24
|
+
const txvBuffer = await state.getIndexerSequenceState();
|
|
25
|
+
const txvHex = b4a.toString(txvBuffer, "hex");
|
|
26
|
+
|
|
27
|
+
const txData = await tracCrypto.transaction.preBuild(
|
|
28
|
+
wallet.address,
|
|
29
|
+
wallet.address,
|
|
30
|
+
b4a.toString($TNK(amountTnk), "hex"),
|
|
31
|
+
txvHex
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const nonceHex = b4a.toString(txData.nonce, "hex");
|
|
35
|
+
const amountHex = txData.amount;
|
|
36
|
+
const toAddress = txData.to;
|
|
37
|
+
|
|
38
|
+
const txvBuf = b4a.from(txData.validity, "hex");
|
|
39
|
+
const nonceBuf = b4a.from(nonceHex, "hex");
|
|
40
|
+
const amountBuf = b4a.from(amountHex, "hex");
|
|
41
|
+
const toBuf = addressToBuffer(toAddress);
|
|
42
|
+
|
|
43
|
+
const message = createMessage(
|
|
44
|
+
NETWORK_ID,
|
|
45
|
+
txvBuf,
|
|
46
|
+
toBuf,
|
|
47
|
+
amountBuf,
|
|
48
|
+
nonceBuf,
|
|
49
|
+
OperationType.TRANSFER
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const messageHash = await blake3Hash(message);
|
|
53
|
+
const signature = wallet.sign(messageHash);
|
|
54
|
+
|
|
55
|
+
const payloadObject = {
|
|
56
|
+
type: OperationType.TRANSFER,
|
|
57
|
+
address: wallet.address,
|
|
58
|
+
tro: {
|
|
59
|
+
tx: b4a.toString(messageHash, "hex"),
|
|
60
|
+
txv: txData.validity,
|
|
61
|
+
in: nonceHex,
|
|
62
|
+
to: toAddress,
|
|
63
|
+
am: amountHex,
|
|
64
|
+
is: b4a.toString(signature, "hex")
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const payload = b4a.toString(
|
|
69
|
+
b4a.from(JSON.stringify(payloadObject)),
|
|
70
|
+
"base64"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
payload,
|
|
75
|
+
txHashHex: b4a.toString(messageHash, "hex")
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import b4a from 'b4a';
|
|
4
|
+
|
|
5
|
+
import PeerWallet from 'trac-wallet';
|
|
6
|
+
import { TRAC_NETWORK_MSB_MAINNET_PREFIX } from 'trac-wallet/constants.js';
|
|
7
|
+
|
|
8
|
+
import NetworkWalletFactory, { EphemeralWallet } from '../../../src/core/network/identity/NetworkWalletFactory.js';
|
|
9
|
+
import { errorMessageIncludes } from '../../helpers/regexHelper.js';
|
|
10
|
+
import { testKeyPair1, testKeyPair2 } from '../../fixtures/apply.fixtures.js';
|
|
11
|
+
|
|
12
|
+
test('NetworkWalletFactory.provide returns wallet when enabled', async t => {
|
|
13
|
+
const publicKey = b4a.from(testKeyPair2.publicKey, 'hex');
|
|
14
|
+
const address = PeerWallet.encodeBech32m(TRAC_NETWORK_MSB_MAINNET_PREFIX, publicKey);
|
|
15
|
+
const signResult = b4a.from('abcd', 'hex');
|
|
16
|
+
const wallet = {
|
|
17
|
+
publicKey,
|
|
18
|
+
address,
|
|
19
|
+
sign: sinon.stub().returns(signResult),
|
|
20
|
+
verify: sinon.stub().returns(true)
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const provider = NetworkWalletFactory.provide({ wallet, enableWallet: true });
|
|
24
|
+
const message = b4a.from('00112233', 'hex');
|
|
25
|
+
const signature = provider.sign(message);
|
|
26
|
+
|
|
27
|
+
t.alike(provider.publicKey, publicKey);
|
|
28
|
+
t.is(provider.address, address);
|
|
29
|
+
t.alike(signature, signResult);
|
|
30
|
+
t.ok(wallet.sign.calledOnceWithExactly(message));
|
|
31
|
+
|
|
32
|
+
const verifyResult = provider.verify(signature, message);
|
|
33
|
+
t.ok(verifyResult);
|
|
34
|
+
t.ok(wallet.verify.calledOnceWithExactly(signature, message));
|
|
35
|
+
|
|
36
|
+
sinon.restore();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('NetworkWalletFactory.provide requires both public and secret keys when wallet disabled', async t => {
|
|
40
|
+
const publicKey = b4a.from(testKeyPair1.publicKey, 'hex');
|
|
41
|
+
await t.exception(
|
|
42
|
+
() => NetworkWalletFactory.provide({ enableWallet: false, keyPair: { publicKey } }),
|
|
43
|
+
errorMessageIncludes('keyPair with publicKey and secretKey is required')
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('NetworkWalletFactory.provide rejects non-buffer inputs', async t => {
|
|
48
|
+
const secretKey = b4a.from(testKeyPair1.secretKey, 'hex');
|
|
49
|
+
await t.exception(
|
|
50
|
+
() =>
|
|
51
|
+
NetworkWalletFactory.provide({
|
|
52
|
+
enableWallet: false,
|
|
53
|
+
keyPair: { publicKey: 'not-a-buffer', secretKey }
|
|
54
|
+
}),
|
|
55
|
+
errorMessageIncludes('must be a Buffer')
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('NetworkWalletFactory.provide propagates invalid public key length errors', async t => {
|
|
60
|
+
const secretKey = b4a.from(testKeyPair1.secretKey, 'hex');
|
|
61
|
+
const invalidPublicKey = b4a.alloc(10);
|
|
62
|
+
|
|
63
|
+
await t.exception(
|
|
64
|
+
() =>
|
|
65
|
+
NetworkWalletFactory.provide({
|
|
66
|
+
enableWallet: false,
|
|
67
|
+
keyPair: { publicKey: invalidPublicKey, secretKey },
|
|
68
|
+
networkPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX
|
|
69
|
+
}),
|
|
70
|
+
errorMessageIncludes('Invalid public key')
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('NetworkWalletFactory.provide derives address and signs payloads from keyPair', async t => {
|
|
75
|
+
const keyPair = {
|
|
76
|
+
publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
|
|
77
|
+
secretKey: b4a.from(testKeyPair1.secretKey, 'hex')
|
|
78
|
+
};
|
|
79
|
+
const provider = NetworkWalletFactory.provide({
|
|
80
|
+
enableWallet: false,
|
|
81
|
+
keyPair,
|
|
82
|
+
networkPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX
|
|
83
|
+
});
|
|
84
|
+
const message = b4a.from('123455555', 'hex');
|
|
85
|
+
const signature = provider.sign(message);
|
|
86
|
+
|
|
87
|
+
t.is(
|
|
88
|
+
provider.address,
|
|
89
|
+
PeerWallet.encodeBech32m(TRAC_NETWORK_MSB_MAINNET_PREFIX, provider.publicKey)
|
|
90
|
+
);
|
|
91
|
+
t.ok(PeerWallet.verify(signature, message, provider.publicKey));
|
|
92
|
+
t.ok(provider.verify(signature, message));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('NetworkWalletFactory handles falsy address derivation results', async t => {
|
|
96
|
+
const keyPair = {
|
|
97
|
+
publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
|
|
98
|
+
secretKey: b4a.from(testKeyPair1.secretKey, 'hex')
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const stub = sinon.stub(PeerWallet, 'encodeBech32m').returns(null);
|
|
102
|
+
await t.exception(
|
|
103
|
+
() =>
|
|
104
|
+
NetworkWalletFactory.provide({
|
|
105
|
+
enableWallet: false,
|
|
106
|
+
keyPair,
|
|
107
|
+
networkPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX
|
|
108
|
+
}),
|
|
109
|
+
errorMessageIncludes('failed to derive address')
|
|
110
|
+
);
|
|
111
|
+
stub.restore();
|
|
112
|
+
sinon.restore();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('NetworkWalletFactory propagates encoder exceptions', async t => {
|
|
116
|
+
const keyPair = {
|
|
117
|
+
publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
|
|
118
|
+
secretKey: b4a.from(testKeyPair1.secretKey, 'hex')
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const stub = sinon.stub(PeerWallet, 'encodeBech32m').throws(new Error('test exception'));
|
|
122
|
+
await t.exception(
|
|
123
|
+
() =>
|
|
124
|
+
NetworkWalletFactory.provide({
|
|
125
|
+
enableWallet: false,
|
|
126
|
+
keyPair,
|
|
127
|
+
networkPrefix: TRAC_NETWORK_MSB_MAINNET_PREFIX
|
|
128
|
+
}),
|
|
129
|
+
errorMessageIncludes('test exception')
|
|
130
|
+
);
|
|
131
|
+
stub.restore();
|
|
132
|
+
sinon.restore();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('EphemeralWallet exposes wallet like interface', async t => {
|
|
136
|
+
const keyPair = {
|
|
137
|
+
publicKey: b4a.from(testKeyPair1.publicKey, 'hex'),
|
|
138
|
+
secretKey: b4a.from(testKeyPair1.secretKey, 'hex')
|
|
139
|
+
};
|
|
140
|
+
const wallet = new EphemeralWallet(keyPair, TRAC_NETWORK_MSB_MAINNET_PREFIX);
|
|
141
|
+
const message = b4a.from('feedface', 'hex');
|
|
142
|
+
const signature = wallet.sign(message);
|
|
143
|
+
|
|
144
|
+
t.alike(wallet.publicKey, keyPair.publicKey);
|
|
145
|
+
t.is(wallet.address, PeerWallet.encodeBech32m(TRAC_NETWORK_MSB_MAINNET_PREFIX, keyPair.publicKey));
|
|
146
|
+
t.ok(PeerWallet.verify(signature, message, wallet.publicKey));
|
|
147
|
+
t.ok(wallet.verify(signature, message));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('EphemeralWallet requires both public and secret keys', async t => {
|
|
151
|
+
const publicKey = b4a.from(testKeyPair1.publicKey, 'hex');
|
|
152
|
+
await t.exception(
|
|
153
|
+
() => new EphemeralWallet({ publicKey }, TRAC_NETWORK_MSB_MAINNET_PREFIX),
|
|
154
|
+
errorMessageIncludes('keyPair with publicKey and secretKey is required')
|
|
155
|
+
);
|
|
156
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import {
|
|
3
|
+
eventFlush,
|
|
4
|
+
deriveIndexerSequenceState
|
|
5
|
+
} from '../../../../helpers/autobaseTestHelpers.js';
|
|
6
|
+
import nodeEntryUtils from '../../../../../src/core/state/utils/nodeEntry.js';
|
|
7
|
+
import { toTerm } from '../../../../../src/core/state/utils/balance.js';
|
|
8
|
+
import CompleteStateMessageOperations from '../../../../../src/messages/completeStateMessages/CompleteStateMessageOperations.js';
|
|
9
|
+
|
|
10
|
+
import { setupAddAdminScenario, assertAdminState } from './addAdminScenarioHelpers.js';
|
|
11
|
+
|
|
12
|
+
export default function addAdminHappyPathScenario() {
|
|
13
|
+
test('State.apply addAdmin bootstraps admin node - happy path', async t => {
|
|
14
|
+
const networkContext = await setupAddAdminScenario(t);
|
|
15
|
+
const adminNode = networkContext.adminBootstrap;
|
|
16
|
+
const readerNodes = networkContext.peers.slice(1);
|
|
17
|
+
const reader = readerNodes[0];
|
|
18
|
+
|
|
19
|
+
const txValidity = await deriveIndexerSequenceState(adminNode.base);
|
|
20
|
+
const addAdminPayload = await CompleteStateMessageOperations.assembleAddAdminMessage(
|
|
21
|
+
adminNode.wallet,
|
|
22
|
+
adminNode.base.local.key,
|
|
23
|
+
txValidity
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
await adminNode.base.append(addAdminPayload);
|
|
27
|
+
await adminNode.base.update();
|
|
28
|
+
await eventFlush();
|
|
29
|
+
|
|
30
|
+
await assertAdminState(t, adminNode.base, adminNode.wallet, adminNode.base.local.key, addAdminPayload);
|
|
31
|
+
|
|
32
|
+
await networkContext.sync();
|
|
33
|
+
await assertAdminState(t, reader.base, adminNode.wallet, adminNode.base.local.key, addAdminPayload);
|
|
34
|
+
|
|
35
|
+
const readerNodeEntry = await reader.base.view.get(reader.wallet.address);
|
|
36
|
+
t.is(readerNodeEntry, null, 'reader node remains without any role');
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import { setupStateNetwork } from '../../../../helpers/StateNetworkFactory.js';
|
|
3
|
+
import {
|
|
4
|
+
seedBootstrapIndexer,
|
|
5
|
+
defaultOpenHyperbeeView,
|
|
6
|
+
deriveIndexerSequenceState,
|
|
7
|
+
eventFlush
|
|
8
|
+
} from '../../../../helpers/autobaseTestHelpers.js';
|
|
9
|
+
import CompleteStateMessageOperations from '../../../../../src/messages/completeStateMessages/CompleteStateMessageOperations.js';
|
|
10
|
+
import {
|
|
11
|
+
safeDecodeApplyOperation,
|
|
12
|
+
safeEncodeApplyOperation
|
|
13
|
+
} from '../../../../../src/utils/protobuf/operationHelpers.js';
|
|
14
|
+
import {
|
|
15
|
+
AUTOBASE_VALUE_ENCODING,
|
|
16
|
+
EntryType,
|
|
17
|
+
ADMIN_INITIAL_BALANCE,
|
|
18
|
+
ADMIN_INITIAL_STAKED_BALANCE
|
|
19
|
+
} from '../../../../../src/utils/constants.js';
|
|
20
|
+
import { createSignature } from '../../../../helpers/createTestSignature.js';
|
|
21
|
+
import adminEntryUtils from '../../../../../src/core/state/utils/adminEntry.js';
|
|
22
|
+
import nodeEntryUtils from '../../../../../src/core/state/utils/nodeEntry.js';
|
|
23
|
+
import lengthEntryUtils from '../../../../../src/core/state/utils/lengthEntry.js';
|
|
24
|
+
import addressUtils from '../../../../../src/core/state/utils/address.js';
|
|
25
|
+
import { safeWriteUInt32BE } from '../../../../../src/utils/buffer.js';
|
|
26
|
+
|
|
27
|
+
export async function setupAddAdminScenario(t) {
|
|
28
|
+
const context = await setupStateNetwork({
|
|
29
|
+
nodes: 2,
|
|
30
|
+
valueEncoding: AUTOBASE_VALUE_ENCODING,
|
|
31
|
+
open: defaultOpenHyperbeeView
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
seedBootstrapIndexer(context);
|
|
35
|
+
|
|
36
|
+
t.teardown(async () => {
|
|
37
|
+
await context.teardown();
|
|
38
|
+
});
|
|
39
|
+
return context;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function buildAddAdminRequesterPayload(context) {
|
|
43
|
+
const adminNode = context.adminBootstrap;
|
|
44
|
+
const txValidity = await deriveIndexerSequenceState(adminNode.base);
|
|
45
|
+
return CompleteStateMessageOperations.assembleAddAdminMessage(
|
|
46
|
+
adminNode.wallet,
|
|
47
|
+
adminNode.base.local.key,
|
|
48
|
+
txValidity
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function assertAddAdminRequesterFailureState(t, context) {
|
|
53
|
+
const adminNode = context.adminBootstrap;
|
|
54
|
+
const readerNodes = context.peers.slice(1);
|
|
55
|
+
|
|
56
|
+
const adminEntryRecord = await adminNode.base.view.get(EntryType.ADMIN);
|
|
57
|
+
t.is(adminEntryRecord, null, 'admin entry remains absent');
|
|
58
|
+
|
|
59
|
+
const initializationEntry = await adminNode.base.view.get(EntryType.INITIALIZATION);
|
|
60
|
+
t.is(initializationEntry, null, 'initialization flag not set');
|
|
61
|
+
|
|
62
|
+
const writerRegistry = await adminNode.base.view.get(
|
|
63
|
+
EntryType.WRITER_ADDRESS + adminNode.base.local.key.toString('hex')
|
|
64
|
+
);
|
|
65
|
+
t.is(writerRegistry, null, 'writer registry not created');
|
|
66
|
+
|
|
67
|
+
await context.sync();
|
|
68
|
+
for (const reader of readerNodes) {
|
|
69
|
+
const readerAdminEntry = await reader.base.view.get(EntryType.ADMIN);
|
|
70
|
+
t.is(readerAdminEntry, null, 'reader node never observes admin entry');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function assertAddAdminRequesterFailureStateLocal(t, context) {
|
|
75
|
+
const adminNode = context.adminBootstrap;
|
|
76
|
+
|
|
77
|
+
const adminEntryRecord = await adminNode.base.view.get(EntryType.ADMIN);
|
|
78
|
+
t.is(adminEntryRecord, null, 'admin entry remains absent');
|
|
79
|
+
|
|
80
|
+
const initializationEntry = await adminNode.base.view.get(EntryType.INITIALIZATION);
|
|
81
|
+
t.is(initializationEntry, null, 'initialization flag not set');
|
|
82
|
+
|
|
83
|
+
const writerRegistry = await adminNode.base.view.get(
|
|
84
|
+
EntryType.WRITER_ADDRESS + adminNode.base.local.key.toString('hex')
|
|
85
|
+
);
|
|
86
|
+
t.is(writerRegistry, null, 'writer registry not created');
|
|
87
|
+
|
|
88
|
+
for (const reader of context.peers.slice(1)) {
|
|
89
|
+
const readerAdminEntry = await reader.base.view.get(EntryType.ADMIN);
|
|
90
|
+
t.is(readerAdminEntry, null, 'reader node never observes admin entry');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function mutateAddAdminPayloadForInvalidSchema(t, validPayload) {
|
|
95
|
+
const decodedPayload = safeDecodeApplyOperation(validPayload);
|
|
96
|
+
t.ok(decodedPayload, 'fixtures decode');
|
|
97
|
+
|
|
98
|
+
decodedPayload.cao.tx = b4a.alloc(decodedPayload.cao.tx.length);
|
|
99
|
+
return safeEncodeApplyOperation(decodedPayload);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function assertAdminState(t, base, wallet, writingKey, payload) {
|
|
103
|
+
const adminEntryRecord = await base.view.get(EntryType.ADMIN);
|
|
104
|
+
t.ok(adminEntryRecord, 'admin entry should exist');
|
|
105
|
+
|
|
106
|
+
const decodedAdminEntry = adminEntryUtils.decode(adminEntryRecord.value);
|
|
107
|
+
t.ok(decodedAdminEntry, 'admin entry decodes');
|
|
108
|
+
t.is(decodedAdminEntry.address, wallet.address, 'admin entry stores wallet address');
|
|
109
|
+
t.ok(b4a.equals(decodedAdminEntry.wk, writingKey), 'admin entry stores writing key');
|
|
110
|
+
|
|
111
|
+
const nodeEntryRecord = await base.view.get(wallet.address);
|
|
112
|
+
t.ok(nodeEntryRecord, 'node entry should exist for admin address');
|
|
113
|
+
|
|
114
|
+
const nodeEntry = nodeEntryUtils.decode(nodeEntryRecord.value);
|
|
115
|
+
t.ok(nodeEntry, 'node entry decodes');
|
|
116
|
+
t.is(nodeEntry.isWriter, true, 'node entry flagged as writer');
|
|
117
|
+
t.is(nodeEntry.isIndexer, true, 'node entry flagged as indexer');
|
|
118
|
+
t.ok(b4a.equals(nodeEntry?.wk, writingKey), 'node entry writing key matches');
|
|
119
|
+
t.ok(b4a.equals(nodeEntry?.balance, ADMIN_INITIAL_BALANCE), 'admin initial balance is set');
|
|
120
|
+
t.ok(
|
|
121
|
+
b4a.equals(nodeEntry?.stakedBalance, ADMIN_INITIAL_STAKED_BALANCE),
|
|
122
|
+
'admin initial staked balance is set'
|
|
123
|
+
);
|
|
124
|
+
t.ok(
|
|
125
|
+
b4a.equals(nodeEntry?.license, lengthEntryUtils.encodeBE(1)),
|
|
126
|
+
'admin license id assigned'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const adminAddressBuffer = addressUtils.addressToBuffer(wallet.address);
|
|
130
|
+
t.ok(adminAddressBuffer.length > 0, 'admin address encoded as buffer');
|
|
131
|
+
const writerRegistry = await base.view.get(EntryType.WRITER_ADDRESS + writingKey.toString('hex'));
|
|
132
|
+
t.ok(writerRegistry, 'writer registry entry exists');
|
|
133
|
+
t.ok(
|
|
134
|
+
b4a.equals(writerRegistry.value, adminAddressBuffer),
|
|
135
|
+
'writer registry links writing key to admin'
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const writersLengthEntry = await base.view.get(EntryType.WRITERS_LENGTH);
|
|
139
|
+
t.ok(writersLengthEntry, 'writers length entry exists');
|
|
140
|
+
const writersLength = lengthEntryUtils.decodeBE(writersLengthEntry.value);
|
|
141
|
+
t.is(writersLength, 1, 'writers length increments to 1');
|
|
142
|
+
|
|
143
|
+
const writerIndexEntry = await base.view.get(`${EntryType.WRITERS_INDEX}0`);
|
|
144
|
+
t.ok(writerIndexEntry, 'writer index entry exists');
|
|
145
|
+
t.is(
|
|
146
|
+
writerIndexEntry.value.toString('ascii'),
|
|
147
|
+
adminAddressBuffer.toString('ascii'),
|
|
148
|
+
'writer index 0 stores admin address'
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const licenseCountEntry = await base.view.get(EntryType.LICENSE_COUNT);
|
|
152
|
+
t.ok(licenseCountEntry, 'license count entry exists');
|
|
153
|
+
const licenseCount = lengthEntryUtils.decodeBE(licenseCountEntry.value);
|
|
154
|
+
t.is(licenseCount, 1, 'license count increments to 1');
|
|
155
|
+
|
|
156
|
+
const licenseIndexEntry = await base.view.get(`${EntryType.LICENSE_INDEX}1`);
|
|
157
|
+
t.ok(licenseIndexEntry, 'license index entry exists');
|
|
158
|
+
t.is(
|
|
159
|
+
licenseIndexEntry.value.toString('ascii'),
|
|
160
|
+
adminAddressBuffer.toString('ascii'),
|
|
161
|
+
'license index 1 stores admin address'
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const initializationEntry = await base.view.get(EntryType.INITIALIZATION);
|
|
165
|
+
t.ok(initializationEntry, 'initialization entry exists');
|
|
166
|
+
t.ok(
|
|
167
|
+
b4a.equals(initializationEntry.value, safeWriteUInt32BE(1, 0)),
|
|
168
|
+
'initialization flag set to 1'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const decodedOperation = safeDecodeApplyOperation(payload);
|
|
172
|
+
t.ok(decodedOperation?.cao?.tx, 'operation decodes');
|
|
173
|
+
const txKey = decodedOperation.cao.tx.toString('hex');
|
|
174
|
+
const txEntry = await base.view.get(txKey);
|
|
175
|
+
t.ok(txEntry, 'operation hash stored to prevent replays');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function assertAdminStatePersists(t, context, payload) {
|
|
179
|
+
const adminNode = context.adminBootstrap;
|
|
180
|
+
const reader = context.peers[1];
|
|
181
|
+
|
|
182
|
+
await assertAdminState(t, adminNode.base, adminNode.wallet, adminNode.base.local.key, payload);
|
|
183
|
+
|
|
184
|
+
await eventFlush();
|
|
185
|
+
await context.sync();
|
|
186
|
+
|
|
187
|
+
await assertAdminState(t, reader.base, adminNode.wallet, adminNode.base.local.key, payload);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function bypassAddAdminReplayGuardsOnce(context) {
|
|
191
|
+
const adminNode = context.adminBootstrap;
|
|
192
|
+
if (!adminNode?.base) {
|
|
193
|
+
throw new Error('AddAdmin replay guard bypass requires an admin bootstrap node.');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const writerRegistryKey = EntryType.WRITER_ADDRESS + adminNode.base.local.key.toString('hex');
|
|
197
|
+
const keysToBypass = new Set([writerRegistryKey, EntryType.ADMIN]);
|
|
198
|
+
|
|
199
|
+
return patchViewGetOnce(adminNode.base, keysToBypass);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function bypassBalanceInitializationAdminConsistencyOnce(context) {
|
|
203
|
+
const adminNode = context.adminBootstrap;
|
|
204
|
+
if (!adminNode?.base) {
|
|
205
|
+
throw new Error('Admin consistency bypass requires an admin bootstrap node.');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const keysToBypass = new Set([EntryType.ADMIN]);
|
|
209
|
+
|
|
210
|
+
return patchViewGetOnce(adminNode.base, keysToBypass, {
|
|
211
|
+
mutateAdminEntry: async entryPromise => {
|
|
212
|
+
const entry = await entryPromise;
|
|
213
|
+
if (!entry?.value) return entry;
|
|
214
|
+
const mutated = b4a.from(entry.value);
|
|
215
|
+
mutated[mutated.length - 1] ^= 0xff;
|
|
216
|
+
return { ...entry, value: mutated };
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function patchViewGetOnce(base, keysToBypass, { mutateAdminEntry } = {}) {
|
|
222
|
+
const originalApply = base._handlers.apply;
|
|
223
|
+
let shouldPatchNextApply = true;
|
|
224
|
+
|
|
225
|
+
base._handlers.apply = async (nodes, view, baseCtx) => {
|
|
226
|
+
if (!shouldPatchNextApply) {
|
|
227
|
+
return originalApply(nodes, view, baseCtx);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
shouldPatchNextApply = false;
|
|
231
|
+
const previousBatch = view.batch;
|
|
232
|
+
const boundBatch = previousBatch.bind(view);
|
|
233
|
+
|
|
234
|
+
view.batch = function patchedBatch(...args) {
|
|
235
|
+
const batch = boundBatch(...args);
|
|
236
|
+
const originalGet = batch.get?.bind(batch);
|
|
237
|
+
if (typeof originalGet === 'function') {
|
|
238
|
+
batch.get = async key => {
|
|
239
|
+
const identifier = resolveKeyIdentifier(key);
|
|
240
|
+
if (keysToBypass.has(identifier)) {
|
|
241
|
+
if (identifier === EntryType.ADMIN && typeof mutateAdminEntry === 'function') {
|
|
242
|
+
return mutateAdminEntry(originalGet(key));
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
return originalGet(key);
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return batch;
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
return await originalApply(nodes, view, baseCtx);
|
|
254
|
+
} finally {
|
|
255
|
+
view.batch = previousBatch;
|
|
256
|
+
base._handlers.apply = originalApply;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return () => {
|
|
261
|
+
base._handlers.apply = originalApply;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function resolveKeyIdentifier(key) {
|
|
266
|
+
if (typeof key === 'string') {
|
|
267
|
+
return key;
|
|
268
|
+
}
|
|
269
|
+
if (b4a.isBuffer(key)) {
|
|
270
|
+
return key.toString('utf8');
|
|
271
|
+
}
|
|
272
|
+
return `${key}`;
|
|
273
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import b4a from 'b4a';
|
|
3
|
+
import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
4
|
+
import {
|
|
5
|
+
setupAddAdminScenario,
|
|
6
|
+
buildAddAdminRequesterPayload,
|
|
7
|
+
assertAddAdminRequesterFailureStateLocal
|
|
8
|
+
} from './addAdminScenarioHelpers.js';
|
|
9
|
+
import adminEntryUtils from '../../../../../src/core/state/utils/adminEntry.js';
|
|
10
|
+
|
|
11
|
+
export default function addAdminEntryEncodingFailureScenario() {
|
|
12
|
+
test('State.apply addAdmin aborts when admin entry encoding fails', async t => {
|
|
13
|
+
const context = await setupAddAdminScenario(t);
|
|
14
|
+
const adminNode = context.adminBootstrap;
|
|
15
|
+
const payload = await buildAddAdminRequesterPayload(context);
|
|
16
|
+
|
|
17
|
+
const originalEncode = adminEntryUtils.encode;
|
|
18
|
+
adminEntryUtils.encode = () => b4a.alloc(0);
|
|
19
|
+
|
|
20
|
+
t.teardown(() => {
|
|
21
|
+
adminEntryUtils.encode = originalEncode;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await adminNode.base.append(payload);
|
|
25
|
+
await adminNode.base.update();
|
|
26
|
+
await eventFlush();
|
|
27
|
+
|
|
28
|
+
await assertAddAdminRequesterFailureStateLocal(t, context);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
3
|
+
import { EntryType } from '../../../../../src/utils/constants.js';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
setupAddAdminScenario,
|
|
7
|
+
buildAddAdminRequesterPayload,
|
|
8
|
+
assertAdminStatePersists
|
|
9
|
+
} from './addAdminScenarioHelpers.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* This test simulates the bootstrap resubmitting a valid ADD_ADMIN operation after the admin
|
|
13
|
+
* was already created.
|
|
14
|
+
*
|
|
15
|
+
* In the real handler the `adminEntryExists` guard blocks that attempt, but earlier validation
|
|
16
|
+
* (`writer key already exists`) fires first. To reach the admin-entry branch we temporarily patch
|
|
17
|
+
* the Hyperbee view used by State.apply and return `null` only for the `EntryType.WRITER_ADDRESS + iw`
|
|
18
|
+
* lookup. That allows the writer-key check to pass and execution continues until the admin-entry check.
|
|
19
|
+
*
|
|
20
|
+
* After the duplicate operation is rejected, `assertAdminStatePersists` confirms that the admin entry
|
|
21
|
+
* and all related registries remain unchanged on both the bootstrap and the reader node.
|
|
22
|
+
*/
|
|
23
|
+
export default function addAdminEntryExistsScenario() {
|
|
24
|
+
test('State.apply addAdmin rejects attempts when admin already exists', async t => {
|
|
25
|
+
const networkContext = await setupAddAdminScenario(t);
|
|
26
|
+
const adminNode = networkContext.adminBootstrap;
|
|
27
|
+
|
|
28
|
+
const initialPayload = await buildAddAdminRequesterPayload(networkContext);
|
|
29
|
+
await adminNode.base.append(initialPayload);
|
|
30
|
+
await adminNode.base.update();
|
|
31
|
+
await eventFlush();
|
|
32
|
+
|
|
33
|
+
const writerRegistryKey = EntryType.WRITER_ADDRESS + adminNode.base.local.key.toString('hex');
|
|
34
|
+
const originalApplyHandler = adminNode.base._handlers.apply;
|
|
35
|
+
let shouldPatchNextApply = true;
|
|
36
|
+
|
|
37
|
+
adminNode.base._handlers.apply = async (nodes, view, baseCtx) => {
|
|
38
|
+
if (!shouldPatchNextApply) {
|
|
39
|
+
return originalApplyHandler(nodes, view, baseCtx);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
shouldPatchNextApply = false;
|
|
43
|
+
const previousBatch = view.batch;
|
|
44
|
+
const boundBatch = previousBatch.bind(view);
|
|
45
|
+
|
|
46
|
+
view.batch = function patchedBatch(...args) {
|
|
47
|
+
const batch = boundBatch(...args);
|
|
48
|
+
const originalGet = batch.get?.bind(batch);
|
|
49
|
+
if (typeof originalGet === 'function') {
|
|
50
|
+
batch.get = async key => {
|
|
51
|
+
if (key === writerRegistryKey) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return originalGet(key);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return batch;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return await originalApplyHandler(nodes, view, baseCtx);
|
|
62
|
+
} finally {
|
|
63
|
+
view.batch = previousBatch;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
t.teardown(() => {
|
|
68
|
+
adminNode.base._handlers.apply = originalApplyHandler;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const duplicatePayload = await buildAddAdminRequesterPayload(networkContext);
|
|
72
|
+
await adminNode.base.append(duplicatePayload);
|
|
73
|
+
await adminNode.base.update();
|
|
74
|
+
await eventFlush();
|
|
75
|
+
|
|
76
|
+
await assertAdminStatePersists(t, networkContext, initialPayload);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import b4a from 'b4a';
|
|
3
|
+
import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
4
|
+
import {
|
|
5
|
+
setupAddAdminScenario,
|
|
6
|
+
buildAddAdminRequesterPayload,
|
|
7
|
+
assertAddAdminRequesterFailureStateLocal
|
|
8
|
+
} from './addAdminScenarioHelpers.js';
|
|
9
|
+
import nodeEntryUtils from '../../../../../src/core/state/utils/nodeEntry.js';
|
|
10
|
+
|
|
11
|
+
export default function addAdminNodeEntryInitializationFailureScenario() {
|
|
12
|
+
test('State.apply addAdmin aborts when node entry initialization fails', async t => {
|
|
13
|
+
const context = await setupAddAdminScenario(t);
|
|
14
|
+
const adminNode = context.adminBootstrap;
|
|
15
|
+
const payload = await buildAddAdminRequesterPayload(context);
|
|
16
|
+
|
|
17
|
+
const originalInit = nodeEntryUtils.init;
|
|
18
|
+
nodeEntryUtils.init = () => b4a.alloc(0);
|
|
19
|
+
|
|
20
|
+
t.teardown(() => {
|
|
21
|
+
nodeEntryUtils.init = originalInit;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
await adminNode.base.append(payload);
|
|
25
|
+
await adminNode.base.update();
|
|
26
|
+
await eventFlush();
|
|
27
|
+
|
|
28
|
+
await assertAddAdminRequesterFailureStateLocal(t, context);
|
|
29
|
+
});
|
|
30
|
+
}
|