Skip to content

fuzz: wallet, add target for Spend #28236

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

Conversation

Ayush170-Future
Copy link
Contributor

This PR adds fuzz coverage for wallet/spend.

Motivation: Issue 27272

This PR adds Fuzz coverage to the whole concept of Creating a New Transaction as well as other sections of the Spend file. Because CreateTransaction is one of the most frequently used functions in the wallet codebase, merging this PR will significantly improve the wallet codebase's Fuzz testing!

I also used the Singleton Class concept for creating Wallet instances because it assures that only one instance of it is created during all Fuzz runs, which significantly boosts the file's exec/sec.

@DrahtBot
Copy link
Contributor

DrahtBot commented Aug 7, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #29936 (fuzz: wallet: add target for CreateTransaction by brunoerg)
  • #26606 (wallet: Implement independent BDB parser by achow101)
  • #26596 (wallet: Migrate legacy wallets to descriptor wallets without requiring BDB by achow101)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@DrahtBot DrahtBot added the Tests label Aug 7, 2023

void InitializeWallet() {
auto& node{g_setup->m_node};
wallet = CreateSyncedWallet(*node.chain, WITH_LOCK(Assert(node.chainman)->GetMutex(), return node.chainman->ActiveChain()), g_setup->coinbaseKey);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that CreateSyncedWallet will call SetupDescriptorScriptPubKeyMans() which internally will make a seed.

void CWallet::SetupDescriptorScriptPubKeyMans()
{
    AssertLockHeld(cs_wallet);

    if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
        // Make a seed
        CKey seed_key;
        seed_key.MakeNewKey(true);
        CPubKey seed = seed_key.GetPubKey();
        assert(seed_key.VerifyPubKey(seed));

        // Get the extended key
        CExtKey master_key;
        master_key.SetSeed(seed_key);

        SetupDescriptorScriptPubKeyMans(master_key);
    }

Not sure, but wouldn't it be non-deterministic? A solution would be to have a fuzz version of CreateSyncedWallet which calls SetupDescriptorScriptPubKeyMans passing a key that we created using buf.

@brunoerg
Copy link
Contributor

brunoerg commented Aug 8, 2023

From lint:

+ test/lint/all-lint.py
This diff appears to have added new lines with trailing whitespace.
The following changes were suspected:

diff --git a/src/wallet/test/fuzz/spend.cpp b/src/wallet/test/fuzz/spend.cpp
@@ -0,0 +1,254 @@
+namespace wallet { 
+ * Singleton wallet class ensures that only one 
+ * instance of CWallet is created and reused as required. 
+ * of creating a new `Descriptor Wallet` each time and deletes 
+CCoinControl GetNewCoinControl(FuzzedDataProvider& fuzzed_data_provider, CWallet& wallet, std::vector<COutput>& coins) 
+    
+    // Because none of the UTXO will ever be used (marked as spent), mining this many should be sufficient. 
+                                        /*nAmount=*/ConsumeMoney(fuzzed_data_provider), 
+                const CTxOut tx_out{ConsumeMoney(fuzzed_data_provider), ConsumeScript(fuzzed_data_provider)}; 
+    
+                // Taking a random sub-sequence from available coins 
+}   
Success: no issues found in 269 source files
^---- failure generated from lint-whitespace.py

@Ayush170-Future
Copy link
Contributor Author

Ayush170-Future commented Aug 10, 2023

Thanks, @brunoerg for your reviews! In the recent push, I've addressed most of your reviews.

  • clang-format to fix the lint's issue.
  • Changed PickValueFromArray to the PickValue approach.
  • Fixed one grammar mistake.

This comment is really interesting, but I'm not sure whether the non-determinism in this case might pose a significant issue or not.
So, I'll wait for more people to give their thoughts on this and we can write a new CreateSyncedWallet specific for Fuzzing purposes if felt necessary.

@Ayush170-Future
Copy link
Contributor Author

The fuzzer logs are unclear, I'm not sure why it failed suddenly. Maybe some issue with the Fuzzer again?

@maflcko
Copy link
Member

maflcko commented Nov 2, 2023

You'll need to rebase on current master in any case

@Ayush170-Future
Copy link
Contributor Author

Ayush170-Future commented Nov 4, 2023

  • Force-pushed rebasing on the current master branch.

PS - There is one change in CRecipient that I didn't know about. Will update this PR against that change soon!

@Ayush170-Future
Copy link
Contributor Author

  • Force-pushed updating the CRecipient wrt 28246.

All CI checks passed now 💪

Copy link
Member

@dergoegge dergoegge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return instance;
}

CWallet& GetWallet()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CWallet& GetWallet()
const CWallet& GetWallet()

It's otherwise hard to tell if the global wallet instance is mutated. Same for GetCoins().

If the global instance is mutated, the target is likely to be non-deterministic which hurts fuzzer efficiency. The target should behave exactly the same when given the same input, but that might not be the case if global state is used and modified.

(The suggested change might not compile if wallet or available_coins are used as non-const/mutated)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this should make it more deterministic. As you said, it's causing some compilation errors with other code. I'll force-push once I've fixed them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CreateTransaction() and FundTransaction() want the CWallet parameter to be non-const. I'm struggling to find a work around but I'm trying.

