From 411cb968ef0704604fa2edfd7cc0daa45b08698e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98swald=20Kardingson?= <164262873+OswaldKardingson@users.noreply.github.com> Date: Wed, 11 Mar 2026 03:47:00 +0200 Subject: [PATCH 1/3] Store sapling and orchard subtree roots in block index & add z_getsubtreesbyindex rpc --- src/main.cpp | 143 ++++++++++++++++++++++++++++++++++- src/main.h | 1 + src/rpc/blockchain.cpp | 139 ++++++++++++++++++++++++++++++++++ src/rpc/server.h | 1 + src/txdb.cpp | 165 +++++++++++++++++++++++++++++++++++++++++ src/txdb.h | 38 ++++++++++ 6 files changed, 484 insertions(+), 3 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d72ef0e48..f33861ce1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3670,6 +3670,81 @@ CCriticalSection& get_cs_main() return cs_main; } +static bool AlignPersistedShieldedSubtreesForType(ShieldedType type, std::string& error) +{ + AssertLockHeld(cs_main); + + if (pblocktree == nullptr) { + return true; + } + + while (true) { + uint64_t latestIndex = 0; + if (!pblocktree->ReadLatestShieldedSubtreeIndex(type, latestIndex)) { + return true; + } + + ShieldedSubtreeData subtree; + if (!pblocktree->ReadShieldedSubtree(type, latestIndex, subtree)) { + error = strprintf( + "missing persisted subtree metadata for type %d at index %llu", + static_cast(type), + static_cast(latestIndex)); + return false; + } + + const int activeTipHeight = chainActive.Tip() ? chainActive.Tip()->nHeight : -1; + if (subtree.nHeight < 0) { + error = strprintf( + "persisted subtree metadata for type %d has invalid height %d", + static_cast(type), + subtree.nHeight); + return false; + } + + if (subtree.nHeight > activeTipHeight) { + if (!pblocktree->TruncateShieldedSubtrees(type, activeTipHeight)) { + error = strprintf( + "failed to truncate persisted subtree metadata for type %d above active tip height %d", + static_cast(type), + activeTipHeight); + return false; + } + continue; + } + + const CBlockIndex* pindex = chainActive[subtree.nHeight]; + if (pindex == nullptr) { + error = strprintf( + "active chain missing block at subtree completion height %d for type %d", + subtree.nHeight, + static_cast(type)); + return false; + } + + if (pindex->GetBlockHash() != subtree.blockHash) { + const int truncateHeight = subtree.nHeight - 1; + if (!pblocktree->TruncateShieldedSubtrees(type, truncateHeight)) { + error = strprintf( + "failed to rewind persisted subtree metadata for type %d from height %d", + static_cast(type), + subtree.nHeight); + return false; + } + continue; + } + + return true; + } +} + +bool AlignPersistedShieldedSubtrees(std::string& error) +{ + AssertLockHeld(cs_main); + return AlignPersistedShieldedSubtreesForType(SAPLINGFRONTIER, error) && + AlignPersistedShieldedSubtreesForType(ORCHARDFRONTIER, error); +} + /***** * @brief Apply the effects of this block (with given index) on the UTXO set represented by coins * @param block the block to add @@ -3868,6 +3943,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin size_t total_sapling_tx = 0; size_t total_orchard_tx = 0; + std::vector> completedSaplingSubtrees; + std::vector> completedOrchardSubtrees; CAmount chainSupplyDelta = 0; CAmount transparentValueDelta = 0; @@ -4040,15 +4117,47 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin uint256 commitment = uint256::FromRawBytes(output.cmu()); sapling_tree.append(commitment); } - + // Append to frontier tree using bundle - sapling_frontier_tree.AppendBundle(tx.GetSaplingBundle()); + auto saplingResult = sapling_frontier_tree.AppendBundle(tx.GetSaplingBundle()); + if (saplingResult.has_subtree_boundary) { + auto completedIndex = sapling_frontier_tree.current_subtree_index(); + if (completedIndex == 0) { + return state.DoS( + 100, + error("ConnectBlock(): Sapling subtree boundary reached before subtree index advanced"), + REJECT_INVALID, + "bad-sapling-subtree-index"); + } + completedSaplingSubtrees.emplace_back( + completedIndex - 1, + ShieldedSubtreeData( + saplingResult.completed_subtree_root, + pindex->nHeight, + pindex->GetBlockHash())); + } total_sapling_tx += 1; } //Append Orchard Outputs to OrchardMerkleFrontier if (tx.GetOrchardBundle().IsPresent()) { - orchard_frontier_tree.AppendBundle(tx.GetOrchardBundle()); + auto orchardResult = orchard_frontier_tree.AppendBundle(tx.GetOrchardBundle()); + if (orchardResult.has_subtree_boundary) { + auto completedIndex = orchard_frontier_tree.current_subtree_index(); + if (completedIndex == 0) { + return state.DoS( + 100, + error("ConnectBlock(): Orchard subtree boundary reached before subtree index advanced"), + REJECT_INVALID, + "bad-orchard-subtree-index"); + } + completedOrchardSubtrees.emplace_back( + completedIndex - 1, + ShieldedSubtreeData( + orchardResult.completed_subtree_root, + pindex->nHeight, + pindex->GetBlockHash())); + } total_orchard_tx += 1; } @@ -4336,6 +4445,19 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return AbortNode(state, "Failed to write blockhash index"); } + if (!fJustCheck) { + if (!completedSaplingSubtrees.empty()) { + if (!pblocktree->WriteShieldedSubtrees(SAPLINGFRONTIER, completedSaplingSubtrees)) { + return AbortNode(state, "Failed to write Sapling subtree metadata"); + } + } + if (!completedOrchardSubtrees.empty()) { + if (!pblocktree->WriteShieldedSubtrees(ORCHARDFRONTIER, completedOrchardSubtrees)) { + return AbortNode(state, "Failed to write Orchard subtree metadata"); + } + } + } + // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); @@ -4644,6 +4766,13 @@ bool static DisconnectTip(CValidationState &state, bool fBare = false) { // Update chainActive and related variables. UpdateTip(pindexDelete->pprev); + if (!pblocktree->TruncateShieldedSubtrees(SAPLINGFRONTIER, chainActive.Height())) { + return AbortNode(state, "Failed to truncate Sapling subtree metadata"); + } + if (!pblocktree->TruncateShieldedSubtrees(ORCHARDFRONTIER, chainActive.Height())) { + return AbortNode(state, "Failed to truncate Orchard subtree metadata"); + } + // Get the current commitment tree SproutMerkleTree newSproutTree; SaplingMerkleTree newSaplingTree; @@ -7029,6 +7158,14 @@ bool static LoadBlockIndexDB() LOCK(cs_main); chainActive.SetTip(it->second); + { + std::string subtreeError; + if (!AlignPersistedShieldedSubtrees(subtreeError)) { + LogPrintf("Failed to align persisted shielded subtree metadata: %s\n", subtreeError); + return false; + } + } + // Set hashFinalSproutRoot for the end of best chain it->second->hashFinalSproutRoot = pcoinsTip->GetBestAnchor(SPROUT); diff --git a/src/main.h b/src/main.h index 9190df0b5..01cf77f23 100644 --- a/src/main.h +++ b/src/main.h @@ -916,6 +916,7 @@ bool WriteBlockToDisk(const CBlock& block, CDiskBlockPos& pos, const CMessageHea bool ReadBlockFromDisk(CBlock& block, const CDiskBlockPos& pos,bool checkPOW); bool ReadBlockFromDisk(CBlock& block, const CBlockIndex* pindex,bool checkPOW); bool PruneOneBlockFile(bool tempfile, const int fileNumber); +bool AlignPersistedShieldedSubtrees(std::string& error); /** Functions for validating blocks and updating the block tree */ diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index dfeb5f83f..f4b417149 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -46,17 +46,74 @@ #include "komodo_kv.h" #include "komodo_gateway.h" #include "rpc/rawtransaction.h" +#include "txdb.h" #include #include +#include +#include +#include #include +#include #include "cc/CCinclude.h" using namespace std; +namespace { + +enum class ShieldedSubtreeProtocol { + Sapling, + Orchard, +}; + +ShieldedSubtreeProtocol ParseShieldedSubtreeProtocol(const UniValue& value) +{ + std::string protocol = value.get_str(); + std::transform(protocol.begin(), protocol.end(), protocol.begin(), [](unsigned char c) { + return std::tolower(c); + }); + + if (protocol == "sapling") { + return ShieldedSubtreeProtocol::Sapling; + } + if (protocol == "orchard") { + return ShieldedSubtreeProtocol::Orchard; + } + + throw JSONRPCError(RPC_INVALID_PARAMETER, "shielded protocol must be \"sapling\" or \"orchard\""); +} + +uint64_t ParseNonNegativeUint64(const UniValue& value, const std::string& fieldName) +{ + if (!(value.isNum() || value.isStr())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, fieldName + " must be a non-negative integer"); + } + + int64_t parsed = value.get_int64(); + if (parsed < 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, fieldName + " must be a non-negative integer"); + } + + return static_cast(parsed); +} + +ShieldedType ToShieldedType(ShieldedSubtreeProtocol protocol) +{ + switch (protocol) { + case ShieldedSubtreeProtocol::Sapling: + return SAPLINGFRONTIER; + case ShieldedSubtreeProtocol::Orchard: + return ORCHARDFRONTIER; + } + + throw JSONRPCError(RPC_INVALID_PARAMETER, "unknown shielded protocol"); +} + +} // namespace + // TODO: remove //extern int32_t KOMODO_INSYNC; //extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); @@ -2335,6 +2392,87 @@ UniValue z_gettreestatelegacy(const UniValue& params, bool fHelp, const CPubKey& return res; } +UniValue z_getsubtreesbyindex(const UniValue& params, bool fHelp, const CPubKey& mypk) +{ + if (fHelp || params.size() < 2 || params.size() > 3) + throw runtime_error( + "z_getsubtreesbyindex \"sapling|orchard\" start_index ( limit )\n" + "Return roots of completed shielded 2^16 subtrees from the active chain.\n" + "\nArguments:\n" + "1. \"sapling|orchard\" (string, required) Shielded protocol to query\n" + "2. start_index (numeric, required) Subtree index to start from\n" + "3. limit (numeric, optional, default=0) Maximum number of subtrees to return, or 0 for all\n" + "\nResult:\n" + "[\n" + " {\n" + " \"index\": n, (numeric) subtree index\n" + " \"root\": \"hex\", (string) subtree root hash\n" + " \"completingBlockHash\": \"hex\",(string) block hash that completed the subtree\n" + " \"completingBlockHeight\": n (numeric) block height that completed the subtree\n" + " }\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("z_getsubtreesbyindex", "\"sapling\" 0 10") + + HelpExampleRpc("z_getsubtreesbyindex", "\"sapling\", 0, 10") + ); + + ShieldedSubtreeProtocol protocol = ParseShieldedSubtreeProtocol(params[0]); + uint64_t startIndex = ParseNonNegativeUint64(params[1], "start_index"); + uint64_t limit = params.size() == 3 ? ParseNonNegativeUint64(params[2], "limit") : 0; + if (limit > std::numeric_limits::max()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "limit exceeds uint32 range"); + } + + LOCK(cs_main); + + std::string subtreeError; + if (!AlignPersistedShieldedSubtrees(subtreeError)) { + throw JSONRPCError(RPC_DATABASE_ERROR, subtreeError); + } + + std::vector> subtrees; + if (!pblocktree->ReadShieldedSubtrees(ToShieldedType(protocol), startIndex, static_cast(limit), subtrees)) { + throw JSONRPCError(RPC_DATABASE_ERROR, "Failed to read persisted subtree metadata"); + } + UniValue result(UniValue::VARR); + + for (const auto& it : subtrees) { + const uint64_t subtreeIndex = it.first; + const ShieldedSubtreeData& subtree = it.second; + if (subtree.nHeight < 0) { + throw JSONRPCError( + RPC_DATABASE_ERROR, + strprintf( + "subtree index %llu has invalid completion height %d", + static_cast(subtreeIndex), + subtree.nHeight)); + } + const CBlockIndex* completingBlock = chainActive[subtree.nHeight]; + if (completingBlock == nullptr) { + throw JSONRPCError( + RPC_DATABASE_ERROR, + strprintf("missing active block index at subtree completion height %d", subtree.nHeight)); + } + if (completingBlock->GetBlockHash() != subtree.blockHash) { + throw JSONRPCError( + RPC_DATABASE_ERROR, + strprintf( + "persisted subtree metadata mismatch at index %llu and height %d", + static_cast(subtreeIndex), + subtree.nHeight)); + } + + UniValue entry(UniValue::VOBJ); + entry.push_back(Pair("index", static_cast(subtreeIndex))); + entry.push_back(Pair("root", HexStr(subtree.root.begin(), subtree.root.end()))); + entry.push_back(Pair("completingBlockHash", subtree.blockHash.GetHex())); + entry.push_back(Pair("completingBlockHeight", static_cast(subtree.nHeight))); + result.push_back(entry); + } + + return result; +} + /** * @brief Convert memory pool information to JSON format * @return JSON object containing memory pool statistics @@ -2576,6 +2714,7 @@ static const CRPCCommand commands[] = { "blockchain", "getchaintips", &getchaintips, true }, { "blockchain", "z_gettreestate", &z_gettreestate, true }, { "blockchain", "z_gettreestatelegacy", &z_gettreestatelegacy, true }, + { "blockchain", "z_getsubtreesbyindex", &z_getsubtreesbyindex, true }, { "blockchain", "getchaintxstats", &getchaintxstats, true }, { "blockchain", "getdifficulty", &getdifficulty, true }, { "blockchain", "getmempoolinfo", &getmempoolinfo, true }, diff --git a/src/rpc/server.h b/src/rpc/server.h index 52ad2499c..75b6a04de 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -437,6 +437,7 @@ extern UniValue verifychain(const UniValue& params, bool fHelp, const CPubKey& m extern UniValue getchaintips(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue z_gettreestate(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue z_gettreestatelegacy(const UniValue& params, bool fHelp, const CPubKey& mypk); +extern UniValue z_getsubtreesbyindex(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue invalidateblock(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue reconsiderblock(const UniValue& params, bool fHelp, const CPubKey& mypk); extern UniValue getspentinfo(const UniValue& params, bool fHelp, const CPubKey& mypk); diff --git a/src/txdb.cpp b/src/txdb.cpp index 08304eb16..692e5ecea 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -63,6 +63,10 @@ static const char DB_TIMESTAMPINDEX = 'H'; static const char DB_BLOCKHASHINDEX = 'h'; static const char DB_SPENTINDEX = 'p'; static const char DB_BLOCK_INDEX = 'b'; +static const char DB_SAPLING_SUBTREE = 'j'; +static const char DB_ORCHARD_SUBTREE = 'J'; +static const char DB_BEST_SAPLING_SUBTREE = 'k'; +static const char DB_BEST_ORCHARD_SUBTREE = 'K'; //History Node static const char DB_MMR_LENGTH = 'M'; @@ -81,6 +85,34 @@ static const char OUTPUT_PROOF_HASH = 'E'; static const char DB_VERSION = 'V'; +namespace { + +char ShieldedSubtreeEntryKey(ShieldedType type) +{ + switch (type) { + case SAPLINGFRONTIER: + return DB_SAPLING_SUBTREE; + case ORCHARDFRONTIER: + return DB_ORCHARD_SUBTREE; + default: + throw runtime_error("Unknown shielded type for subtree metadata"); + } +} + +char ShieldedSubtreeBestKey(ShieldedType type) +{ + switch (type) { + case SAPLINGFRONTIER: + return DB_BEST_SAPLING_SUBTREE; + case ORCHARDFRONTIER: + return DB_BEST_ORCHARD_SUBTREE; + default: + throw runtime_error("Unknown shielded type for subtree metadata"); + } +} + +} // namespace + CCoinsViewDB::CCoinsViewDB(std::string dbName, size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / dbName, nCacheSize, fMemory, fWipe) { } @@ -858,6 +890,139 @@ bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) const { return true; } +bool CBlockTreeDB::WriteShieldedSubtrees( + ShieldedType type, + const std::vector>& subtrees) +{ + if (subtrees.empty()) { + return true; + } + + uint64_t latestIndex = 0; + bool hasLatest = ReadLatestShieldedSubtreeIndex(type, latestIndex); + uint64_t expectedIndex = hasLatest ? latestIndex + 1 : 0; + for (const auto& it : subtrees) { + if (it.first != expectedIndex) { + LogPrintf( + "%s: subtree metadata discontinuity for type=%d expected=%llu actual=%llu\n", + __func__, + static_cast(type), + static_cast(expectedIndex), + static_cast(it.first)); + return false; + } + expectedIndex++; + } + + const char entryKey = ShieldedSubtreeEntryKey(type); + const char bestKey = ShieldedSubtreeBestKey(type); + CDBBatch batch(*this); + for (const auto& it : subtrees) { + batch.Write(std::make_pair(entryKey, it.first), it.second); + } + batch.Write(bestKey, subtrees.back().first); + return WriteBatch(batch, true); +} + +bool CBlockTreeDB::ReadShieldedSubtrees( + ShieldedType type, + uint64_t startIndex, + uint32_t limit, + std::vector>& subtrees) const +{ + subtrees.clear(); + + boost::scoped_ptr pcursor(NewIterator()); + const char entryKey = ShieldedSubtreeEntryKey(type); + pcursor->Seek(std::make_pair(entryKey, startIndex)); + + uint64_t expectedIndex = startIndex; + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + try { + std::pair keyObj; + pcursor->GetKey(keyObj); + if (keyObj.first != entryKey) { + break; + } + if (keyObj.second != expectedIndex) { + LogPrintf( + "%s: subtree metadata gap for type=%d expected=%llu actual=%llu\n", + __func__, + static_cast(type), + static_cast(expectedIndex), + static_cast(keyObj.second)); + return false; + } + + ShieldedSubtreeData subtree; + if (!pcursor->GetValue(subtree)) { + return false; + } + subtrees.push_back(std::make_pair(keyObj.second, subtree)); + expectedIndex++; + if (limit != 0 && subtrees.size() >= limit) { + break; + } + pcursor->Next(); + } catch (const std::exception&) { + return false; + } + } + + return true; +} + +bool CBlockTreeDB::ReadShieldedSubtree( + ShieldedType type, + uint64_t index, + ShieldedSubtreeData& subtree) const +{ + return Read(std::make_pair(ShieldedSubtreeEntryKey(type), index), subtree); +} + +bool CBlockTreeDB::ReadLatestShieldedSubtreeIndex(ShieldedType type, uint64_t& index) const +{ + return Read(ShieldedSubtreeBestKey(type), index); +} + +bool CBlockTreeDB::TruncateShieldedSubtrees(ShieldedType type, int newTipHeight) +{ + uint64_t latestIndex = 0; + if (!ReadLatestShieldedSubtreeIndex(type, latestIndex)) { + return true; + } + + const char entryKey = ShieldedSubtreeEntryKey(type); + const char bestKey = ShieldedSubtreeBestKey(type); + CDBBatch batch(*this); + bool changed = false; + while (true) { + ShieldedSubtreeData subtree; + if (!Read(std::make_pair(entryKey, latestIndex), subtree)) { + return false; + } + if (subtree.nHeight <= newTipHeight) { + break; + } + + batch.Erase(std::make_pair(entryKey, latestIndex)); + changed = true; + if (latestIndex == 0) { + batch.Erase(bestKey); + return WriteBatch(batch, true); + } + latestIndex--; + } + + if (!changed) { + return true; + } + + batch.Write(bestKey, latestIndex); + return WriteBatch(batch, true); +} + void komodo_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height); bool CBlockTreeDB::blockOnchainActive(const uint256 &hash) { diff --git a/src/txdb.h b/src/txdb.h index 2f4c9862a..093563cea 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -55,6 +55,30 @@ static const int64_t nMaxDbCache = sizeof(void*) > 4 ? 16384 : 1024; //! min. -dbcache in (MiB) static const int64_t nMinDbCache = 4; +class ShieldedSubtreeData +{ +public: + uint8_t leadbyte = 0x00; + libzcash::SubtreeRoot root; + int nHeight; + uint256 blockHash; + + ShieldedSubtreeData() : nHeight(0) {} + + ShieldedSubtreeData(libzcash::SubtreeRoot root, int nHeight, uint256 blockHash) + : root(root), nHeight(nHeight), blockHash(blockHash) {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(leadbyte); + READWRITE(root); + READWRITE(nHeight); + READWRITE(blockHash); + } +}; + /** * CCoinsView backed by the coin database (chainstate/) */ @@ -291,6 +315,20 @@ class CBlockTreeDB : public CDBWrapper * @returns true on success */ bool ReadFlag(const std::string &name, bool &fValue) const; + bool WriteShieldedSubtrees( + ShieldedType type, + const std::vector>& subtrees); + bool ReadShieldedSubtrees( + ShieldedType type, + uint64_t startIndex, + uint32_t limit, + std::vector>& subtrees) const; + bool ReadShieldedSubtree( + ShieldedType type, + uint64_t index, + ShieldedSubtreeData& subtree) const; + bool ReadLatestShieldedSubtreeIndex(ShieldedType type, uint64_t& index) const; + bool TruncateShieldedSubtrees(ShieldedType type, int newTipHeight); /**** * Load the block headers from disk * NOTE: this does no consistency check beyond verifying records exist From 334f3da42f3deb055aca5e14eaf6812eb4ce477e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98swald=20Kardingson?= <164262873+OswaldKardingson@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:29:09 +0200 Subject: [PATCH 2/3] Fix subtree metadata writes during verifydb & const db iteration --- src/main.cpp | 5 ++++- src/txdb.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f33861ce1..45411ff2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3937,6 +3937,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin OrchardMerkleFrontier orchard_frontier_tree; assert(view.GetOrchardFrontierAnchorAt(view.GetBestAnchor(ORCHARDFRONTIER), orchard_frontier_tree)); + const bool persistShieldedSubtreeMetadata = + !fJustCheck && pcoinsTip != nullptr && view.GetBestBlock() == pcoinsTip->GetBestBlock(); + // Grab the consensus branch ID for this block and its parent auto consensusBranchId = CurrentEpochBranchId(pindex->nHeight, chainparams.GetConsensus()); auto prevConsensusBranchId = CurrentEpochBranchId(pindex->nHeight - 1, chainparams.GetConsensus()); @@ -4445,7 +4448,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return AbortNode(state, "Failed to write blockhash index"); } - if (!fJustCheck) { + if (persistShieldedSubtreeMetadata) { if (!completedSaplingSubtrees.empty()) { if (!pblocktree->WriteShieldedSubtrees(SAPLINGFRONTIER, completedSaplingSubtrees)) { return AbortNode(state, "Failed to write Sapling subtree metadata"); diff --git a/src/txdb.cpp b/src/txdb.cpp index 692e5ecea..58fd2119d 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -568,7 +568,7 @@ bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector > &unspentOutputs) { - boost::scoped_ptr pcursor(NewIterator()); + boost::scoped_ptr pcursor(const_cast(this)->NewIterator()); pcursor->Seek(make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, addressHash))); From d39e72793ba8f2c4fbeb3cc0c512f2f96a1c452c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98swald=20Kardingson?= <164262873+OswaldKardingson@users.noreply.github.com> Date: Wed, 11 Mar 2026 04:51:12 +0200 Subject: [PATCH 3/3] Fix const iterator use in subtree metadata db reads --- src/txdb.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/txdb.cpp b/src/txdb.cpp index 58fd2119d..572cb2a8d 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -568,7 +568,7 @@ bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector > &unspentOutputs) { - boost::scoped_ptr pcursor(const_cast(this)->NewIterator()); + boost::scoped_ptr pcursor(NewIterator()); pcursor->Seek(make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, addressHash))); @@ -932,7 +932,7 @@ bool CBlockTreeDB::ReadShieldedSubtrees( { subtrees.clear(); - boost::scoped_ptr pcursor(NewIterator()); + boost::scoped_ptr pcursor(const_cast(this)->NewIterator()); const char entryKey = ShieldedSubtreeEntryKey(type); pcursor->Seek(std::make_pair(entryKey, startIndex));