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,1167 @@
|
|
|
1
|
+
import b4a from 'b4a';
|
|
2
|
+
import PartialStateMessageOperations from '../../../../../src/messages/partialStateMessages/PartialStateMessageOperations.js';
|
|
3
|
+
import CompleteStateMessageOperations from '../../../../../src/messages/completeStateMessages/CompleteStateMessageOperations.js';
|
|
4
|
+
import { deriveIndexerSequenceState, eventFlush } from '../../../../helpers/autobaseTestHelpers.js';
|
|
5
|
+
import {
|
|
6
|
+
setupAdminNetwork,
|
|
7
|
+
initializeBalances,
|
|
8
|
+
whitelistAddress
|
|
9
|
+
} from '../common/commonScenarioHelper.js';
|
|
10
|
+
import { promotePeerToWriter } from '../addWriter/addWriterScenarioHelpers.js';
|
|
11
|
+
import nodeEntryUtils from '../../../../../src/core/state/utils/nodeEntry.js';
|
|
12
|
+
import addressUtils from '../../../../../src/core/state/utils/address.js';
|
|
13
|
+
import transactionUtils from '../../../../../src/core/state/utils/transaction.js';
|
|
14
|
+
import { toBalance, PERCENT_75, BALANCE_ZERO } from '../../../../../src/core/state/utils/balance.js';
|
|
15
|
+
import { decimalStringToBigInt, bigIntTo16ByteBuffer } from '../../../../../src/utils/amountSerialization.js';
|
|
16
|
+
import { safeDecodeApplyOperation, safeEncodeApplyOperation } from '../../../../../src/utils/protobuf/operationHelpers.js';
|
|
17
|
+
import { ZERO_WK } from '../../../../../src/utils/buffer.js';
|
|
18
|
+
import { EntryType, OperationType, NETWORK_ID } from '../../../../../src/utils/constants.js';
|
|
19
|
+
import { createMessage } from '../../../../../src/utils/buffer.js';
|
|
20
|
+
import { blake3Hash } from '../../../../../src/utils/crypto.js';
|
|
21
|
+
import OperationValidationScenarioBase from '../common/base/OperationValidationScenarioBase.js';
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_INITIAL_BALANCE = bigIntTo16ByteBuffer(decimalStringToBigInt('10'));
|
|
24
|
+
export const DEFAULT_TRANSFER_AMOUNT = bigIntTo16ByteBuffer(decimalStringToBigInt('2'));
|
|
25
|
+
export const ZERO_TRANSFER_AMOUNT = bigIntTo16ByteBuffer(decimalStringToBigInt('0'));
|
|
26
|
+
|
|
27
|
+
function selectValidatorPeer(context, offset = 0) {
|
|
28
|
+
const candidates = context.peers.slice(1);
|
|
29
|
+
if (!candidates.length) {
|
|
30
|
+
throw new Error('Transfer scenarios require at least one non-admin peer as validator.');
|
|
31
|
+
}
|
|
32
|
+
return candidates[Math.min(offset, candidates.length - 1)];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function selectSenderPeer(context, offset = 1) {
|
|
36
|
+
const candidates = context.peers.slice(1);
|
|
37
|
+
if (candidates.length < 2) {
|
|
38
|
+
throw new Error('Transfer scenarios require at least two non-admin peers (validator + sender).');
|
|
39
|
+
}
|
|
40
|
+
return candidates[Math.min(offset, candidates.length - 1)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function selectRecipientPeer(context, offset = 2) {
|
|
44
|
+
const candidates = context.peers.slice(1);
|
|
45
|
+
if (!candidates.length) {
|
|
46
|
+
throw new Error('Transfer scenarios require at least one recipient candidate.');
|
|
47
|
+
}
|
|
48
|
+
return candidates[Math.min(offset, candidates.length - 1)];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function cloneEntry(entry) {
|
|
52
|
+
if (!entry?.value) return null;
|
|
53
|
+
return { value: b4a.from(entry.value) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function setupTransferScenario(
|
|
57
|
+
t,
|
|
58
|
+
{
|
|
59
|
+
nodes = 4,
|
|
60
|
+
validatorInitialBalance = DEFAULT_INITIAL_BALANCE,
|
|
61
|
+
senderInitialBalance = DEFAULT_INITIAL_BALANCE,
|
|
62
|
+
recipientInitialBalance = DEFAULT_INITIAL_BALANCE,
|
|
63
|
+
recipientHasEntry = true,
|
|
64
|
+
recipientPeer = null,
|
|
65
|
+
senderPeer = null,
|
|
66
|
+
validatorPeer = null
|
|
67
|
+
} = {}
|
|
68
|
+
) {
|
|
69
|
+
const context = await setupAdminNetwork(t, { nodes: Math.max(nodes, 4) });
|
|
70
|
+
|
|
71
|
+
const resolvedValidator = validatorPeer ?? selectValidatorPeer(context, 0);
|
|
72
|
+
const resolvedSender = senderPeer ?? selectSenderPeer(context, 1);
|
|
73
|
+
const resolvedRecipient = recipientPeer ?? selectRecipientPeer(context, 2);
|
|
74
|
+
|
|
75
|
+
const funding = [
|
|
76
|
+
[resolvedValidator.wallet.address, validatorInitialBalance],
|
|
77
|
+
[resolvedSender.wallet.address, senderInitialBalance]
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
if (recipientHasEntry && recipientInitialBalance && resolvedRecipient) {
|
|
81
|
+
funding.push([resolvedRecipient.wallet.address, recipientInitialBalance]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (funding.length) {
|
|
85
|
+
await initializeBalances(context, funding);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await whitelistAddress(context, resolvedValidator.wallet.address);
|
|
89
|
+
await whitelistAddress(context, resolvedSender.wallet.address);
|
|
90
|
+
if (recipientHasEntry && resolvedRecipient) {
|
|
91
|
+
await whitelistAddress(context, resolvedRecipient.wallet.address);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
context.addWriterScenario = { writerInitialBalance: validatorInitialBalance };
|
|
95
|
+
await promotePeerToWriter(t, context, { readerPeer: resolvedValidator });
|
|
96
|
+
await context.sync();
|
|
97
|
+
|
|
98
|
+
context.transferScenario = {
|
|
99
|
+
validatorPeer: resolvedValidator,
|
|
100
|
+
senderPeer: resolvedSender,
|
|
101
|
+
recipientPeer: resolvedRecipient
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return context;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function buildTransferPayload(
|
|
108
|
+
context,
|
|
109
|
+
{
|
|
110
|
+
senderPeer = context.transferScenario?.senderPeer ?? selectSenderPeer(context, 1),
|
|
111
|
+
validatorPeer = context.transferScenario?.validatorPeer ?? selectValidatorPeer(context, 0),
|
|
112
|
+
recipientPeer = context.transferScenario?.recipientPeer ?? selectRecipientPeer(context, 2),
|
|
113
|
+
recipientAddress = recipientPeer?.wallet?.address,
|
|
114
|
+
amount = DEFAULT_TRANSFER_AMOUNT,
|
|
115
|
+
txValidity = null
|
|
116
|
+
} = {}
|
|
117
|
+
) {
|
|
118
|
+
if (!recipientAddress) {
|
|
119
|
+
throw new Error('buildTransferPayload requires a recipient address.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const resolvedTxValidity =
|
|
123
|
+
txValidity ?? (await deriveIndexerSequenceState(validatorPeer.base));
|
|
124
|
+
|
|
125
|
+
const partial = await PartialStateMessageOperations.assembleTransferOperationMessage(
|
|
126
|
+
senderPeer.wallet,
|
|
127
|
+
recipientAddress,
|
|
128
|
+
b4a.toString(amount, 'hex'),
|
|
129
|
+
b4a.toString(resolvedTxValidity, 'hex')
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return CompleteStateMessageOperations.assembleCompleteTransferOperationMessage(
|
|
133
|
+
validatorPeer.wallet,
|
|
134
|
+
partial.address,
|
|
135
|
+
b4a.from(partial.tro.tx, 'hex'),
|
|
136
|
+
b4a.from(partial.tro.txv, 'hex'),
|
|
137
|
+
b4a.from(partial.tro.in, 'hex'),
|
|
138
|
+
partial.tro.to,
|
|
139
|
+
b4a.from(partial.tro.am, 'hex'),
|
|
140
|
+
b4a.from(partial.tro.is, 'hex')
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function buildTransferPayloadWithTxValidity(
|
|
145
|
+
context,
|
|
146
|
+
mutatedTxValidity,
|
|
147
|
+
options = {}
|
|
148
|
+
) {
|
|
149
|
+
if (!b4a.isBuffer(mutatedTxValidity)) {
|
|
150
|
+
throw new Error('buildTransferPayloadWithTxValidity requires a tx validity buffer.');
|
|
151
|
+
}
|
|
152
|
+
return buildTransferPayload(context, { ...options, txValidity: mutatedTxValidity });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function snapshotTransferEntries(
|
|
156
|
+
context,
|
|
157
|
+
{
|
|
158
|
+
senderPeer = context.transferScenario?.senderPeer ?? selectSenderPeer(context, 1),
|
|
159
|
+
recipientPeer = context.transferScenario?.recipientPeer ?? selectRecipientPeer(context, 2),
|
|
160
|
+
validatorPeer = context.transferScenario?.validatorPeer ?? selectValidatorPeer(context, 0),
|
|
161
|
+
skipSync = false
|
|
162
|
+
} = {}
|
|
163
|
+
) {
|
|
164
|
+
if (!skipSync) {
|
|
165
|
+
await context.sync();
|
|
166
|
+
}
|
|
167
|
+
const [senderEntry, recipientEntry, validatorEntry, txValidity] = await Promise.all([
|
|
168
|
+
validatorPeer.base.view.get(senderPeer.wallet.address),
|
|
169
|
+
recipientPeer ? validatorPeer.base.view.get(recipientPeer.wallet.address) : null,
|
|
170
|
+
validatorPeer.base.view.get(validatorPeer.wallet.address),
|
|
171
|
+
deriveIndexerSequenceState(validatorPeer.base)
|
|
172
|
+
]);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
senderEntry: cloneEntry(senderEntry),
|
|
176
|
+
recipientEntry: cloneEntry(recipientEntry),
|
|
177
|
+
validatorEntry: cloneEntry(validatorEntry),
|
|
178
|
+
txValidity
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function decodeBalance(entryBuffer) {
|
|
183
|
+
const decoded = nodeEntryUtils.decode(entryBuffer);
|
|
184
|
+
return decoded ? { decoded, balance: toBalance(decoded.balance) } : { decoded: null, balance: null };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function assertTransferSuccessState(
|
|
188
|
+
t,
|
|
189
|
+
context,
|
|
190
|
+
{
|
|
191
|
+
payload,
|
|
192
|
+
senderPeer = context.transferScenario?.senderPeer ?? selectSenderPeer(context, 1),
|
|
193
|
+
recipientPeer = context.transferScenario?.recipientPeer ?? selectRecipientPeer(context, 2),
|
|
194
|
+
validatorPeer = context.transferScenario?.validatorPeer ?? selectValidatorPeer(context, 0),
|
|
195
|
+
senderEntryBefore,
|
|
196
|
+
recipientEntryBefore,
|
|
197
|
+
validatorEntryBefore,
|
|
198
|
+
skipSync = false
|
|
199
|
+
} = {}
|
|
200
|
+
) {
|
|
201
|
+
if (!payload) throw new Error('assertTransferSuccessState requires a payload.');
|
|
202
|
+
|
|
203
|
+
const decodedPayload = safeDecodeApplyOperation(payload);
|
|
204
|
+
t.ok(decodedPayload?.tro, 'transfer payload decodes');
|
|
205
|
+
if (!decodedPayload?.tro) return;
|
|
206
|
+
|
|
207
|
+
const senderAddress = addressUtils.bufferToAddress(decodedPayload.address);
|
|
208
|
+
const recipientAddress = addressUtils.bufferToAddress(decodedPayload.tro.to);
|
|
209
|
+
const validatorAddress = addressUtils.bufferToAddress(decodedPayload.tro.va);
|
|
210
|
+
|
|
211
|
+
const amount = toBalance(decodedPayload.tro.am);
|
|
212
|
+
const fee = toBalance(transactionUtils.FEE);
|
|
213
|
+
t.ok(amount, 'transfer amount decodes');
|
|
214
|
+
t.ok(fee, 'fee decodes');
|
|
215
|
+
if (!amount || !fee) return;
|
|
216
|
+
|
|
217
|
+
const feeReward = fee.percentage(PERCENT_75);
|
|
218
|
+
t.ok(feeReward, 'validator reward computed');
|
|
219
|
+
if (!feeReward) return;
|
|
220
|
+
|
|
221
|
+
const senderBeforeBuf = senderEntryBefore?.value ?? senderEntryBefore ?? null;
|
|
222
|
+
const recipientBeforeBuf = recipientEntryBefore?.value ?? recipientEntryBefore ?? null;
|
|
223
|
+
const validatorBeforeBuf = validatorEntryBefore?.value ?? validatorEntryBefore ?? null;
|
|
224
|
+
|
|
225
|
+
const { decoded: senderBefore, balance: senderBalanceBefore } = decodeBalance(senderBeforeBuf);
|
|
226
|
+
const { decoded: recipientBefore, balance: recipientBalanceBefore } = decodeBalance(recipientBeforeBuf);
|
|
227
|
+
const { decoded: validatorBefore, balance: validatorBalanceBefore } = decodeBalance(validatorBeforeBuf);
|
|
228
|
+
|
|
229
|
+
t.ok(senderBefore, 'sender entry decodes before transfer');
|
|
230
|
+
t.ok(validatorBefore, 'validator entry decodes before transfer');
|
|
231
|
+
t.ok(senderBalanceBefore, 'sender balance available before transfer');
|
|
232
|
+
t.ok(validatorBalanceBefore, 'validator balance available before transfer');
|
|
233
|
+
if (!senderBefore || !validatorBefore || !senderBalanceBefore || !validatorBalanceBefore) return;
|
|
234
|
+
|
|
235
|
+
const isSelfTransfer = b4a.equals(decodedPayload.address, decodedPayload.tro.to);
|
|
236
|
+
const recipientIsValidator = b4a.equals(decodedPayload.tro.to, decodedPayload.tro.va);
|
|
237
|
+
|
|
238
|
+
const totalDeducted = isSelfTransfer ? fee : amount.add(fee);
|
|
239
|
+
t.ok(totalDeducted, 'total deducted amount calculated');
|
|
240
|
+
const expectedSenderBalance = totalDeducted ? senderBalanceBefore.sub(totalDeducted) : null;
|
|
241
|
+
t.ok(expectedSenderBalance, 'expected sender balance calculated');
|
|
242
|
+
|
|
243
|
+
const validatorBonus = recipientIsValidator ? amount.add(feeReward) : feeReward;
|
|
244
|
+
t.ok(validatorBonus, 'validator bonus calculated');
|
|
245
|
+
const expectedValidatorBalance = validatorBonus ? validatorBalanceBefore.add(validatorBonus) : null;
|
|
246
|
+
t.ok(expectedValidatorBalance, 'expected validator balance calculated');
|
|
247
|
+
|
|
248
|
+
let expectedRecipientBalance = null;
|
|
249
|
+
if (!isSelfTransfer && !recipientIsValidator) {
|
|
250
|
+
expectedRecipientBalance = recipientBalanceBefore
|
|
251
|
+
? recipientBalanceBefore.add(amount)
|
|
252
|
+
: amount;
|
|
253
|
+
t.ok(expectedRecipientBalance, 'expected recipient balance calculated');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!skipSync) {
|
|
257
|
+
await context.sync();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const senderAfter = await validatorPeer.base.view.get(senderAddress);
|
|
261
|
+
const recipientAfter = await validatorPeer.base.view.get(recipientAddress);
|
|
262
|
+
const validatorAfter = await validatorPeer.base.view.get(validatorAddress);
|
|
263
|
+
|
|
264
|
+
t.ok(senderAfter?.value, 'sender entry exists after transfer');
|
|
265
|
+
t.ok(validatorAfter?.value, 'validator entry exists after transfer');
|
|
266
|
+
|
|
267
|
+
const senderAfterDecoded = senderAfter?.value ? nodeEntryUtils.decode(senderAfter.value) : null;
|
|
268
|
+
const validatorAfterDecoded = validatorAfter?.value ? nodeEntryUtils.decode(validatorAfter.value) : null;
|
|
269
|
+
t.ok(senderAfterDecoded, 'sender entry decodes after transfer');
|
|
270
|
+
t.ok(validatorAfterDecoded, 'validator entry decodes after transfer');
|
|
271
|
+
|
|
272
|
+
if (!senderAfterDecoded || !validatorAfterDecoded) return;
|
|
273
|
+
|
|
274
|
+
t.ok(
|
|
275
|
+
b4a.equals(senderAfterDecoded.balance, expectedSenderBalance?.value),
|
|
276
|
+
'sender balance reflects fee (and amount if applicable)'
|
|
277
|
+
);
|
|
278
|
+
t.ok(
|
|
279
|
+
b4a.equals(validatorAfterDecoded.balance, expectedValidatorBalance?.value),
|
|
280
|
+
'validator balance reflects 75% fee reward'
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (!isSelfTransfer) {
|
|
284
|
+
t.ok(recipientAfter?.value, 'recipient entry exists after transfer');
|
|
285
|
+
const recipientAfterDecoded = recipientAfter?.value ? nodeEntryUtils.decode(recipientAfter.value) : null;
|
|
286
|
+
t.ok(recipientAfterDecoded, 'recipient entry decodes after transfer');
|
|
287
|
+
|
|
288
|
+
if (recipientIsValidator) {
|
|
289
|
+
t.ok(
|
|
290
|
+
b4a.equals(recipientAfter?.value, validatorAfter?.value),
|
|
291
|
+
'recipient equals validator entry when validator is recipient'
|
|
292
|
+
);
|
|
293
|
+
} else if (recipientAfterDecoded && expectedRecipientBalance) {
|
|
294
|
+
t.ok(
|
|
295
|
+
b4a.equals(recipientAfterDecoded.balance, expectedRecipientBalance.value),
|
|
296
|
+
'recipient balance reflects transferred amount'
|
|
297
|
+
);
|
|
298
|
+
if (!recipientBefore) {
|
|
299
|
+
t.is(recipientAfterDecoded.isWriter, false, 'new recipient is not a writer');
|
|
300
|
+
t.is(recipientAfterDecoded.isWhitelisted, false, 'new recipient is not whitelisted by default');
|
|
301
|
+
t.is(recipientAfterDecoded.isIndexer, false, 'new recipient is not an indexer');
|
|
302
|
+
t.ok(b4a.equals(recipientAfterDecoded.wk, ZERO_WK), 'new recipient uses zero writing key');
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const txEntryKey = decodedPayload.tro.tx.toString('hex');
|
|
308
|
+
const txEntry = await validatorPeer.base.view.get(txEntryKey);
|
|
309
|
+
t.ok(txEntry, 'transfer hash recorded for replay protection');
|
|
310
|
+
|
|
311
|
+
const recipientRegistryKey = `${EntryType.WRITER_ADDRESS}${senderBefore?.wk?.toString('hex') ?? ''}`;
|
|
312
|
+
if (recipientRegistryKey && recipientRegistryKey !== `${EntryType.WRITER_ADDRESS}`) {
|
|
313
|
+
const registryEntry = await validatorPeer.base.view.get(recipientRegistryKey);
|
|
314
|
+
if (registryEntry) {
|
|
315
|
+
t.comment('writer registry remains present (sanity check)');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function assertTransferFailureState(
|
|
321
|
+
t,
|
|
322
|
+
context,
|
|
323
|
+
{
|
|
324
|
+
payload,
|
|
325
|
+
senderPeer = context.transferScenario?.senderPeer ?? selectSenderPeer(context, 1),
|
|
326
|
+
recipientPeer = context.transferScenario?.recipientPeer ?? selectRecipientPeer(context, 2),
|
|
327
|
+
validatorPeer = context.transferScenario?.validatorPeer ?? selectValidatorPeer(context, 0),
|
|
328
|
+
senderEntryBefore = null,
|
|
329
|
+
recipientEntryBefore = null,
|
|
330
|
+
validatorEntryBefore = null
|
|
331
|
+
} = {}
|
|
332
|
+
) {
|
|
333
|
+
if (!payload) throw new Error('assertTransferFailureState requires payload.');
|
|
334
|
+
|
|
335
|
+
const decoded = safeDecodeApplyOperation(payload);
|
|
336
|
+
t.ok(decoded, 'invalid transfer payload decodes');
|
|
337
|
+
|
|
338
|
+
const senderAfter = await validatorPeer.base.view.get(senderPeer.wallet.address);
|
|
339
|
+
const validatorAfter = await validatorPeer.base.view.get(validatorPeer.wallet.address);
|
|
340
|
+
const recipientAfter = await validatorPeer.base.view.get(recipientPeer.wallet.address);
|
|
341
|
+
|
|
342
|
+
if (senderEntryBefore?.value) {
|
|
343
|
+
t.ok(senderAfter, 'sender entry persists after rejection');
|
|
344
|
+
if (senderAfter?.value) {
|
|
345
|
+
t.ok(b4a.equals(senderAfter.value, senderEntryBefore.value), 'sender entry unchanged after rejection');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (validatorEntryBefore?.value) {
|
|
350
|
+
t.ok(validatorAfter, 'validator entry persists after rejection');
|
|
351
|
+
if (validatorAfter?.value) {
|
|
352
|
+
t.ok(b4a.equals(validatorAfter.value, validatorEntryBefore.value), 'validator entry unchanged after rejection');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (recipientEntryBefore) {
|
|
357
|
+
if (recipientEntryBefore.value) {
|
|
358
|
+
t.ok(recipientAfter, 'recipient entry persists after rejection');
|
|
359
|
+
if (recipientAfter?.value) {
|
|
360
|
+
t.ok(b4a.equals(recipientAfter.value, recipientEntryBefore.value), 'recipient entry unchanged after rejection');
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
t.is(recipientAfter, null, 'recipient entry still missing after rejection');
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const txHash = decoded?.tro?.tx?.toString('hex');
|
|
368
|
+
if (txHash) {
|
|
369
|
+
const txEntry = await validatorPeer.base.view.get(txHash);
|
|
370
|
+
t.is(txEntry, null, 'tx hash not recorded after rejection');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export async function snapshotTransferStateAfterApply(context) {
|
|
375
|
+
const { senderEntry, recipientEntry, validatorEntry } = await snapshotTransferEntries(context, {
|
|
376
|
+
skipSync: false
|
|
377
|
+
});
|
|
378
|
+
return {
|
|
379
|
+
senderEntryAfter: senderEntry,
|
|
380
|
+
recipientEntryAfter: recipientEntry,
|
|
381
|
+
validatorEntryAfter: validatorEntry
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export async function assertTransferReplayIgnoredState(
|
|
386
|
+
t,
|
|
387
|
+
context,
|
|
388
|
+
{
|
|
389
|
+
payload,
|
|
390
|
+
senderEntryAfter,
|
|
391
|
+
recipientEntryAfter,
|
|
392
|
+
validatorEntryAfter,
|
|
393
|
+
validatorPeer = context.transferScenario?.validatorPeer ?? selectValidatorPeer(context, 0)
|
|
394
|
+
} = {}
|
|
395
|
+
) {
|
|
396
|
+
if (!payload) throw new Error('assertTransferReplayIgnoredState requires a payload.');
|
|
397
|
+
|
|
398
|
+
const decoded = safeDecodeApplyOperation(payload);
|
|
399
|
+
const senderAddress = decoded?.address;
|
|
400
|
+
const recipientAddress = decoded?.tro?.to;
|
|
401
|
+
const validatorAddress = decoded?.tro?.va;
|
|
402
|
+
const txHash = decoded?.tro?.tx?.toString('hex');
|
|
403
|
+
|
|
404
|
+
await context.sync();
|
|
405
|
+
|
|
406
|
+
const [senderAfter, recipientAfter, validatorAfter] = await Promise.all([
|
|
407
|
+
senderAddress ? validatorPeer.base.view.get(senderAddress) : null,
|
|
408
|
+
recipientAddress ? validatorPeer.base.view.get(recipientAddress) : null,
|
|
409
|
+
validatorAddress ? validatorPeer.base.view.get(validatorAddress) : null
|
|
410
|
+
]);
|
|
411
|
+
|
|
412
|
+
if (senderEntryAfter?.value) {
|
|
413
|
+
t.ok(senderAfter?.value, 'sender entry persists after replay ignore');
|
|
414
|
+
t.ok(
|
|
415
|
+
b4a.equals(senderAfter.value, senderEntryAfter.value),
|
|
416
|
+
'sender entry unchanged after replay ignore'
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (validatorEntryAfter?.value) {
|
|
421
|
+
t.ok(validatorAfter?.value, 'validator entry persists after replay ignore');
|
|
422
|
+
t.ok(
|
|
423
|
+
b4a.equals(validatorAfter.value, validatorEntryAfter.value),
|
|
424
|
+
'validator entry unchanged after replay ignore'
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (recipientEntryAfter) {
|
|
429
|
+
if (recipientEntryAfter.value) {
|
|
430
|
+
t.ok(recipientAfter?.value, 'recipient entry persists after replay ignore');
|
|
431
|
+
t.ok(
|
|
432
|
+
b4a.equals(recipientAfter.value, recipientEntryAfter.value),
|
|
433
|
+
'recipient entry unchanged after replay ignore'
|
|
434
|
+
);
|
|
435
|
+
} else {
|
|
436
|
+
t.is(recipientAfter, null, 'recipient entry still absent after replay ignore');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (txHash) {
|
|
441
|
+
const txEntry = await validatorPeer.base.view.get(txHash);
|
|
442
|
+
t.ok(txEntry, 'original tx hash remains recorded after replay ignore');
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function appendInvalidTransferPayload(context, invalidPayload, { node = null } = {}) {
|
|
447
|
+
const targetNode =
|
|
448
|
+
node ??
|
|
449
|
+
context.adminBootstrap ??
|
|
450
|
+
context.bootstrap ??
|
|
451
|
+
context.transferScenario?.validatorPeer ??
|
|
452
|
+
context.peers?.[0];
|
|
453
|
+
await targetNode.base.append(invalidPayload);
|
|
454
|
+
await targetNode.base.update();
|
|
455
|
+
await eventFlush();
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export async function applyInvalidTransferWithSchemaBypass(context, invalidPayload) {
|
|
459
|
+
const node =
|
|
460
|
+
context.transferScenario?.validatorPeer ??
|
|
461
|
+
context.adminBootstrap ??
|
|
462
|
+
context.bootstrap ??
|
|
463
|
+
context.peers?.[0];
|
|
464
|
+
|
|
465
|
+
if (!node?.state?.check) {
|
|
466
|
+
throw new Error('applyInvalidTransferWithSchemaBypass requires a node with state and check.');
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const originalValidate = node.state.check.validateTransferOperation;
|
|
470
|
+
node.state.check.validateTransferOperation = () => true;
|
|
471
|
+
try {
|
|
472
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
473
|
+
} finally {
|
|
474
|
+
node.state.check.validateTransferOperation = originalValidate;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export async function applyInvalidTransferMissingValidatorFields(context, invalidPayload) {
|
|
479
|
+
const node =
|
|
480
|
+
context.transferScenario?.validatorPeer ??
|
|
481
|
+
context.adminBootstrap ??
|
|
482
|
+
context.bootstrap ??
|
|
483
|
+
context.peers?.[0];
|
|
484
|
+
|
|
485
|
+
if (!node?.state?.check) {
|
|
486
|
+
throw new Error('applyInvalidTransferMissingValidatorFields requires a node with state and check.');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const originalValidate = node.state.check.validateTransferOperation;
|
|
490
|
+
const originalHasOwn = Object.hasOwn;
|
|
491
|
+
node.state.check.validateTransferOperation = () => true;
|
|
492
|
+
Object.hasOwn = (obj, prop) => {
|
|
493
|
+
if (prop === 'vs' || prop === 'va' || prop === 'vn') return false;
|
|
494
|
+
return originalHasOwn(obj, prop);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
499
|
+
} finally {
|
|
500
|
+
node.state.check.validateTransferOperation = originalValidate;
|
|
501
|
+
Object.hasOwn = originalHasOwn;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export async function applyInvalidTransferPayloadWithNoMutations(t, context, invalidPayload) {
|
|
506
|
+
const node =
|
|
507
|
+
context.adminBootstrap ??
|
|
508
|
+
context.bootstrap ??
|
|
509
|
+
context.transferScenario?.validatorPeer ??
|
|
510
|
+
context.peers?.[0];
|
|
511
|
+
|
|
512
|
+
if (!node?.base?._handlers?.apply) {
|
|
513
|
+
throw new Error('applyInvalidTransferPayloadWithNoMutations requires a node with an apply handler.');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const base = node.base;
|
|
517
|
+
const originalApply = base._handlers.apply;
|
|
518
|
+
let putCount = 0;
|
|
519
|
+
|
|
520
|
+
base._handlers.apply = async function patchedApply(nodes, view, baseCtx) {
|
|
521
|
+
const originalBatch = view.batch;
|
|
522
|
+
view.batch = function patchedBatch(...args) {
|
|
523
|
+
const batch = originalBatch.apply(this, args);
|
|
524
|
+
const originalPut = batch?.put?.bind(batch);
|
|
525
|
+
if (typeof originalPut === 'function') {
|
|
526
|
+
batch.put = (...putArgs) => {
|
|
527
|
+
putCount += 1;
|
|
528
|
+
return originalPut(...putArgs);
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
return batch;
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
return await originalApply.call(this, nodes, view, baseCtx);
|
|
536
|
+
} finally {
|
|
537
|
+
view.batch = originalBatch;
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
543
|
+
} finally {
|
|
544
|
+
base._handlers.apply = originalApply;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
t.is(putCount, 0, 'transfer schema validation does not mutate state');
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
export async function applyTransferAlreadyApplied(context, invalidPayload, validPayload) {
|
|
551
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
552
|
+
if (!node?.base) {
|
|
553
|
+
throw new Error('Operation already applied scenario requires a validator peer.');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
await node.base.append(validPayload);
|
|
557
|
+
await node.base.update();
|
|
558
|
+
await eventFlush();
|
|
559
|
+
|
|
560
|
+
context.transferScenario = context.transferScenario ?? {};
|
|
561
|
+
context.transferScenario.replaySnapshot = await snapshotTransferStateAfterApply(context);
|
|
562
|
+
|
|
563
|
+
await node.base.append(invalidPayload);
|
|
564
|
+
await node.base.update();
|
|
565
|
+
await eventFlush();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export async function applyTransferTotalDeductedAmountFailure(context, payload) {
|
|
569
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
570
|
+
if (!node?.base) {
|
|
571
|
+
throw new Error('Total deducted amount scenario requires a validator peer.');
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const balancePrototype = Object.getPrototypeOf(BALANCE_ZERO);
|
|
575
|
+
const originalAdd = balancePrototype.add;
|
|
576
|
+
let shouldFailNextAdd = true;
|
|
577
|
+
|
|
578
|
+
balancePrototype.add = function patchedAdd(...args) {
|
|
579
|
+
if (shouldFailNextAdd) {
|
|
580
|
+
shouldFailNextAdd = false;
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
return originalAdd.call(this, ...args);
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
await node.base.append(payload);
|
|
588
|
+
await node.base.update();
|
|
589
|
+
await eventFlush();
|
|
590
|
+
} finally {
|
|
591
|
+
balancePrototype.add = originalAdd;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export async function applyTransferSenderEntryRemoval(context, invalidPayload) {
|
|
596
|
+
return applyTransferSenderEntryOverride(context, invalidPayload, () => null);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export async function applyTransferSenderEntryCorruption(context, invalidPayload) {
|
|
600
|
+
return applyTransferSenderEntryOverride(context, invalidPayload, entry => {
|
|
601
|
+
if (!entry) return entry;
|
|
602
|
+
return { ...entry, value: b4a.alloc(1) };
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
async function applyTransferSenderEntryOverride(context, invalidPayload, mutateEntry) {
|
|
607
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
608
|
+
const senderPeer = context.transferScenario?.senderPeer ?? selectSenderPeer(context, 1);
|
|
609
|
+
if (!node?.base || !senderPeer?.wallet?.address) {
|
|
610
|
+
throw new Error('Sender entry override scenario requires validator and sender peers.');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const senderAddress = senderPeer.wallet.address;
|
|
614
|
+
const senderBuffer = addressUtils.addressToBuffer(senderAddress);
|
|
615
|
+
const base = node.base;
|
|
616
|
+
const originalApply = base._handlers.apply;
|
|
617
|
+
|
|
618
|
+
base._handlers.apply = async function patchedApply(nodes, view, baseCtx) {
|
|
619
|
+
const originalBatch = view.batch;
|
|
620
|
+
view.batch = function patchedBatch(...args) {
|
|
621
|
+
const batch = originalBatch.apply(this, args);
|
|
622
|
+
if (!batch?.get) return batch;
|
|
623
|
+
|
|
624
|
+
const originalGet = batch.get.bind(batch);
|
|
625
|
+
batch.get = async key => {
|
|
626
|
+
const entry = await originalGet(key);
|
|
627
|
+
if (isTargetKey(key, senderAddress, senderBuffer)) {
|
|
628
|
+
return mutateEntry(entry);
|
|
629
|
+
}
|
|
630
|
+
return entry;
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
return batch;
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
try {
|
|
637
|
+
return await originalApply.call(this, nodes, view, baseCtx);
|
|
638
|
+
} finally {
|
|
639
|
+
view.batch = originalBatch;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
645
|
+
} finally {
|
|
646
|
+
base._handlers.apply = originalApply;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function isTargetKey(key, targetAddressString, targetAddressBuffer) {
|
|
651
|
+
if (typeof key === 'string') {
|
|
652
|
+
return key === targetAddressString;
|
|
653
|
+
}
|
|
654
|
+
if (b4a.isBuffer(key) && targetAddressBuffer) {
|
|
655
|
+
return b4a.equals(key, targetAddressBuffer);
|
|
656
|
+
}
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export function mutateTransferTxHash(t, validPayload) {
|
|
661
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
662
|
+
t.ok(decoded, 'transfer payload decodes before mutation');
|
|
663
|
+
if (!decoded?.tro?.tx) return validPayload;
|
|
664
|
+
const mutated = b4a.alloc(decoded.tro.tx.length, 0x42);
|
|
665
|
+
decoded.tro.tx = mutated;
|
|
666
|
+
return safeEncodeApplyOperation(decoded);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
export function mutateTransferAmount(t, validPayload) {
|
|
670
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
671
|
+
t.ok(decoded, 'transfer payload decodes before mutation');
|
|
672
|
+
const amount = decoded?.tro?.am;
|
|
673
|
+
if (!amount || !b4a.isBuffer(amount)) return validPayload;
|
|
674
|
+
const mutated = b4a.from(amount);
|
|
675
|
+
mutated[mutated.length - 1] ^= 0x01;
|
|
676
|
+
decoded.tro.am = mutated;
|
|
677
|
+
return safeEncodeApplyOperation(decoded);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export async function mutateTransferAmountWithRehashedTx(t, validPayload) {
|
|
681
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
682
|
+
t.ok(decoded, 'transfer payload decodes before mutation');
|
|
683
|
+
const parent = decoded?.tro;
|
|
684
|
+
if (!parent?.am || !parent?.txv || !parent?.to || !parent?.in) return validPayload;
|
|
685
|
+
|
|
686
|
+
const mutatedAmount = b4a.from(parent.am);
|
|
687
|
+
mutatedAmount[mutatedAmount.length - 1] ^= 0x01;
|
|
688
|
+
parent.am = mutatedAmount;
|
|
689
|
+
|
|
690
|
+
const message = createMessage(NETWORK_ID, parent.txv, parent.to, parent.am, parent.in, OperationType.TRANSFER);
|
|
691
|
+
const regeneratedTxHash = await blake3Hash(message);
|
|
692
|
+
if (regeneratedTxHash?.length === parent.tx?.length) {
|
|
693
|
+
parent.tx = regeneratedTxHash;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return safeEncodeApplyOperation(decoded);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export function mutateTransferPayloadRemoveValidatorFields(t, validPayload) {
|
|
700
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
701
|
+
t.ok(decoded, 'transfer payload decodes before mutation');
|
|
702
|
+
if (!decoded?.tro) return validPayload;
|
|
703
|
+
delete decoded.tro.vs;
|
|
704
|
+
delete decoded.tro.va;
|
|
705
|
+
delete decoded.tro.vn;
|
|
706
|
+
return safeEncodeApplyOperation(decoded);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export function mutateTransferValidatorSignature(t, validPayload, { zeroFill = false } = {}) {
|
|
710
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
711
|
+
t.ok(decoded, 'transfer payload decodes before signature mutation');
|
|
712
|
+
const parent = decoded?.tro;
|
|
713
|
+
if (!parent?.vs) return validPayload;
|
|
714
|
+
|
|
715
|
+
const mutated = zeroFill ? b4a.alloc(parent.vs.length) : b4a.from(parent.vs);
|
|
716
|
+
if (!zeroFill && mutated.length > 0) {
|
|
717
|
+
mutated[mutated.length - 1] ^= 0xff;
|
|
718
|
+
}
|
|
719
|
+
if (parent.is && b4a.equals(mutated, parent.is) && mutated.length > 0) {
|
|
720
|
+
mutated[0] ^= 0x01; // ensure validator signature differs from requester signature
|
|
721
|
+
}
|
|
722
|
+
parent.vs = mutated;
|
|
723
|
+
return safeEncodeApplyOperation(decoded);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
export async function mutateTransferAmountInvalidWithRehash(t, validPayload, context) {
|
|
727
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
728
|
+
t.ok(decoded, 'transfer payload decodes before amount mutation');
|
|
729
|
+
const parent = decoded?.tro;
|
|
730
|
+
if (!parent?.am || !parent?.txv || !parent?.to || !parent?.in) return validPayload;
|
|
731
|
+
|
|
732
|
+
const requesterWallet = context?.transferScenario?.senderPeer?.wallet;
|
|
733
|
+
const validatorWallet = context?.transferScenario?.validatorPeer?.wallet;
|
|
734
|
+
|
|
735
|
+
parent.am = b4a.alloc(1); // invalid length to break toBalance
|
|
736
|
+
|
|
737
|
+
const requesterMessage = createMessage(
|
|
738
|
+
NETWORK_ID,
|
|
739
|
+
parent.txv,
|
|
740
|
+
parent.to,
|
|
741
|
+
parent.am,
|
|
742
|
+
parent.in,
|
|
743
|
+
OperationType.TRANSFER
|
|
744
|
+
);
|
|
745
|
+
const regeneratedTxHash = await blake3Hash(requesterMessage);
|
|
746
|
+
if (regeneratedTxHash?.length === parent.tx?.length) {
|
|
747
|
+
parent.tx = regeneratedTxHash;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (requesterWallet) {
|
|
751
|
+
parent.is = requesterWallet.sign(regeneratedTxHash);
|
|
752
|
+
}
|
|
753
|
+
if (validatorWallet && parent.vn) {
|
|
754
|
+
const validatorMessage = createMessage(NETWORK_ID, parent.tx, parent.vn, OperationType.TRANSFER);
|
|
755
|
+
parent.vs = validatorWallet.sign(await blake3Hash(validatorMessage));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return safeEncodeApplyOperation(decoded);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export async function mutateTransferRecipientAddressWithRehash(t, validPayload, context) {
|
|
762
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
763
|
+
t.ok(decoded, 'transfer payload decodes before recipient mutation');
|
|
764
|
+
const parent = decoded?.tro;
|
|
765
|
+
if (!parent?.to || !parent?.txv || !parent?.am || !parent?.in) return validPayload;
|
|
766
|
+
const requesterWallet = context?.transferScenario?.senderPeer?.wallet;
|
|
767
|
+
const validatorWallet = context?.transferScenario?.validatorPeer?.wallet;
|
|
768
|
+
|
|
769
|
+
const mutatedTo = b4a.from(parent.to);
|
|
770
|
+
if (mutatedTo.length > 0) {
|
|
771
|
+
mutatedTo[mutatedTo.length - 1] ^= 0x01;
|
|
772
|
+
}
|
|
773
|
+
parent.to = mutatedTo;
|
|
774
|
+
|
|
775
|
+
const message = createMessage(NETWORK_ID, parent.txv, parent.to, parent.am, parent.in, OperationType.TRANSFER);
|
|
776
|
+
const regeneratedTxHash = await blake3Hash(message);
|
|
777
|
+
if (regeneratedTxHash?.length === parent.tx?.length) {
|
|
778
|
+
parent.tx = regeneratedTxHash;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
if (requesterWallet) {
|
|
782
|
+
parent.is = requesterWallet.sign(regeneratedTxHash);
|
|
783
|
+
}
|
|
784
|
+
if (validatorWallet && parent.vn) {
|
|
785
|
+
const validatorMessage = createMessage(NETWORK_ID, parent.tx, parent.vn, OperationType.TRANSFER);
|
|
786
|
+
parent.vs = validatorWallet.sign(await blake3Hash(validatorMessage));
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return safeEncodeApplyOperation(decoded);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
export async function mutateTransferRecipientPublicKeyInvalidWithRehash(t, validPayload, context) {
|
|
793
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
794
|
+
t.ok(decoded, 'transfer payload decodes before recipient pk mutation');
|
|
795
|
+
const parent = decoded?.tro;
|
|
796
|
+
if (!parent?.to || !parent?.txv || !parent?.am || !parent?.in) return validPayload;
|
|
797
|
+
const requesterWallet = context?.transferScenario?.senderPeer?.wallet;
|
|
798
|
+
const validatorWallet = context?.transferScenario?.validatorPeer?.wallet;
|
|
799
|
+
|
|
800
|
+
const mutatedTo = b4a.from(parent.to);
|
|
801
|
+
if (mutatedTo.length > 0) {
|
|
802
|
+
const lastIndex = mutatedTo.length - 1;
|
|
803
|
+
mutatedTo[lastIndex] = mutatedTo[lastIndex] === 0x70 ? 0x71 : mutatedTo[lastIndex] ^ 0x01;
|
|
804
|
+
}
|
|
805
|
+
parent.to = mutatedTo;
|
|
806
|
+
|
|
807
|
+
const message = createMessage(NETWORK_ID, parent.txv, parent.to, parent.am, parent.in, OperationType.TRANSFER);
|
|
808
|
+
const regeneratedTxHash = await blake3Hash(message);
|
|
809
|
+
if (regeneratedTxHash?.length === parent.tx?.length) {
|
|
810
|
+
parent.tx = regeneratedTxHash;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
if (requesterWallet) {
|
|
814
|
+
parent.is = requesterWallet.sign(regeneratedTxHash);
|
|
815
|
+
}
|
|
816
|
+
if (validatorWallet && parent.vn) {
|
|
817
|
+
const validatorMessage = createMessage(NETWORK_ID, parent.tx, parent.vn, OperationType.TRANSFER);
|
|
818
|
+
parent.vs = validatorWallet.sign(await blake3Hash(validatorMessage));
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return safeEncodeApplyOperation(decoded);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
export function mutateTransferPayloadForInvalidSchema(t, validPayload) {
|
|
825
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
826
|
+
t.ok(decoded, 'transfer payload decodes before schema mutation');
|
|
827
|
+
if (!decoded) return validPayload;
|
|
828
|
+
return safeEncodeApplyOperation({ ...decoded, address: b4a.alloc(1) });
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export async function mutateTransferAmountToInvalidValue(t, validPayload, context) {
|
|
832
|
+
const decoded = safeDecodeApplyOperation(validPayload);
|
|
833
|
+
t.ok(decoded, 'transfer payload decodes before invalid amount mutation');
|
|
834
|
+
const parent = decoded?.tro;
|
|
835
|
+
if (!parent?.am || !parent?.txv || !parent?.to || !parent?.in) return validPayload;
|
|
836
|
+
|
|
837
|
+
const requesterWallet = context?.transferScenario?.senderPeer?.wallet;
|
|
838
|
+
const validatorWallet = context?.transferScenario?.validatorPeer?.wallet;
|
|
839
|
+
|
|
840
|
+
console.error('Invalid transfer amount.');
|
|
841
|
+
parent.am = b4a.alloc(1); // forces toBalance(amount).value === null
|
|
842
|
+
|
|
843
|
+
const requesterMessage = createMessage(
|
|
844
|
+
NETWORK_ID,
|
|
845
|
+
parent.txv,
|
|
846
|
+
parent.to,
|
|
847
|
+
parent.am,
|
|
848
|
+
parent.in,
|
|
849
|
+
OperationType.TRANSFER
|
|
850
|
+
);
|
|
851
|
+
const regeneratedTxHash = await blake3Hash(requesterMessage);
|
|
852
|
+
if (regeneratedTxHash?.length === parent.tx?.length) {
|
|
853
|
+
parent.tx = regeneratedTxHash;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (requesterWallet) {
|
|
857
|
+
parent.is = requesterWallet.sign(regeneratedTxHash);
|
|
858
|
+
}
|
|
859
|
+
if (validatorWallet && parent.vn) {
|
|
860
|
+
const validatorMessage = createMessage(NETWORK_ID, parent.tx, parent.vn, OperationType.TRANSFER);
|
|
861
|
+
parent.vs = validatorWallet.sign(await blake3Hash(validatorMessage));
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return safeEncodeApplyOperation(decoded);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
export async function applyTransferRecipientEntryCorruption(context, invalidPayload) {
|
|
868
|
+
return applyTransferRecipientEntryOverride(context, invalidPayload, entry => {
|
|
869
|
+
if (!entry) return entry;
|
|
870
|
+
return { ...entry, value: b4a.alloc(1) };
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
export async function applyTransferRecipientBalanceInvalid(context, invalidPayload) {
|
|
875
|
+
return applyTransferRecipientBalanceDecodeFailure(context, invalidPayload);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
export async function applyTransferRecipientBalanceAddFailure(context, invalidPayload) {
|
|
879
|
+
const balancePrototype = Object.getPrototypeOf(BALANCE_ZERO);
|
|
880
|
+
const originalAdd = balancePrototype.add;
|
|
881
|
+
let shouldFailNextAdd = true;
|
|
882
|
+
|
|
883
|
+
balancePrototype.add = function patchedAdd(...args) {
|
|
884
|
+
if (shouldFailNextAdd) {
|
|
885
|
+
shouldFailNextAdd = false;
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
return originalAdd.call(this, ...args);
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
try {
|
|
892
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
893
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
894
|
+
} finally {
|
|
895
|
+
balancePrototype.add = originalAdd;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
export function createTransferHandlerGuardBypassScenario({ title, logMessage, mutatePayload }) {
|
|
900
|
+
return new OperationValidationScenarioBase({
|
|
901
|
+
title,
|
|
902
|
+
setupScenario: setupTransferScenario,
|
|
903
|
+
buildValidPayload: context => buildTransferPayload(context),
|
|
904
|
+
mutatePayload: mutatePayload ?? ((_t, payload) => payload),
|
|
905
|
+
applyInvalidPayload: async (context, invalidPayload) => {
|
|
906
|
+
const snapshots = await snapshotTransferEntries(context);
|
|
907
|
+
context.transferScenario = {
|
|
908
|
+
...(context.transferScenario ?? {}),
|
|
909
|
+
handlerGuardSnapshot: snapshots
|
|
910
|
+
};
|
|
911
|
+
console.error(logMessage);
|
|
912
|
+
},
|
|
913
|
+
assertStateUnchanged: (t, context, _valid, invalid) =>
|
|
914
|
+
assertTransferFailureState(t, context, {
|
|
915
|
+
payload: invalid,
|
|
916
|
+
...(context.transferScenario?.handlerGuardSnapshot ?? {})
|
|
917
|
+
}),
|
|
918
|
+
expectedLogs: [logMessage]
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
export async function applyTransferRecipientBalanceUpdateFailure(context, invalidPayload) {
|
|
923
|
+
const balancePrototype = Object.getPrototypeOf(BALANCE_ZERO);
|
|
924
|
+
const originalUpdate = balancePrototype.update;
|
|
925
|
+
let shouldFailNextUpdate = true;
|
|
926
|
+
|
|
927
|
+
balancePrototype.update = function patchedUpdate(...args) {
|
|
928
|
+
if (shouldFailNextUpdate) {
|
|
929
|
+
shouldFailNextUpdate = false;
|
|
930
|
+
return null;
|
|
931
|
+
}
|
|
932
|
+
return originalUpdate.call(this, ...args);
|
|
933
|
+
};
|
|
934
|
+
|
|
935
|
+
try {
|
|
936
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
937
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
938
|
+
} finally {
|
|
939
|
+
balancePrototype.update = originalUpdate;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
async function applyTransferRecipientEntryOverride(context, invalidPayload, mutateEntry) {
|
|
944
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
945
|
+
const recipientPeer = context.transferScenario?.recipientPeer ?? selectRecipientPeer(context, 2);
|
|
946
|
+
if (!node?.base || !recipientPeer?.wallet?.address) {
|
|
947
|
+
throw new Error('Recipient entry override scenario requires validator and recipient peers.');
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const recipientAddress = recipientPeer.wallet.address;
|
|
951
|
+
const recipientBuffer = addressUtils.addressToBuffer(recipientAddress);
|
|
952
|
+
const base = node.base;
|
|
953
|
+
const originalApply = base._handlers.apply;
|
|
954
|
+
|
|
955
|
+
base._handlers.apply = async function patchedApply(nodes, view, baseCtx) {
|
|
956
|
+
const originalBatch = view.batch;
|
|
957
|
+
view.batch = function patchedBatch(...args) {
|
|
958
|
+
const batch = originalBatch.apply(this, args);
|
|
959
|
+
if (!batch?.get) return batch;
|
|
960
|
+
|
|
961
|
+
const originalGet = batch.get.bind(batch);
|
|
962
|
+
batch.get = async key => {
|
|
963
|
+
const entry = await originalGet(key);
|
|
964
|
+
if (isTargetKey(key, recipientAddress, recipientBuffer)) {
|
|
965
|
+
return mutateEntry(entry);
|
|
966
|
+
}
|
|
967
|
+
return entry;
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
return batch;
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
try {
|
|
974
|
+
return await originalApply.call(this, nodes, view, baseCtx);
|
|
975
|
+
} finally {
|
|
976
|
+
view.batch = originalBatch;
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
|
|
980
|
+
try {
|
|
981
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
982
|
+
} finally {
|
|
983
|
+
base._handlers.apply = originalApply;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
async function applyTransferRecipientBalanceDecodeFailure(context, invalidPayload) {
|
|
988
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
989
|
+
const recipientPeer = context.transferScenario?.recipientPeer ?? selectRecipientPeer(context, 2);
|
|
990
|
+
if (!node?.base || !recipientPeer?.wallet?.address) {
|
|
991
|
+
throw new Error('Recipient balance decode failure scenario requires validator and recipient peers.');
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
const targetAddress = recipientPeer.wallet.address;
|
|
995
|
+
const targetBuffer = addressUtils.addressToBuffer(targetAddress);
|
|
996
|
+
const originalDecode = nodeEntryUtils.decode;
|
|
997
|
+
let shouldMutateNextDecode = false;
|
|
998
|
+
|
|
999
|
+
nodeEntryUtils.decode = function patchedDecode(buffer) {
|
|
1000
|
+
const decoded = originalDecode(buffer);
|
|
1001
|
+
if (shouldMutateNextDecode && decoded) {
|
|
1002
|
+
shouldMutateNextDecode = false;
|
|
1003
|
+
console.error('Invalid recipient balance.');
|
|
1004
|
+
return { ...decoded, balance: b4a.alloc(1) };
|
|
1005
|
+
}
|
|
1006
|
+
return decoded;
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
const base = node.base;
|
|
1010
|
+
const originalApply = base._handlers.apply;
|
|
1011
|
+
|
|
1012
|
+
base._handlers.apply = async function patchedApply(nodes, view, baseCtx) {
|
|
1013
|
+
const originalBatch = view.batch;
|
|
1014
|
+
view.batch = function patchedBatch(...args) {
|
|
1015
|
+
const batch = originalBatch.apply(this, args);
|
|
1016
|
+
if (!batch?.get) return batch;
|
|
1017
|
+
|
|
1018
|
+
const originalGet = batch.get.bind(batch);
|
|
1019
|
+
batch.get = async key => {
|
|
1020
|
+
const entry = await originalGet(key);
|
|
1021
|
+
if (isTargetKey(key, targetAddress, targetBuffer)) {
|
|
1022
|
+
shouldMutateNextDecode = true;
|
|
1023
|
+
}
|
|
1024
|
+
return entry;
|
|
1025
|
+
};
|
|
1026
|
+
|
|
1027
|
+
return batch;
|
|
1028
|
+
};
|
|
1029
|
+
|
|
1030
|
+
try {
|
|
1031
|
+
return await originalApply.call(this, nodes, view, baseCtx);
|
|
1032
|
+
} finally {
|
|
1033
|
+
view.batch = originalBatch;
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
try {
|
|
1038
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
1039
|
+
} finally {
|
|
1040
|
+
base._handlers.apply = originalApply;
|
|
1041
|
+
nodeEntryUtils.decode = originalDecode;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
export async function applyTransferValidatorEntryDecodeFailure(context, invalidPayload) {
|
|
1046
|
+
const { node, validatorEntryBuffer } = await resolveValidatorContext(context);
|
|
1047
|
+
|
|
1048
|
+
const originalDecode = nodeEntryUtils.decode;
|
|
1049
|
+
let decodeCount = 0;
|
|
1050
|
+
|
|
1051
|
+
nodeEntryUtils.decode = function patchedDecode(buffer) {
|
|
1052
|
+
if (validatorEntryBuffer && b4a.isBuffer(buffer) && b4a.equals(buffer, validatorEntryBuffer)) {
|
|
1053
|
+
decodeCount += 1;
|
|
1054
|
+
if (decodeCount === 2) {
|
|
1055
|
+
console.error('Invalid validator entry.');
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return originalDecode(buffer);
|
|
1060
|
+
};
|
|
1061
|
+
|
|
1062
|
+
try {
|
|
1063
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
1064
|
+
} finally {
|
|
1065
|
+
nodeEntryUtils.decode = originalDecode;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
export async function applyTransferValidatorBalanceInvalid(context, invalidPayload) {
|
|
1070
|
+
const { node, validatorEntryBuffer } = await resolveValidatorContext(context);
|
|
1071
|
+
|
|
1072
|
+
const originalDecode = nodeEntryUtils.decode;
|
|
1073
|
+
|
|
1074
|
+
nodeEntryUtils.decode = function patchedDecode(buffer) {
|
|
1075
|
+
const decoded = originalDecode(buffer);
|
|
1076
|
+
if (decoded && validatorEntryBuffer && b4a.isBuffer(buffer) && b4a.equals(buffer, validatorEntryBuffer)) {
|
|
1077
|
+
console.error('Invalid validator balance.');
|
|
1078
|
+
return { ...decoded, balance: b4a.alloc(1) };
|
|
1079
|
+
}
|
|
1080
|
+
return decoded;
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
try {
|
|
1084
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
1085
|
+
} finally {
|
|
1086
|
+
nodeEntryUtils.decode = originalDecode;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
export async function applyTransferValidatorRewardFailure(context, invalidPayload) {
|
|
1091
|
+
const { node } = await resolveValidatorContext(context);
|
|
1092
|
+
const balancePrototype = Object.getPrototypeOf(BALANCE_ZERO);
|
|
1093
|
+
const originalPercentage = balancePrototype.percentage;
|
|
1094
|
+
let shouldFailNextPercentage = true;
|
|
1095
|
+
|
|
1096
|
+
balancePrototype.percentage = function patchedPercentage(...args) {
|
|
1097
|
+
if (shouldFailNextPercentage) {
|
|
1098
|
+
shouldFailNextPercentage = false;
|
|
1099
|
+
console.error('Invalid validator reward.');
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
return originalPercentage.call(this, ...args);
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
try {
|
|
1106
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
1107
|
+
} finally {
|
|
1108
|
+
balancePrototype.percentage = originalPercentage;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
export async function applyTransferValidatorBalanceAddFailure(context, invalidPayload) {
|
|
1113
|
+
const { node } = await resolveValidatorContext(context);
|
|
1114
|
+
const balancePrototype = Object.getPrototypeOf(BALANCE_ZERO);
|
|
1115
|
+
const originalAdd = balancePrototype.add;
|
|
1116
|
+
let shouldFailNextAdd = true;
|
|
1117
|
+
|
|
1118
|
+
balancePrototype.add = function patchedAdd(...args) {
|
|
1119
|
+
if (shouldFailNextAdd) {
|
|
1120
|
+
shouldFailNextAdd = false;
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
return originalAdd.call(this, ...args);
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
try {
|
|
1127
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
1128
|
+
} finally {
|
|
1129
|
+
balancePrototype.add = originalAdd;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
export async function applyTransferValidatorBalanceUpdateFailure(context, invalidPayload) {
|
|
1134
|
+
const { node } = await resolveValidatorContext(context);
|
|
1135
|
+
const balancePrototype = Object.getPrototypeOf(BALANCE_ZERO);
|
|
1136
|
+
const originalUpdate = balancePrototype.update;
|
|
1137
|
+
let updateCallCount = 0;
|
|
1138
|
+
|
|
1139
|
+
balancePrototype.update = function patchedUpdate(...args) {
|
|
1140
|
+
updateCallCount += 1;
|
|
1141
|
+
if (updateCallCount === 2) {
|
|
1142
|
+
return null;
|
|
1143
|
+
}
|
|
1144
|
+
return originalUpdate.call(this, ...args);
|
|
1145
|
+
};
|
|
1146
|
+
|
|
1147
|
+
try {
|
|
1148
|
+
await appendInvalidTransferPayload(context, invalidPayload, { node });
|
|
1149
|
+
} finally {
|
|
1150
|
+
balancePrototype.update = originalUpdate;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
async function resolveValidatorContext(context) {
|
|
1155
|
+
const node = context.transferScenario?.validatorPeer ?? context.peers?.[1];
|
|
1156
|
+
const validatorPeer = context.transferScenario?.validatorPeer ?? selectValidatorPeer(context, 0);
|
|
1157
|
+
if (!node?.base || !validatorPeer?.wallet?.address) {
|
|
1158
|
+
throw new Error('Validator scenario requires a validator peer.');
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const validatorEntry = await node.base.view.get(validatorPeer.wallet.address);
|
|
1162
|
+
if (!validatorEntry?.value) {
|
|
1163
|
+
throw new Error('Validator scenario requires an existing validator entry.');
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
return { node, validatorPeer, validatorEntryBuffer: validatorEntry.value };
|
|
1167
|
+
}
|