Just wanted to clear this, I think the CWallet instance inside these functions doesn't seem to be directly mutated or changed anytime. So, do we really want to make it to const. Am I understanding this correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If CreateTransaction() and FundTransaction() do not mutate the wallet, then they should probably take a const reference instead. But glancing at FundTransaction, it calls CWallet::LockCoin which is not a const method and could probably lead to non-determinism since it appears to be modifying internal state of a wallet.

wallet.LockCoin(txin.prevout);

Just wanted to clear this, I think the CWallet instance inside these functions doesn't seem to be directly mutated or changed anytime. So, do we really want to make it to const.

Marking things that should not be mutated as const will make this a compile time check, giving us more confidence that the test is working properly.

You could try to refactor CreateTransaction and/or FundTransaction to take a const CWallet& and see what happens when you compile. The LockCoin issue mentioned above should be one of the things the compiler complains about.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I believe the LockCoin function does mutate the global wallet. I'll need to figure out how to make this deterministic then.

I apologize for the delayed response, I was/am occupied with something else. But, I'm looking forward to brainstorming this and finding a solution as soon as possible.

Copy link
Contributor Author

@Ayush170-Future Ayush170-Future Jan 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've thought about it, and I think a global Singleton might not be the smartest way to save resources. The wallet object definitely mutates during execution. I think I've to remove the Singleton pattern, but doing so will make the file very slow, as creating the Descriptor Wallet is really time-consuming.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you removed the Singleton, did you perhaps not push the latest state of this PR?

},
[&] {
// Occasionally, creating a random Transaction for Fuzzing.
auto new_mtx = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing ser-params? See compile failure due to silent merge conflict.

Needs rebase

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I'll rebase in the next force push.

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spend.cpp coverage is not too bad already: https://maflcko.github.io/b-c-cov/fuzz.coverage/src/wallet/spend.cpp.gcov.html

It may be good to clarify the new coverage a bit.

Comment on lines +205 to +207
CoinsResult wallet_coins;

LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 50)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this should be a separate fuzz target?

Copy link
Contributor Author

@Ayush170-Future Ayush170-Future Dec 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can create a separate PR for this immediately. Once it's ready, we can get it merged, and I can continue working on this. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree wtih @maflcko, since the seeds are created per target, it would be better to have a separate target for CoinsResult.

@Ayush170-Future
Copy link
Contributor Author

Spend.cpp coverage is not too bad already: https://maflcko.github.io/b-c-cov/fuzz.coverage/src/wallet/spend.cpp.gcov.html

It may be good to clarify the new coverage a bit.

I didn't understand. Are you suggesting that I reconsider what I want to increase coverage of in this file?

Also, I have a question about the blue lines in the coverage reports. Do they represent the line coverage of the code during fuzzing? For instance, if these functions are encountered in the process of running any fuzz file, the fuzz coverage will display them in blue. If I'm understanding this correctly, wouldn't it still make sense to have a separate fuzz file for Creating Transaction so that we can explore more test cases and code flows? These might not be tested if the coverage is coming from another fuzz file designed for a different purpose or codebase.

https://maflcko.github.io/b-c-cov/fuzz.coverage/src/wallet/spend.cpp.gcov.html

@brunoerg brunoerg mentioned this pull request Apr 17, 2024
13 tasks
Copy link
Contributor

@murchandamus murchandamus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey,

Just did a quick pass. What is the status of this PR?

static auto testing_setup = MakeNoLogFileContext<TestChain100Setup>();
g_setup = testing_setup.get();

// Add 50 spendable UTXO, 50 BTC each, to the wallet (total balance 5000 BTC)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is inconsistent:

Suggested change
// Add 50 spendable UTXO, 50 BTC each, to the wallet (total balance 5000 BTC)
// Add 50 spendable UTXO, 50 BTC each, to the wallet (total balance 2500 BTC)

A variable set of available coins might be desirable. Smaller amounts could for example trigger behavior regarding negative effective value or having insufficient funds to create a transaction.

return instance;
}

CWallet& GetWallet()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you removed the Singleton, did you perhaps not push the latest state of this PR?

Comment on lines +205 to +207
CoinsResult wallet_coins;

LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 50)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree wtih @maflcko, since the seeds are created per target, it would be better to have a separate target for CoinsResult.

@maflcko
Copy link
Member

maflcko commented Apr 17, 2024

Are you still working on this?

}
}

// Occasionally, some random `CCoutPoint` are also selected.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Occasionally, some random `CCoutPoint` are also selected.
// Occasionally, some random `COutPoint` are also selected.

@Ayush170-Future
Copy link
Contributor Author

Ayush170-Future commented Apr 18, 2024

Are you still working on this?

Sorry I got a little occupied with some other things and would want to continue working on this. I've the approach in mind and will force push the update by the end of this month.

  • Will create a new file for CoinsResult and remove global Singleton from the Spend file.

@DrahtBot
Copy link
Contributor

🐙 This pull request conflicts with the target branch and needs rebase.

@fanquake fanquake marked this pull request as draft June 25, 2024 15:40
@fanquake
Copy link
Member

I've the approach in mind and will force push the update by the end of this month.

Moved to draft for now.

@maflcko
Copy link
Member

maflcko commented Jul 16, 2024

Picked up in #30461

@maflcko maflcko closed this Jul 16, 2024
@bitcoin bitcoin locked and limited conversation to collaborators Jul 16, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants