forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
test: add asset lock/unlock validation fuzz targets #7168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
thepastaclaw
wants to merge
6
commits into
dashpay:develop
from
thepastaclaw:fuzz/asset-lock-validation
Closed
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f4959eb
test(fuzz): add asset lock/unlock validation fuzz targets
thepastaclaw c80c9f7
chore(ci): retrigger flaky sqlite functional test
thepastaclaw fa5e710
fix: correct alphabetical ordering in Makefile.test.include
thepastaclaw 197187c
fix: add fuzz target to non-backported.txt
thepastaclaw 0fcd955
fix: add missing newline in non-backported.txt
thepastaclaw 3c4073c
style: apply clang-format to asset lock fuzz target
thepastaclaw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,206 @@ | ||
| // Copyright (c) 2026 The Dash Core developers | ||
| // Distributed under the MIT software license, see the accompanying | ||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
|
||
| #include <chainparams.h> | ||
| #include <consensus/validation.h> | ||
| #include <evo/assetlocktx.h> | ||
| #include <evo/specialtx.h> | ||
| #include <primitives/transaction.h> | ||
| #include <script/script.h> | ||
| #include <streams.h> | ||
| #include <test/fuzz/FuzzedDataProvider.h> | ||
| #include <test/fuzz/fuzz.h> | ||
| #include <version.h> | ||
|
|
||
| #include <cassert> | ||
| #include <cstdint> | ||
| #include <exception> | ||
| #include <limits> | ||
| #include <vector> | ||
|
|
||
| namespace { | ||
| CScript ConsumeScript(FuzzedDataProvider& fuzzed_data_provider, size_t max_size = 64) | ||
| { | ||
| const std::vector<uint8_t> raw_script = fuzzed_data_provider.ConsumeBytes<uint8_t>( | ||
| fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, max_size)); | ||
| return CScript(raw_script.begin(), raw_script.end()); | ||
| } | ||
|
|
||
| CScript ConsumeP2PKHScript(FuzzedDataProvider& fuzzed_data_provider) | ||
| { | ||
| const std::vector<uint8_t> key_hash = fuzzed_data_provider.ConsumeBytes<uint8_t>(20); | ||
| return CScript() << OP_DUP << OP_HASH160 << key_hash << OP_EQUALVERIFY << OP_CHECKSIG; | ||
| } | ||
|
|
||
| CAmount ConsumeAmount(FuzzedDataProvider& fuzzed_data_provider) | ||
| { | ||
| return fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(-1, static_cast<CAmount>(MAX_MONEY) + 1); | ||
| } | ||
|
|
||
| std::vector<uint8_t> BuildAssetLockPayload(const uint8_t version, const std::vector<CTxOut>& credit_outputs) | ||
| { | ||
| CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); | ||
| ds << version; | ||
| ds << credit_outputs; | ||
| return {UCharCast(ds.data()), UCharCast(ds.data() + ds.size())}; | ||
| } | ||
|
|
||
| CBLSSignature ConsumeBLSSignature(FuzzedDataProvider& fuzzed_data_provider) | ||
| { | ||
| CBLSSignature sig; | ||
| auto bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(CBLSSignature::SerSize); | ||
| bytes.resize(CBLSSignature::SerSize); | ||
| sig.SetBytes(bytes, fuzzed_data_provider.ConsumeBool()); | ||
| return sig; | ||
| } | ||
|
|
||
| uint256 ConsumeUInt256(FuzzedDataProvider& fuzzed_data_provider) | ||
| { | ||
| uint256 value; | ||
| auto it = value.begin(); | ||
| const std::vector<uint8_t> bytes = fuzzed_data_provider.ConsumeBytes<uint8_t>(32); | ||
| for (uint8_t b : bytes) { | ||
| *it = b; | ||
| ++it; | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| void initialize_asset_lock_unlock() { SelectParams(CBaseChainParams::REGTEST); } | ||
| } // namespace | ||
|
|
||
| FUZZ_TARGET(asset_lock_tx, .init = initialize_asset_lock_unlock) | ||
| { | ||
| FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); | ||
|
|
||
| CMutableTransaction tx; | ||
| tx.nVersion = CTransaction::SPECIAL_VERSION; | ||
| tx.nType = TRANSACTION_ASSET_LOCK; | ||
|
|
||
| std::vector<CTxOut> credit_outputs; | ||
| const size_t num_credit_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 4); | ||
| CAmount credit_outputs_total{0}; | ||
| for (size_t i = 0; i < num_credit_outputs; ++i) { | ||
| const CAmount amount = fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, 10 * COIN); | ||
| assert(MoneyRange(amount)); | ||
| assert(MoneyRange(credit_outputs_total + amount)); | ||
| credit_outputs_total += amount; | ||
| credit_outputs.emplace_back(amount, ConsumeP2PKHScript(fuzzed_data_provider)); | ||
| } | ||
|
|
||
| SetTxPayload(tx, CAssetLockPayload(credit_outputs)); | ||
|
|
||
| tx.vout.emplace_back(credit_outputs_total, CScript() << OP_RETURN << std::vector<uint8_t>{}); | ||
|
|
||
| const size_t num_regular_outputs = fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4); | ||
| for (size_t i = 0; i < num_regular_outputs; ++i) { | ||
| tx.vout.emplace_back(ConsumeAmount(fuzzed_data_provider), ConsumeScript(fuzzed_data_provider)); | ||
| } | ||
|
|
||
| const uint8_t scenario = fuzzed_data_provider.ConsumeIntegralInRange<uint8_t>(0, 11); | ||
| switch (scenario) { | ||
| case 0: // bad-assetlocktx-type | ||
| tx.nType = fuzzed_data_provider.ConsumeBool() ? TRANSACTION_ASSET_UNLOCK : TRANSACTION_NORMAL; | ||
| break; | ||
| case 1: // bad-assetlocktx-non-empty-return | ||
| tx.vout[0].scriptPubKey = CScript() << OP_RETURN | ||
| << fuzzed_data_provider.ConsumeBytes<uint8_t>( | ||
| fuzzed_data_provider.ConsumeIntegralInRange<size_t>(1, 16)); | ||
| break; | ||
| case 2: // bad-assetlocktx-opreturn-outofrange | ||
| tx.vout[0].nValue = fuzzed_data_provider.ConsumeBool() ? 0 : static_cast<CAmount>(MAX_MONEY) + 1; | ||
| break; | ||
| case 3: // bad-assetlocktx-multiple-return | ||
| tx.vout.emplace_back(1, CScript() << OP_RETURN << std::vector<uint8_t>{}); | ||
| break; | ||
| case 4: // bad-assetlocktx-no-return | ||
| tx.vout[0].scriptPubKey = ConsumeScript(fuzzed_data_provider); | ||
| if (!tx.vout[0].scriptPubKey.empty() && tx.vout[0].scriptPubKey[0] == OP_RETURN) { | ||
| tx.vout[0].scriptPubKey = CScript() << OP_1; | ||
| } | ||
| break; | ||
| case 5: // bad-assetlocktx-payload | ||
| tx.vExtraPayload = fuzzed_data_provider.ConsumeBytes<uint8_t>( | ||
| fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 8)); | ||
| break; | ||
| case 6: // bad-assetlocktx-version | ||
| tx.vExtraPayload = BuildAssetLockPayload(fuzzed_data_provider.ConsumeBool() ? uint8_t{0} | ||
| : std::numeric_limits<uint8_t>::max(), | ||
| credit_outputs); | ||
| break; | ||
| case 7: // bad-assetlocktx-emptycreditoutputs | ||
| tx.vExtraPayload = BuildAssetLockPayload(CAssetLockPayload::CURRENT_VERSION, {}); | ||
| break; | ||
| case 8: { // bad-assetlocktx-credit-outofrange | ||
| auto bad_credit_outputs = credit_outputs; | ||
| bad_credit_outputs[0].nValue = fuzzed_data_provider.ConsumeBool() ? 0 : static_cast<CAmount>(MAX_MONEY) + 1; | ||
| tx.vExtraPayload = BuildAssetLockPayload(CAssetLockPayload::CURRENT_VERSION, bad_credit_outputs); | ||
| break; | ||
| } | ||
| case 9: { // bad-assetlocktx-pubKeyHash | ||
| auto bad_credit_outputs = credit_outputs; | ||
| bad_credit_outputs[0].scriptPubKey = ConsumeScript(fuzzed_data_provider); | ||
| if (bad_credit_outputs[0].scriptPubKey.IsPayToPublicKeyHash()) { | ||
| bad_credit_outputs[0].scriptPubKey = CScript() << OP_1; | ||
| } | ||
| tx.vExtraPayload = BuildAssetLockPayload(CAssetLockPayload::CURRENT_VERSION, bad_credit_outputs); | ||
| break; | ||
| } | ||
| case 10: { // bad-assetlocktx-creditamount | ||
| auto bad_credit_outputs = credit_outputs; | ||
| bad_credit_outputs[0].nValue += fuzzed_data_provider.ConsumeBool() ? 1 : -1; | ||
| tx.vExtraPayload = BuildAssetLockPayload(CAssetLockPayload::CURRENT_VERSION, bad_credit_outputs); | ||
| break; | ||
| } | ||
| case 11: // Valid path | ||
| break; | ||
| } | ||
|
|
||
| TxValidationState state; | ||
| const bool result = CheckAssetLockTx(CTransaction(tx), state); | ||
| assert(result == state.IsValid()); | ||
| } | ||
|
|
||
| FUZZ_TARGET(asset_lock_tx_raw, .init = initialize_asset_lock_unlock) | ||
| { | ||
| CDataStream ds(buffer, SER_NETWORK, INIT_PROTO_VERSION); | ||
| try { | ||
| const CTransaction tx(deserialize, ds); | ||
| TxValidationState state; | ||
| const bool result = CheckAssetLockTx(tx, state); | ||
| assert(result == state.IsValid()); | ||
| } catch (const std::exception&) { | ||
| } | ||
| } | ||
|
|
||
| FUZZ_TARGET(asset_unlock_fee, .init = initialize_asset_lock_unlock) | ||
| { | ||
| FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); | ||
|
|
||
| CMutableTransaction tx; | ||
| tx.nVersion = CTransaction::SPECIAL_VERSION; | ||
| tx.nType = TRANSACTION_ASSET_UNLOCK; | ||
|
|
||
| const uint8_t payload_version = fuzzed_data_provider.ConsumeIntegral<uint8_t>(); | ||
| const uint64_t index = fuzzed_data_provider.ConsumeIntegral<uint64_t>(); | ||
| const uint32_t fee = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); | ||
| const uint32_t requested_height = fuzzed_data_provider.ConsumeIntegral<uint32_t>(); | ||
| const uint256 quorum_hash = ConsumeUInt256(fuzzed_data_provider); | ||
| const CBLSSignature quorum_sig = ConsumeBLSSignature(fuzzed_data_provider); | ||
| SetTxPayload(tx, CAssetUnlockPayload(payload_version, index, fee, requested_height, quorum_hash, quorum_sig)); | ||
|
|
||
| if (fuzzed_data_provider.ConsumeBool()) { | ||
| // Trigger payload deserialization failures with short/truncated random bytes. | ||
| tx.vExtraPayload = fuzzed_data_provider.ConsumeBytes<uint8_t>( | ||
| fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 16)); | ||
| } else if (fuzzed_data_provider.ConsumeBool()) { | ||
| // Trigger bad-txns-assetunlock-fee-outofrange. | ||
| SetTxPayload(tx, CAssetUnlockPayload(payload_version, index, 0, requested_height, quorum_hash, quorum_sig)); | ||
| } | ||
|
|
||
| CAmount txfee{0}; | ||
| TxValidationState state; | ||
| const bool result = GetAssetUnlockFee(CTransaction(tx), txfee, state); | ||
| assert(result == state.IsValid()); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.