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,68 @@
|
|
|
1
|
+
import { test } from 'brittle';
|
|
2
|
+
import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
setupAddAdminScenario,
|
|
6
|
+
buildAddAdminRequesterPayload,
|
|
7
|
+
assertAddAdminRequesterFailureStateLocal
|
|
8
|
+
} from './addAdminScenarioHelpers.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Verifies that ADD_ADMIN rejects payloads appended by any node other than the bootstrap writer.
|
|
12
|
+
*
|
|
13
|
+
* Only the bootstrap node has permission to run this operation. During the test we let the bootstrap
|
|
14
|
+
* append a valid payload, but intercept the ensuing `State.apply` call and mutate `node.from.key`
|
|
15
|
+
* so that the handler thinks the operation was produced by a reader node. That should trigger the
|
|
16
|
+
* "Node is not a bootstrap node." guard, and the state must remain untouched.
|
|
17
|
+
*/
|
|
18
|
+
export default function addAdminNonBootstrapNodeScenario() {
|
|
19
|
+
test('State.apply addAdmin rejects non-bootstrap requesters', async t => {
|
|
20
|
+
const networkContext = await setupAddAdminScenario(t);
|
|
21
|
+
const adminNode = networkContext.adminBootstrap;
|
|
22
|
+
const readerNode = networkContext.peers[1];
|
|
23
|
+
|
|
24
|
+
const originalApply = adminNode.base._handlers.apply;
|
|
25
|
+
let shouldMutateNextCall = true;
|
|
26
|
+
|
|
27
|
+
adminNode.base._handlers.apply = async (nodes, view, baseCtx) => {
|
|
28
|
+
if (!shouldMutateNextCall) {
|
|
29
|
+
return originalApply(nodes, view, baseCtx);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
shouldMutateNextCall = false;
|
|
33
|
+
adminNode.base._handlers.apply = originalApply;
|
|
34
|
+
|
|
35
|
+
const readerKey = readerNode.base.local.key;
|
|
36
|
+
const mutatedNodes = nodes.map(node => {
|
|
37
|
+
if (!node?.from) return node;
|
|
38
|
+
return {
|
|
39
|
+
...node,
|
|
40
|
+
from: new Proxy(node.from, {
|
|
41
|
+
// We only need to fake node.from.key. A Proxy lets us override just that property
|
|
42
|
+
// while Reflect delegates every other access to the original object, so the node
|
|
43
|
+
// shape Autobase expects stays untouched.
|
|
44
|
+
get(target, prop, receiver) {
|
|
45
|
+
if (prop === 'key') {
|
|
46
|
+
return readerKey;
|
|
47
|
+
}
|
|
48
|
+
return Reflect.get(target, prop, receiver);
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return originalApply(mutatedNodes, view, baseCtx);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
t.teardown(() => {
|
|
58
|
+
adminNode.base._handlers.apply = originalApply;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const payload = await buildAddAdminRequesterPayload(networkContext);
|
|
62
|
+
await adminNode.base.append(payload);
|
|
63
|
+
await adminNode.base.update();
|
|
64
|
+
await eventFlush();
|
|
65
|
+
|
|
66
|
+
await assertAddAdminRequesterFailureStateLocal(t, networkContext);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
setupAddAdminScenario,
|
|
3
|
+
buildAddAdminRequesterPayload,
|
|
4
|
+
assertAddAdminRequesterFailureState,
|
|
5
|
+
assertAddAdminRequesterFailureStateLocal,
|
|
6
|
+
mutateAddAdminPayloadForInvalidSchema,
|
|
7
|
+
assertAdminStatePersists,
|
|
8
|
+
bypassAddAdminReplayGuardsOnce
|
|
9
|
+
} from './addAdminScenarioHelpers.js';
|
|
10
|
+
import addAdminHappyPathScenario from './addAdminHappyPathScenario.js';
|
|
11
|
+
import RequesterAddressValidationScenario from '../common/requesterAddressValidationScenario.js';
|
|
12
|
+
import createRequesterPublicKeyValidationScenario from '../common/requesterPublicKeyValidationScenario.js';
|
|
13
|
+
import InvalidHashValidationScenario from '../common/payload-structure/invalidHashValidationScenario.js';
|
|
14
|
+
import InvalidSignatureValidationScenario, { SignatureMutationStrategy } from '../common/payload-structure/invalidSignatureValidationScenario.js';
|
|
15
|
+
import InvalidMessageComponentValidationScenario, { MessageComponentStrategy } from '../common/invalidMessageComponentValidationScenario.js';
|
|
16
|
+
import InvalidPayloadValidationScenario from '../common/payload-structure/invalidPayloadValidationScenario.js';
|
|
17
|
+
import WriterKeyExistsValidationScenario from '../common/writerKeyExistsValidationScenario.js';
|
|
18
|
+
import OperationAlreadyAppliedScenario from '../common/operationAlreadyAppliedScenario.js';
|
|
19
|
+
import TransactionValidityMismatchScenario from '../common/transactionValidityMismatchScenario.js';
|
|
20
|
+
import IndexerSequenceStateInvalidScenario from '../common/indexer/indexerSequenceStateInvalidScenario.js';
|
|
21
|
+
import CompleteStateMessageOperations from '../../../../../src/messages/completeStateMessages/CompleteStateMessageOperations.js';
|
|
22
|
+
import addAdminEntryExistsScenario from './adminEntryExistsScenario.js';
|
|
23
|
+
import addAdminNonBootstrapNodeScenario from './nonBootstrapNodeScenario.js';
|
|
24
|
+
import addAdminNodeEntryInitializationFailureScenario from './nodeEntryInitializationFailureScenario.js';
|
|
25
|
+
import addAdminEntryEncodingFailureScenario from './adminEntryEncodingFailureScenario.js';
|
|
26
|
+
|
|
27
|
+
// happy path
|
|
28
|
+
addAdminHappyPathScenario();
|
|
29
|
+
|
|
30
|
+
addAdminEntryExistsScenario();
|
|
31
|
+
|
|
32
|
+
addAdminNodeEntryInitializationFailureScenario();
|
|
33
|
+
addAdminEntryEncodingFailureScenario();
|
|
34
|
+
|
|
35
|
+
// common invalid scenarios
|
|
36
|
+
new InvalidPayloadValidationScenario({
|
|
37
|
+
title: 'State.apply addAdmin rejects payloads that fail schema validation',
|
|
38
|
+
setupScenario: setupAddAdminScenario,
|
|
39
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
40
|
+
mutatePayload: mutateAddAdminPayloadForInvalidSchema,
|
|
41
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
42
|
+
expectedLogs: ['Contract schema validation failed.']
|
|
43
|
+
}).performScenario();
|
|
44
|
+
|
|
45
|
+
new RequesterAddressValidationScenario({
|
|
46
|
+
title: 'State.apply addAdmin requester address is invalid',
|
|
47
|
+
setupScenario: setupAddAdminScenario,
|
|
48
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
49
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
50
|
+
expectedLogs: ['Requester address is invalid.']
|
|
51
|
+
}).performScenario();
|
|
52
|
+
|
|
53
|
+
createRequesterPublicKeyValidationScenario({
|
|
54
|
+
title: 'State.apply addAdmin requester public key is invalid',
|
|
55
|
+
setupScenario: setupAddAdminScenario,
|
|
56
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
57
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
58
|
+
expectedLogs: ['Error while decoding requester public key.']
|
|
59
|
+
}).performScenario();
|
|
60
|
+
|
|
61
|
+
addAdminNonBootstrapNodeScenario();
|
|
62
|
+
|
|
63
|
+
new InvalidHashValidationScenario({
|
|
64
|
+
title: 'State.apply addAdmin requester message hash mismatch',
|
|
65
|
+
setupScenario: setupAddAdminScenario,
|
|
66
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
67
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
68
|
+
expectedLogs: ['Message hash does not match the tx_hash.']
|
|
69
|
+
}).performScenario();
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
new InvalidMessageComponentValidationScenario({
|
|
73
|
+
title: 'State.apply addAdmin transaction validity mismatch',
|
|
74
|
+
setupScenario: setupAddAdminScenario,
|
|
75
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
76
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
77
|
+
strategy: MessageComponentStrategy.TX_VALIDITY,
|
|
78
|
+
expectedLogs: ['Message hash does not match the tx_hash.']
|
|
79
|
+
}).performScenario();
|
|
80
|
+
|
|
81
|
+
new InvalidMessageComponentValidationScenario({
|
|
82
|
+
title: 'State.apply addAdmin requester nonce mismatch',
|
|
83
|
+
setupScenario: setupAddAdminScenario,
|
|
84
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
85
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
86
|
+
strategy: MessageComponentStrategy.NONCE,
|
|
87
|
+
expectedLogs: ['Message hash does not match the tx_hash.']
|
|
88
|
+
}).performScenario();
|
|
89
|
+
|
|
90
|
+
new InvalidSignatureValidationScenario({
|
|
91
|
+
title: 'State.apply addAdmin requester signature is invalid (foreign signature)',
|
|
92
|
+
setupScenario: setupAddAdminScenario,
|
|
93
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
94
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
95
|
+
expectedLogs: ['Failed to verify message signature.']
|
|
96
|
+
}).performScenario();
|
|
97
|
+
|
|
98
|
+
new InvalidSignatureValidationScenario({
|
|
99
|
+
title: 'State.apply addAdmin requester signature is invalid (zero fill)',
|
|
100
|
+
setupScenario: setupAddAdminScenario,
|
|
101
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
102
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
103
|
+
strategy: SignatureMutationStrategy.ZERO_FILL,
|
|
104
|
+
expectedLogs: ['Failed to verify message signature.']
|
|
105
|
+
}).performScenario();
|
|
106
|
+
|
|
107
|
+
new InvalidSignatureValidationScenario({
|
|
108
|
+
title: 'State.apply addAdmin requester signature is invalid (type mismatch)',
|
|
109
|
+
setupScenario: setupAddAdminScenario,
|
|
110
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
111
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
112
|
+
strategy: SignatureMutationStrategy.TYPE_MISMATCH,
|
|
113
|
+
expectedLogs: ['Failed to verify message signature.']
|
|
114
|
+
}).performScenario();
|
|
115
|
+
|
|
116
|
+
new IndexerSequenceStateInvalidScenario({
|
|
117
|
+
title: 'State.apply addAdmin rejects payload when indexer sequence state is invalid',
|
|
118
|
+
setupScenario: setupAddAdminScenario,
|
|
119
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
120
|
+
assertStateUnchanged: (t, context) => assertAddAdminRequesterFailureStateLocal(t, context),
|
|
121
|
+
expectedLogs: ['Indexer sequence state is invalid.']
|
|
122
|
+
}).performScenario();
|
|
123
|
+
|
|
124
|
+
new TransactionValidityMismatchScenario({
|
|
125
|
+
title: 'State.apply addAdmin rejects payload when cao.txv does not match indexer state',
|
|
126
|
+
setupScenario: setupAddAdminScenario,
|
|
127
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
128
|
+
assertStateUnchanged: assertAddAdminRequesterFailureState,
|
|
129
|
+
rebuildPayloadWithTxValidity: ({ context, mutatedTxValidity }) => {
|
|
130
|
+
const adminNode = context.adminBootstrap;
|
|
131
|
+
return CompleteStateMessageOperations.assembleAddAdminMessage(
|
|
132
|
+
adminNode.wallet,
|
|
133
|
+
adminNode.base.local.key,
|
|
134
|
+
mutatedTxValidity
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
expectedLogs: ['Transaction was not executed.']
|
|
138
|
+
}).performScenario();
|
|
139
|
+
|
|
140
|
+
new WriterKeyExistsValidationScenario({
|
|
141
|
+
title: 'State.apply addAdmin writer key already exists',
|
|
142
|
+
setupScenario: setupAddAdminScenario,
|
|
143
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
144
|
+
assertStateUnchanged: assertAdminStatePersists,
|
|
145
|
+
expectedLogs: ['Writer key already exists.']
|
|
146
|
+
}).performScenario();
|
|
147
|
+
|
|
148
|
+
new OperationAlreadyAppliedScenario({
|
|
149
|
+
title: 'State.apply addAdmin rejects duplicate operations',
|
|
150
|
+
setupScenario: setupAddAdminScenario,
|
|
151
|
+
buildValidPayload: buildAddAdminRequesterPayload,
|
|
152
|
+
assertStateUnchanged: assertAdminStatePersists,
|
|
153
|
+
beforeInvalidApply: ({ context }) => bypassAddAdminReplayGuardsOnce(context),
|
|
154
|
+
expectedLogs: ['Operation has already been applied.']
|
|
155
|
+
}).performScenario();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import { test } from 'brittle';
|
|
3
|
+
import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
4
|
+
import {
|
|
5
|
+
setupAddIndexerScenario,
|
|
6
|
+
selectIndexerCandidatePeer,
|
|
7
|
+
buildAddIndexerPayload,
|
|
8
|
+
assertAddIndexerSuccessState
|
|
9
|
+
} from './addIndexerScenarioHelpers.js';
|
|
10
|
+
|
|
11
|
+
export default function addIndexerHappyPathScenario() {
|
|
12
|
+
test('State.apply addIndexer promotes active writer to indexer (happy path)', async t => {
|
|
13
|
+
const context = await setupAddIndexerScenario(t);
|
|
14
|
+
const adminPeer = context.adminBootstrap;
|
|
15
|
+
const writerPeer = selectIndexerCandidatePeer(context);
|
|
16
|
+
|
|
17
|
+
const writerEntryBefore = await adminPeer.base.view.get(writerPeer.wallet.address);
|
|
18
|
+
t.ok(writerEntryBefore, 'writer entry exists before addIndexer');
|
|
19
|
+
const adminEntryBefore = await adminPeer.base.view.get(adminPeer.wallet.address);
|
|
20
|
+
t.ok(adminEntryBefore, 'admin entry exists before addIndexer');
|
|
21
|
+
|
|
22
|
+
const payload = await buildAddIndexerPayload(context, {
|
|
23
|
+
writerPeer,
|
|
24
|
+
adminPeer
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await adminPeer.base.append(payload);
|
|
28
|
+
await adminPeer.base.update();
|
|
29
|
+
await eventFlush();
|
|
30
|
+
|
|
31
|
+
await assertAddIndexerSuccessState(t, context, {
|
|
32
|
+
writerPeer,
|
|
33
|
+
adminPeer,
|
|
34
|
+
writerEntryBefore: { value: b4a.from(writerEntryBefore.value) },
|
|
35
|
+
adminEntryBefore: { value: b4a.from(adminEntryBefore.value) },
|
|
36
|
+
payload
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import { test } from 'brittle';
|
|
3
|
+
import nodeEntryUtils from '../../../../../src/core/state/utils/nodeEntry.js';
|
|
4
|
+
import {
|
|
5
|
+
setupAddIndexerScenario,
|
|
6
|
+
selectIndexerCandidatePeer,
|
|
7
|
+
buildAddIndexerPayload,
|
|
8
|
+
assertAddIndexerSuccessState
|
|
9
|
+
} from './addIndexerScenarioHelpers.js';
|
|
10
|
+
import { promotePeerToWriter } from '../addWriter/addWriterScenarioHelpers.js';
|
|
11
|
+
import { initializeBalances, whitelistAddress } from '../common/commonScenarioHelper.js';
|
|
12
|
+
import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
13
|
+
import lengthEntryUtils from '../../../../../src/core/state/utils/lengthEntry.js';
|
|
14
|
+
import { EntryType } from '../../../../../src/utils/constants.js';
|
|
15
|
+
|
|
16
|
+
export default function addIndexerMultipleIndexersInTheNetworkScenario() {
|
|
17
|
+
test('State.apply addIndexer adds multiple indexers sequentially', async t => {
|
|
18
|
+
const context = await setupAddIndexerScenario(t, { nodes: 4 });
|
|
19
|
+
const adminPeer = context.adminBootstrap;
|
|
20
|
+
const initialIndexerCount = getIndexerCount(adminPeer.base);
|
|
21
|
+
t.comment(`Network peers: ${context.peers.length}`);
|
|
22
|
+
|
|
23
|
+
const candidates = [
|
|
24
|
+
selectIndexerCandidatePeer(context, 0),
|
|
25
|
+
selectIndexerCandidatePeer(context, 1),
|
|
26
|
+
selectIndexerCandidatePeer(context, 2)
|
|
27
|
+
];
|
|
28
|
+
t.comment(
|
|
29
|
+
`Indexer candidates: ${candidates.map(peer => peer.wallet.address).join(', ')}`
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
for (const peer of candidates.slice(1)) {
|
|
33
|
+
await prepareWriterCandidate(t, context, peer);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const trackedIndexers = [];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
t.comment(`Promoting writer ${candidate.wallet.address} to indexer...`);
|
|
39
|
+
await promoteWriterToIndexer(t, context, candidate, trackedIndexers);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await context.sync();
|
|
43
|
+
await assertNetworkSeesIndexers(
|
|
44
|
+
t,
|
|
45
|
+
context,
|
|
46
|
+
trackedIndexers,
|
|
47
|
+
initialIndexerCount + trackedIndexers.length
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function prepareWriterCandidate(t, context, peer) {
|
|
53
|
+
const funding = context.addWriterScenario?.writerInitialBalance;
|
|
54
|
+
if (!funding) {
|
|
55
|
+
throw new Error('addIndexer scenarios require writerInitialBalance buffer.');
|
|
56
|
+
}
|
|
57
|
+
const expectedWriterIndex = await deriveNextWriterIndex(context.adminBootstrap.base);
|
|
58
|
+
await initializeBalances(context, [[peer.wallet.address, funding]]);
|
|
59
|
+
await whitelistAddress(context, peer.wallet.address);
|
|
60
|
+
await promotePeerToWriter(t, context, { readerPeer: peer, expectedWriterIndex });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function promoteWriterToIndexer(t, context, writerPeer, trackedIndexers) {
|
|
64
|
+
const adminPeer = context.adminBootstrap;
|
|
65
|
+
const writerAddress = writerPeer.wallet.address;
|
|
66
|
+
|
|
67
|
+
const writerEntryBefore = await adminPeer.base.view.get(writerAddress);
|
|
68
|
+
t.ok(writerEntryBefore, 'writer entry exists before addIndexer');
|
|
69
|
+
const adminEntryBefore = await adminPeer.base.view.get(adminPeer.wallet.address);
|
|
70
|
+
t.ok(adminEntryBefore, 'admin entry exists before addIndexer');
|
|
71
|
+
|
|
72
|
+
const payload = await buildAddIndexerPayload(context, {
|
|
73
|
+
writerPeer,
|
|
74
|
+
adminPeer
|
|
75
|
+
});
|
|
76
|
+
t.comment(`Built addIndexer payload for ${writerAddress}`);
|
|
77
|
+
|
|
78
|
+
await adminPeer.base.append(payload);
|
|
79
|
+
await adminPeer.base.update();
|
|
80
|
+
await eventFlush();
|
|
81
|
+
t.comment(`Applied addIndexer payload for ${writerAddress}`);
|
|
82
|
+
|
|
83
|
+
await assertAddIndexerSuccessState(t, context, {
|
|
84
|
+
writerPeer,
|
|
85
|
+
adminPeer,
|
|
86
|
+
writerEntryBefore: { value: b4a.from(writerEntryBefore.value) },
|
|
87
|
+
adminEntryBefore: { value: b4a.from(adminEntryBefore.value) },
|
|
88
|
+
payload,
|
|
89
|
+
skipSync: true
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const decodedBefore = nodeEntryUtils.decode(writerEntryBefore.value);
|
|
93
|
+
if (!decodedBefore) {
|
|
94
|
+
throw new Error('Failed to decode writer entry before addIndexer.');
|
|
95
|
+
}
|
|
96
|
+
trackedIndexers.push({
|
|
97
|
+
address: writerAddress,
|
|
98
|
+
writingKey: b4a.from(writerPeer.base.local.key),
|
|
99
|
+
balance: b4a.from(decodedBefore.balance),
|
|
100
|
+
stakedBalance: b4a.from(decodedBefore.stakedBalance),
|
|
101
|
+
license: b4a.from(decodedBefore.license)
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
t.comment(`Completed promotion for ${writerAddress}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function assertNetworkSeesIndexers(t, context, trackedIndexers, expectedIndexerCount) {
|
|
108
|
+
for (const node of context.peers) {
|
|
109
|
+
await assertPeerObservesIndexers(t, node, trackedIndexers, expectedIndexerCount);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function assertPeerObservesIndexers(t, node, trackedIndexers, expectedIndexerCount) {
|
|
114
|
+
const base = node.base;
|
|
115
|
+
const membership = Object.values(base.system.indexers ?? {});
|
|
116
|
+
t.is(
|
|
117
|
+
membership.length,
|
|
118
|
+
expectedIndexerCount,
|
|
119
|
+
`${node.name} observes expected indexer count`
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
for (const indexer of trackedIndexers) {
|
|
123
|
+
const entry = await base.view.get(indexer.address);
|
|
124
|
+
t.ok(entry, `${node.name} sees indexer entry for ${indexer.address}`);
|
|
125
|
+
const decoded = nodeEntryUtils.decode(entry.value);
|
|
126
|
+
t.ok(decoded, `${node.name} decodes indexer entry for ${indexer.address}`);
|
|
127
|
+
if (!decoded) continue;
|
|
128
|
+
|
|
129
|
+
t.is(decoded.isWhitelisted, true, `${node.name} keeps ${indexer.address} whitelisted`);
|
|
130
|
+
t.is(decoded.isWriter, true, `${node.name} keeps ${indexer.address} as writer`);
|
|
131
|
+
t.is(decoded.isIndexer, true, `${node.name} marks ${indexer.address} as indexer`);
|
|
132
|
+
t.ok(
|
|
133
|
+
b4a.equals(decoded.wk, indexer.writingKey),
|
|
134
|
+
`${node.name} sees unchanged writing key for ${indexer.address}`
|
|
135
|
+
);
|
|
136
|
+
t.ok(
|
|
137
|
+
b4a.equals(decoded.balance, indexer.balance),
|
|
138
|
+
`${node.name} sees unchanged balance for ${indexer.address}`
|
|
139
|
+
);
|
|
140
|
+
t.ok(
|
|
141
|
+
b4a.equals(decoded.stakedBalance, indexer.stakedBalance),
|
|
142
|
+
`${node.name} sees unchanged staked balance for ${indexer.address}`
|
|
143
|
+
);
|
|
144
|
+
t.ok(
|
|
145
|
+
b4a.equals(decoded.license, indexer.license),
|
|
146
|
+
`${node.name} sees unchanged license for ${indexer.address}`
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const hasWritingKey = membership.some(entry => entry?.key && b4a.equals(entry.key, indexer.writingKey));
|
|
150
|
+
t.ok(
|
|
151
|
+
hasWritingKey,
|
|
152
|
+
`${node.name} records ${indexer.address} in validator set`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getIndexerCount(base) {
|
|
158
|
+
return Object.values(base.system.indexers ?? {}).length;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function deriveNextWriterIndex(base) {
|
|
162
|
+
const entry = await base.view.get(EntryType.WRITERS_LENGTH);
|
|
163
|
+
if (!entry?.value) {
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
return lengthEntryUtils.decodeBE(entry.value);
|
|
167
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import OperationValidationScenarioBase from '../common/base/OperationValidationScenarioBase.js';
|
|
2
|
+
import nodeRoleUtils from '../../../../../src/core/state/utils/roles.js';
|
|
3
|
+
import {
|
|
4
|
+
setupAddIndexerScenario,
|
|
5
|
+
buildAddIndexerPayload,
|
|
6
|
+
assertAddIndexerFailureState,
|
|
7
|
+
applyWithPretenderRoleMutation
|
|
8
|
+
} from './addIndexerScenarioHelpers.js';
|
|
9
|
+
|
|
10
|
+
export default function addIndexerPretenderAlreadyIndexerScenario() {
|
|
11
|
+
new OperationValidationScenarioBase({
|
|
12
|
+
title: 'State.apply addIndexer rejects payloads when target is already an indexer',
|
|
13
|
+
setupScenario: setupAddIndexerScenario,
|
|
14
|
+
buildValidPayload: context => buildAddIndexerPayload(context),
|
|
15
|
+
mutatePayload: (_t, payload) => payload,
|
|
16
|
+
applyInvalidPayload: (context, invalidPayload) =>
|
|
17
|
+
applyWithPretenderRoleMutation(context, invalidPayload, nodeRoleUtils.NodeRole.INDEXER),
|
|
18
|
+
assertStateUnchanged: (t, context) => assertAddIndexerFailureState(t, context, { skipSync: true }),
|
|
19
|
+
expectedLogs: ['Node must be a writer, and cannot already be an indexer.']
|
|
20
|
+
}).performScenario();
|
|
21
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import OperationValidationScenarioBase from '../common/base/OperationValidationScenarioBase.js';
|
|
2
|
+
import nodeRoleUtils from '../../../../../src/core/state/utils/roles.js';
|
|
3
|
+
import {
|
|
4
|
+
setupAddIndexerScenario,
|
|
5
|
+
buildAddIndexerPayload,
|
|
6
|
+
assertAddIndexerFailureState,
|
|
7
|
+
applyWithPretenderRoleMutation
|
|
8
|
+
} from './addIndexerScenarioHelpers.js';
|
|
9
|
+
|
|
10
|
+
export default function addIndexerPretenderNotWriterScenario() {
|
|
11
|
+
new OperationValidationScenarioBase({
|
|
12
|
+
title: 'State.apply addIndexer rejects payloads when target is not a writer',
|
|
13
|
+
setupScenario: setupAddIndexerScenario,
|
|
14
|
+
buildValidPayload: context => buildAddIndexerPayload(context),
|
|
15
|
+
mutatePayload: (_t, payload) => payload,
|
|
16
|
+
applyInvalidPayload: (context, invalidPayload) =>
|
|
17
|
+
applyWithPretenderRoleMutation(context, invalidPayload, nodeRoleUtils.NodeRole.READER),
|
|
18
|
+
assertStateUnchanged: (t, context) => assertAddIndexerFailureState(t, context, { skipSync: true }),
|
|
19
|
+
expectedLogs: ['Node must be a writer, and cannot already be an indexer.']
|
|
20
|
+
}).performScenario();
|
|
21
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import { test } from 'brittle';
|
|
3
|
+
import nodeEntryUtils from '../../../../../src/core/state/utils/nodeEntry.js';
|
|
4
|
+
import { EntryType } from '../../../../../src/utils/constants.js';
|
|
5
|
+
import {
|
|
6
|
+
setupAddIndexerScenario,
|
|
7
|
+
selectIndexerCandidatePeer,
|
|
8
|
+
buildAddIndexerPayload,
|
|
9
|
+
buildRemoveIndexerPayload,
|
|
10
|
+
assertAddIndexerSuccessState,
|
|
11
|
+
ensureIndexerRegistration
|
|
12
|
+
} from './addIndexerScenarioHelpers.js';
|
|
13
|
+
import { eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
14
|
+
import { toBalance } from '../../../../../src/core/state/utils/balance.js';
|
|
15
|
+
import transactionUtils from '../../../../../src/core/state/utils/transaction.js';
|
|
16
|
+
import { safeDecodeApplyOperation } from '../../../../../src/utils/protobuf/operationHelpers.js';
|
|
17
|
+
|
|
18
|
+
export default function addIndexerRemoveAndReAddScenario() {
|
|
19
|
+
test('State.apply addIndexer re-promotes writer after removeIndexer cycle', async t => {
|
|
20
|
+
const context = await setupAddIndexerScenario(t, { nodes: 4 });
|
|
21
|
+
const adminPeer = context.adminBootstrap;
|
|
22
|
+
const writerPeer = selectIndexerCandidatePeer(context);
|
|
23
|
+
|
|
24
|
+
// Initial promotion to indexer
|
|
25
|
+
const writerEntryBefore = await adminPeer.base.view.get(writerPeer.wallet.address);
|
|
26
|
+
const initialAdminEntry = await adminPeer.base.view.get(adminPeer.wallet.address);
|
|
27
|
+
const initialAdminBalance = readBalance(initialAdminEntry);
|
|
28
|
+
|
|
29
|
+
const firstAddPayload = await buildAddIndexerPayload(context, { writerPeer, adminPeer });
|
|
30
|
+
await adminPeer.base.append(firstAddPayload);
|
|
31
|
+
await adminPeer.base.update();
|
|
32
|
+
await eventFlush();
|
|
33
|
+
|
|
34
|
+
await assertAddIndexerSuccessState(t, context, {
|
|
35
|
+
writerPeer,
|
|
36
|
+
adminPeer,
|
|
37
|
+
writerEntryBefore: { value: b4a.from(writerEntryBefore.value) },
|
|
38
|
+
adminEntryBefore: { value: b4a.from(initialAdminEntry.value) },
|
|
39
|
+
payload: firstAddPayload,
|
|
40
|
+
skipSync: true
|
|
41
|
+
});
|
|
42
|
+
const adminBalanceAfterInitialAdd = await readAdminBalance(adminPeer);
|
|
43
|
+
t.ok(
|
|
44
|
+
adminBalanceAfterInitialAdd && initialAdminBalance,
|
|
45
|
+
'admin balance decodes after initial add'
|
|
46
|
+
);
|
|
47
|
+
ensureIndexerRegistration(adminPeer.base, writerPeer.base.local.key);
|
|
48
|
+
t.is(
|
|
49
|
+
readIndexerMembership(adminPeer.base, writerPeer.base.local.key),
|
|
50
|
+
true,
|
|
51
|
+
'writer key registered before removeIndexer'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Remove indexer
|
|
55
|
+
const writersLengthBeforeRemove = await readWritersLength(adminPeer);
|
|
56
|
+
|
|
57
|
+
const removePayload = await buildRemoveIndexerPayload(context, {
|
|
58
|
+
indexerPeer: writerPeer,
|
|
59
|
+
adminPeer
|
|
60
|
+
});
|
|
61
|
+
await adminPeer.base.append(removePayload);
|
|
62
|
+
await adminPeer.base.update();
|
|
63
|
+
await eventFlush();
|
|
64
|
+
|
|
65
|
+
await assertWriterDemotedToWriter(t, adminPeer.base, writerPeer);
|
|
66
|
+
|
|
67
|
+
const adminBalanceAfterRemove = await readAdminBalance(adminPeer);
|
|
68
|
+
assertFeeDeducted(
|
|
69
|
+
t,
|
|
70
|
+
adminBalanceAfterInitialAdd,
|
|
71
|
+
adminBalanceAfterRemove,
|
|
72
|
+
'admin balance reduced by removeIndexer fee'
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const writersLengthAfterRemove = await readWritersLength(adminPeer);
|
|
76
|
+
t.is(
|
|
77
|
+
writersLengthAfterRemove,
|
|
78
|
+
writersLengthBeforeRemove + 1,
|
|
79
|
+
'writers length increments after removeIndexer'
|
|
80
|
+
);
|
|
81
|
+
t.is(
|
|
82
|
+
readIndexerMembership(adminPeer.base, writerPeer.base.local.key),
|
|
83
|
+
false,
|
|
84
|
+
'writer key removed from validator set after removeIndexer'
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Re-add as indexer
|
|
88
|
+
const reAddPayload = await buildAddIndexerPayload(context, { writerPeer, adminPeer });
|
|
89
|
+
await adminPeer.base.append(reAddPayload);
|
|
90
|
+
await adminPeer.base.update();
|
|
91
|
+
await eventFlush();
|
|
92
|
+
|
|
93
|
+
const adminBalanceAfterReAdd = await readAdminBalance(adminPeer);
|
|
94
|
+
assertFeeDeducted(
|
|
95
|
+
t,
|
|
96
|
+
adminBalanceAfterRemove,
|
|
97
|
+
adminBalanceAfterReAdd,
|
|
98
|
+
'admin balance reduced by re-add fee'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const writersLengthAfterReAdd = await readWritersLength(adminPeer);
|
|
102
|
+
t.is(
|
|
103
|
+
writersLengthAfterReAdd,
|
|
104
|
+
writersLengthAfterRemove,
|
|
105
|
+
'writers length remains stable after re-adding indexer'
|
|
106
|
+
);
|
|
107
|
+
ensureIndexerRegistration(adminPeer.base, writerPeer.base.local.key);
|
|
108
|
+
t.is(
|
|
109
|
+
readIndexerMembership(adminPeer.base, writerPeer.base.local.key),
|
|
110
|
+
true,
|
|
111
|
+
'writer key restored in validator set after re-add'
|
|
112
|
+
);
|
|
113
|
+
await assertIndexerEntry(t, adminPeer.base, writerPeer);
|
|
114
|
+
await assertReplayRecorded(t, adminPeer.base, reAddPayload);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function assertWriterDemotedToWriter(t, base, writerPeer) {
|
|
119
|
+
const entry = await base.view.get(writerPeer.wallet.address);
|
|
120
|
+
t.ok(entry, 'writer entry exists after removeIndexer');
|
|
121
|
+
const decoded = nodeEntryUtils.decode(entry?.value);
|
|
122
|
+
t.ok(decoded, 'writer entry decodes after removeIndexer');
|
|
123
|
+
if (!decoded) return;
|
|
124
|
+
|
|
125
|
+
t.is(decoded.isWhitelisted, true, 'writer stays whitelisted after removeIndexer');
|
|
126
|
+
t.is(decoded.isWriter, true, 'writer role remains assigned after removeIndexer');
|
|
127
|
+
t.is(decoded.isIndexer, false, 'writer no longer an indexer after removeIndexer');
|
|
128
|
+
t.ok(b4a.equals(decoded.wk, writerPeer.base.local.key), 'writer key preserved after removeIndexer');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function readWritersLength(adminPeer) {
|
|
132
|
+
const entry = await adminPeer.base.view.get(EntryType.WRITERS_LENGTH);
|
|
133
|
+
if (!entry?.value) return 0;
|
|
134
|
+
return entry.value.readUInt32BE();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readIndexerMembership(base, writingKey) {
|
|
138
|
+
return Object.values(base.system.indexers ?? {}).some(entry => entry?.key && b4a.equals(entry.key, writingKey));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function readAdminBalance(adminPeer) {
|
|
142
|
+
const entry = await adminPeer.base.view.get(adminPeer.wallet.address);
|
|
143
|
+
return readBalance(entry);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function readBalance(entry) {
|
|
147
|
+
if (!entry?.value) return null;
|
|
148
|
+
const decoded = nodeEntryUtils.decode(entry.value);
|
|
149
|
+
if (!decoded) return null;
|
|
150
|
+
return toBalance(decoded.balance);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function assertFeeDeducted(t, before, after, message) {
|
|
154
|
+
const feeBalance = toBalance(transactionUtils.FEE);
|
|
155
|
+
if (!before || !after || !feeBalance) {
|
|
156
|
+
t.fail('Balance values missing for fee assertion.');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const expected = before.sub(feeBalance);
|
|
160
|
+
t.ok(expected, 'expected balance computed');
|
|
161
|
+
if (!expected) return;
|
|
162
|
+
t.ok(
|
|
163
|
+
b4a.equals(after.value, expected.value),
|
|
164
|
+
message
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function assertIndexerEntry(t, base, writerPeer) {
|
|
169
|
+
const entry = await base.view.get(writerPeer.wallet.address);
|
|
170
|
+
t.ok(entry, 'writer entry exists after re-adding indexer');
|
|
171
|
+
const decoded = nodeEntryUtils.decode(entry?.value);
|
|
172
|
+
t.ok(decoded, 'writer entry decodes after re-adding indexer');
|
|
173
|
+
if (!decoded) return;
|
|
174
|
+
t.is(decoded.isIndexer, true, 'writer flagged as indexer after re-adding');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function assertReplayRecorded(t, base, payload) {
|
|
178
|
+
const decodedOperation = safeDecodeApplyOperation(payload);
|
|
179
|
+
t.ok(decodedOperation, 're-add payload decodes');
|
|
180
|
+
if (!decodedOperation) return;
|
|
181
|
+
const txBuffer = decodedOperation?.aco?.tx;
|
|
182
|
+
t.ok(txBuffer, 're-add payload contains tx hash');
|
|
183
|
+
if (!txBuffer) return;
|
|
184
|
+
const entry = await base.view.get(txBuffer.toString('hex'));
|
|
185
|
+
t.ok(entry, 're-add tx recorded for replay protection');
|
|
186
|
+
}
|