wallet: take note heights from the wallet's depth, not tx_height()
For minDepth > 1, GetFilteredNotes located each wallet tx's block with tx_height(), which calls GetTransaction and re-reads the tx from disk through the txindex. It did this once per wallet tx, transparent ones included, while holding LOCK2. z_listunspent and z_listreceivedbyaddress did it once more per returned note. Without -txindex, GetTransaction can only find a tx that still has unspent transparent outputs. tx_height() then returned 0 for every fully shielded tx, and the tx was skipped. So on a -txindex=0 node, sweep and consolidation (minDepth 11) and any minconf >= 2 call never saw those notes. The wallet already has the depth, and for an active-chain tx GetDepthInMainChain is chainActive.Height() - height + 1. The new HeightFromDepth() inverts that (0 when unconfirmed or off the active chain) and replaces tx_height() at all three sites. The height only feeds the == 0 skip and hush_dpowconfs. Both sides hold cs_main for the whole call, so the tip can't move between the walk and the RPC tail. On a -txindex node the results are identical: the txindex block and wtx.hashBlock are the same block. The extended equivalence test now also covers minDepth 2/11/12/22. On -txindex=0 nodes, fully shielded notes now show up for minDepth > 1. With -zsweep or -consolidation enabled, those notes will now be swept and consolidated, so init logs that once. MinDepthAboveOneUsesWalletHeight pins the new behaviour and shows that the old path returned nothing without a txindex. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "zcash/zip32.h"
|
||||
|
||||
int32_t hush_dpowconfs(int32_t height, int32_t numconfs);
|
||||
int tx_height(const uint256 &hash);
|
||||
|
||||
using namespace libzcash;
|
||||
|
||||
@@ -191,7 +192,7 @@ void ExpectSameEntries(const std::vector<SaplingNoteEntry>& want, const std::vec
|
||||
// TearDown, because every gtest suite runs in the same process.
|
||||
class WalletNoteCache : public ::testing::Test {
|
||||
protected:
|
||||
static const int TIP_HEIGHT = 30;
|
||||
static constexpr int TIP_HEIGHT = 30;
|
||||
|
||||
CWallet wallet;
|
||||
SaplingPaymentAddress paA, paB, paC; // A and C spendable, B viewing key only
|
||||
@@ -361,10 +362,39 @@ TEST_F(WalletNoteCache, FixtureExercisesEveryFilter) {
|
||||
}
|
||||
}
|
||||
|
||||
// The walk returns exactly what the 69bb035e7 loop returned, in the same order, for every filter
|
||||
// combination.
|
||||
// The walk returns exactly what the 69bb035e7 loop returned on a -txindex node, in the same order,
|
||||
// for every filter combination. minDepth 11/12/22 sit on the fixture's depth-11 and depth-21 txs.
|
||||
TEST_F(WalletNoteCache, FilteredNotesMatchLegacy) {
|
||||
ExpectEquivalent({0, 1});
|
||||
ExpectEquivalent({0, 1, 2, 11, 12, 22});
|
||||
}
|
||||
|
||||
// minDepth > 1 (sweep and consolidation use 11) locates each tx's block from its depth. The old
|
||||
// tx_height() re-read the tx through the txindex, and without one (as here, and on a -txindex=0
|
||||
// node) it never found a fully shielded tx, so nothing was returned at all.
|
||||
TEST_F(WalletNoteCache, MinDepthAboveOneUsesWalletHeight) {
|
||||
// Unspent notes at depth >= 11: tx3's 3 (depth 11) and tx4's 1 (depth 21); tx6 (12) is spent.
|
||||
EXPECT_EQ(4u, Filtered({}, 11, INT_MAX, true, false, false).size());
|
||||
EXPECT_EQ(5u, Filtered({}, 11, INT_MAX, false, false, false).size());
|
||||
EXPECT_EQ(1u, Filtered({}, 12, INT_MAX, true, false, false).size()) << "depth 11 is below minDepth 12";
|
||||
EXPECT_EQ(3u, Filtered({}, 11, 11, true, false, false).size()) << "depth 21 is above maxDepth 11";
|
||||
EXPECT_EQ(0u, Filtered({}, 22, INT_MAX, false, false, false).size());
|
||||
for (const SaplingNoteEntry& e : Filtered({}, 2, INT_MAX, false, false, false))
|
||||
EXPECT_GE(e.confirmations, 2) << "unmined and shallower txs stay out";
|
||||
|
||||
std::set<PaymentAddress> none;
|
||||
std::vector<SaplingNoteEntry> legacy;
|
||||
LegacyFilteredNotes(wallet, legacy, none, 11, INT_MAX, true, false, false, tx_height);
|
||||
EXPECT_EQ(0u, legacy.size()) << "the old code found nothing here without a txindex";
|
||||
}
|
||||
|
||||
TEST_F(WalletNoteCache, HeightFromDepth) {
|
||||
LOCK(cs_main);
|
||||
EXPECT_EQ(TIP_HEIGHT, HeightFromDepth(1));
|
||||
EXPECT_EQ(TIP_HEIGHT - 10, HeightFromDepth(11));
|
||||
EXPECT_EQ(0, HeightFromDepth(0)) << "mempool";
|
||||
EXPECT_EQ(0, HeightFromDepth(-1)) << "not in the chain or mempool";
|
||||
for (const auto& d : depthOf)
|
||||
EXPECT_EQ(minedHeight.at(d.first), HeightFromDepth(wallet.mapWallet.at(d.first).GetDepthInMainChain()));
|
||||
}
|
||||
|
||||
// Two notes recorded under A's viewing key that it can't actually decrypt (real outputs to someone
|
||||
|
||||
Reference in New Issue
Block a user