This implements a proposal in #12805 and is a rebase of #14533.
This seems to be a working approach, but I’m not sure why the p2p_segwit.py
functional test needed a change, so I’ll look into it more.
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
No conflicts as of last run.
60@@ -61,7 +61,7 @@ const CBlockIndex *CChain::FindFork(const CBlockIndex *pindex) const {
61
62 CBlockIndex* CChain::FindEarliestAtLeast(int64_t nTime, int height) const
63 {
64- std::pair<int64_t, int> blockparams = std::make_pair(nTime, height);
65+2 std::pair<int64_t, int> blockparams = std::make_pair(nTime, height);
60@@ -61,6 +61,7 @@ void WalletInit::AddWalletOptions() const
61 gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
62 gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
63 gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET);
64+ gArgs.AddArg("-walletcrosschain", strprintf("Allow reusing wallet files across chains (default: %u)", DEFAULT_WALLETCROSSCHAIN), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET);
WALLET_DEBUG_TEST
category?
3922+ {
3923+ // Wallet is assumed to be from another chain, if none of
3924+ // its last 6 best known blocks are in the active chain
3925+ // (this heavily relies on the fact that locator stores
3926+ // last 10 blocks consecutively):
3927+ const Optional<int> fork_height = chain.findLocatorFork(locator);
std::optional
in new code.
Looks like the CI picked up a memory leak running wallet_crosschain.py
:
0 node0 stderr =================================================================
1==39469==ERROR: LeakSanitizer: detected memory leaks
2Direct leak of 22096 byte(s) in 18 object(s) allocated from:
3 [#0](/bitcoin-bitcoin/0/) 0x556b3fed1e6d in malloc (/tmp/cirrus-ci-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/src/bitcoind+0x17b7e6d)
4 [#1](/bitcoin-bitcoin/1/) 0x7fd21ead48c4 in __os_malloc (/lib/x86_64-linux-gnu/libdb_cxx-5.3.so+0x1698c4)
5Indirect leak of 2016 byte(s) in 18 object(s) allocated from:
6 [#0](/bitcoin-bitcoin/0/) 0x556b3fed1e6d in malloc (/tmp/cirrus-ci-build/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/src/bitcoind+0x17b7e6d)
7 [#1](/bitcoin-bitcoin/1/) 0x7fd21ead48c4 in __os_malloc (/lib/x86_64-linux-gnu/libdb_cxx-5.3.so+0x1698c4)
8SUMMARY: AddressSanitizer: 24112 byte(s) leaked in 36 allocation(s).
2752+ error = _("Wallet files should not be reused across chains.");
2753+ return false;
2754+ }
2755+ }
2756+ }
2757+
2756+ // Wallet is assumed to be from another chain, if none of
2757+ // its last 6 best known blocks are in the active chain
2758+ // (this heavily relies on the fact that locator stores
2759+ // last 10 blocks consecutively):
2760+ const std::optional<int> fork_height = chain.findLocatorFork(locator);
2761+ if (fork_height && std::distance(locator.vHave.begin(),
For descriptor wallets the case with altcoins is already covered by storing network magic.
My vote goes for simpler code and genesis block hash check.
I tested this today and tried to reproduce the issue with the steps mentioned in this issue: #22792
0error code: -18
1error message:
2Wallet file verification failed. Failed to load database path '/mnt/testnet3/T1'. Data is not in recognized format.
One test is failing in CI: wallet_crosschain.py
I am not sure if this is introduced in this PR. Getting an error when trying to run bitcoind -regtest=1 -datadir=/path
0EXCEPTION: St13runtime_error
1Invalid combination of -regtest, -signet, -testnet and -chain. Can use at most one.
2bitcoin in AppInit()
3
4bitcoind: chainparamsbase.cpp:35: const CBaseChainParams& BaseParams(): Assertion `globalChainBaseParams' failed.
I was unable to reproduce the issue. However, I am getting a different error so I am not sure:
If that is a descriptor wallet, then that would be because the sqlite implementation (only used for descriptor wallets) includes a crosschain protection mechanism already. However legacy wallets don’t, and this PR would allow for that.
One test is failing in CI: wallet_crosschain.py
Indeed. The test is incorrect because all regtest chains use the same genesis even if the rest of the blockchain is different. @rodentrabies You will need to change the test to start a node in testnet or mainnet mode (without syncing of course) in order to test the crosschain protection.
I am not sure if this is introduced in this PR. Getting an error when trying to run
bitcoind -regtest=1 -datadir=/path
I don’t think that is related, you should double check your bitcoin.conf and make sure it does not specify a specific chain to use.
22+ self.setup_nodes()
23+
24+ def run_test(self):
25+ self.log.info("Generating initial blockchains")
26+ self.nodes[0].generate(5)
27+ self.nodes[1].generate(5)
In ece5cd111660005ce71cb8a7092f96c3e084d975 “tests: add tests for cross-chain wallet use prevention”
0 self.generate(self.nodes[0], 5, sync_fun=self.no_op)
1 self.generate(self.nodes[1], 5, sync_fun=self.no_op)
38+ node_2_wallet = os.path.join(self.nodes[2].datadir, 'node_2_wallet')
39+ self.nodes[2].createwallet(node_2_wallet)
40+ self.nodes[2].unloadwallet(node_2_wallet)
41+
42+ self.log.info("Loading wallets into nodes with a different genesis blocks")
43+ assert_raises_rpc_error(-4, 'Wallet files should not be reused across chains.', self.nodes[0].loadwallet, node_1_wallet)
In ece5cd111660005ce71cb8a7092f96c3e084d975 “tests: add tests for cross-chain wallet use prevention”
This assertion is currently not raised. The two wallets are detected as being on the same chain because all regtest nodes use the same genesis block regardless of what comes after.
If that is a descriptor wallet, then that would be because the sqlite implementation (only used for descriptor wallets) includes a crosschain protection mechanism already. However legacy wallets don’t, and this PR would allow for that.
I am not sure if there was some misunderstanding. Not able to reproduce issue in PR branch was a good thing but error was confusing. @rodentrabies its been couple of years and issue is still not fixed. There were some suggestions by @achow101 as well. You tried your best and I appreciate it however I think this can lead to some kind of vulnerability if not fixed soon. Last update was 3 months ago. Is it okay if someone else can work on this pull request?
self.chain
, but without success, so I’ll just have to do that manually.
2917@@ -2918,6 +2918,23 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
2918 assert(!walletInstance->m_chain || walletInstance->m_chain == &chain);
2919 walletInstance->m_chain = &chain;
2920
2921+ // Unless allowed, ensure wallet files are not reused across chains:
2922+ if (!gArgs.GetBoolArg("-walletcrosschain", DEFAULT_WALLETCROSSCHAIN))
0 if (!gArgs.GetBoolArg("-walletcrosschain", DEFAULT_WALLETCROSSCHAIN)) {
nit: no newline after if
if
s on the lines below, but I see now that those are outliers.
Tried testing different things. Two tests worked as expected and 1 failed.
✅ Test 1 :
bitcoind
with PR branchbitcoin-cli createwallet T1
. This creates a descriptor wallet as shared by @achow101 in #18554 (comment) which I understood now after looking at PR #23002 and reading everything again.bitcoind
for mainnet. This already has a protection so gives a different error: Wallet file verification failed. Failed to load database path '/home/prayank/.bitcoin/testnet3/wallets/T1'. Data is not in recognized format.
✅ Test 2:
bitcoind
with PR branchbitcoin-cli -named createwallet -wallet_name="T2" -descriptors=false
. This creates a legacy wallet.bitcoind
for mainnet. This gives an error: Wallet loading failed. Wallet files should not be reused across chains.
❌ Test 3:
bitcoind
with v22.0 binarybitcoin-cli createwallet T3
.0$ bitcoin-cli loadwallet "/home/prayank/.bitcoin/testnet3/wallets/T3"
1{
2 "name": "/home/prayank/.bitcoin/testnet3/wallets/T3",
3 "warning": ""
4}
Just tried this (test 3) and got expected behavior:
0$ ./bitcoin-22.0/bin/bitcoind -testnet -maxconnections=0
1...
2$ ./bitcoin-22.0/bin/bitcoind-cli -testnet createwallet t3
3...
4# stop bitcoind
5...
6$ ./bitcoin/src/bitcoind -maxconnections=0
7...
8$ ./bitcoin/src/bitcoin-cli loadwallet ~/.bitcoin/testnet3/wallets/t3
9error code: -4
10error message:
11Wallet loading failed. Wallet files should not be reused across chains.
Perhaps maxconnections
somehow affects the condition batch.ReadBestBlock(locator) && locator.vHave.size() > 0 && chain.getHeight()
at wallet.cpp:2926
. I’ll investigate.
EDIT: just tried the same without maxconnections
(though before block header synchronization finishes on either of the node instances) and still see the expected behavior.
2927+ {
2928+ // Wallet is assumed to be from another chain, if genesis block in the active
2929+ // chain is differs from the genesis block know to the wallet.
2930+ if (chain.getBlockHash(0) != locator.vHave.back())
2931+ {
2932+ error = _("Wallet files should not be reused across chains.");
Untranslated()
? It is not like this is something that happens in everyday operation?
-walletcrosschain
and wrapped with Untranslated(...)
.
2924+ WalletBatch batch(walletInstance->GetDatabase());
2925+ CBlockLocator locator;
2926+ if (batch.ReadBestBlock(locator) && locator.vHave.size() > 0 && chain.getHeight())
2927+ {
2928+ // Wallet is assumed to be from another chain, if genesis block in the active
2929+ // chain is differs from the genesis block know to the wallet.
0 // chain differs from the genesis block known to the wallet.
Just tried this (test 3) and got expected behavior:
Even I could not reproduce it and getting cross chain error now for a new wallet. Old wallet still loads in mainnet that was created on testnet. I have added the wallet files if that helps: https://github.com/prayank23/bitcoin/blob/test-wallets/T2.zip
Just tried this (test 3) and got expected behavior:
Even I could not reproduce it and getting cross chain error now for a new wallet. Old wallet still loads in mainnet that was created on testnet. I have added the wallet files if that helps: https://github.com/prayank23/bitcoin/blob/test-wallets/T2.zip
Are you sure you didn’t just make a mistake when doing this test? The provided wallet file has a mainnet block locator record and it doesn’t load in testnet with this PR.
Are you sure you didn’t just make a mistake when doing this test? The provided wallet file has a mainnet block locator record and it doesn’t load in testnet with this PR.
Right. Looks like I did something wrong. I think this PR fixes the issue and if I find anything else it can be done in a follow up PR. Its weird that this wallet T2 exists with other testnet wallets in testnet3 directory.
Code Review ACK 5f213213cb17429353ef7ec3e97b185af06d236f
Apropos of nothing: @rodentrabies has quite the fortitude, carrying this PR since 2018