From 733cdbca3bf13ce5b9d92dfa77da48f1a67c6679 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 11 Aug 2025 15:42:04 +0300 Subject: [PATCH 01/11] feat: introduce `CDeterministicMNStateDiffLegacy` class with original field positions and conversion to new format --- src/evo/dmnstate.h | 136 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 9d2f45f3eb9d..6e0415ccd89f 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -35,6 +35,7 @@ class CDeterministicMNState int nPoSeBanHeight{-1}; friend class CDeterministicMNStateDiff; + friend class CDeterministicMNStateDiffLegacy; public: int nVersion{ProTxVersion::LegacyBLS}; @@ -284,4 +285,139 @@ class CDeterministicMNStateDiff } }; +// Legacy deserializer class +class CDeterministicMNStateDiffLegacy +{ +private: + // Legacy field enum with original bit positions + enum LegacyField : uint32_t { + LegacyField_nRegisteredHeight = 0x0001, + LegacyField_nLastPaidHeight = 0x0002, + LegacyField_nPoSePenalty = 0x0004, + LegacyField_nPoSeRevivedHeight = 0x0008, + LegacyField_nPoSeBanHeight = 0x0010, + LegacyField_nRevocationReason = 0x0020, + LegacyField_confirmedHash = 0x0040, + LegacyField_confirmedHashWithProRegTxHash = 0x0080, + LegacyField_keyIDOwner = 0x0100, + LegacyField_pubKeyOperator = 0x0200, + LegacyField_keyIDVoting = 0x0400, + LegacyField_netInfo = 0x0800, + LegacyField_scriptPayout = 0x1000, + LegacyField_scriptOperatorPayout = 0x2000, + LegacyField_nConsecutivePayments = 0x4000, + LegacyField_platformNodeID = 0x8000, + LegacyField_platformP2PPort = 0x10000, + LegacyField_platformHTTPPort = 0x20000, + LegacyField_nVersion = 0x40000, + }; + + // Legacy member template with old bit positions + template + struct LegacyMember { + static constexpr uint32_t mask = Mask; + static auto& get(CDeterministicMNState& state) { return state.*Field; } + static const auto& get(const CDeterministicMNState& state) { return state.*Field; } + }; + +#define LEGACY_DMN_STATE_MEMBER(name) \ + LegacyMember<&CDeterministicMNState::name, LegacyField_##name> {} + static constexpr auto legacy_members = boost::hana::make_tuple( + LEGACY_DMN_STATE_MEMBER(nRegisteredHeight), + LEGACY_DMN_STATE_MEMBER(nLastPaidHeight), + LEGACY_DMN_STATE_MEMBER(nPoSePenalty), + LEGACY_DMN_STATE_MEMBER(nPoSeRevivedHeight), + LEGACY_DMN_STATE_MEMBER(nPoSeBanHeight), + LEGACY_DMN_STATE_MEMBER(nRevocationReason), + LEGACY_DMN_STATE_MEMBER(confirmedHash), + LEGACY_DMN_STATE_MEMBER(confirmedHashWithProRegTxHash), + LEGACY_DMN_STATE_MEMBER(keyIDOwner), + LEGACY_DMN_STATE_MEMBER(pubKeyOperator), + LEGACY_DMN_STATE_MEMBER(keyIDVoting), + LEGACY_DMN_STATE_MEMBER(netInfo), + LEGACY_DMN_STATE_MEMBER(scriptPayout), + LEGACY_DMN_STATE_MEMBER(scriptOperatorPayout), + LEGACY_DMN_STATE_MEMBER(nConsecutivePayments), + LEGACY_DMN_STATE_MEMBER(platformNodeID), + LEGACY_DMN_STATE_MEMBER(platformP2PPort), + LEGACY_DMN_STATE_MEMBER(platformHTTPPort), + LEGACY_DMN_STATE_MEMBER(nVersion) + ); +#undef LEGACY_DMN_STATE_MEMBER + +public: + uint32_t fields{0}; + CDeterministicMNState state; + + CDeterministicMNStateDiffLegacy() = default; + + template + CDeterministicMNStateDiffLegacy(deserialize_type, Stream& s) { s >> *this; } + + // Deserialize using legacy format + SERIALIZE_METHODS(CDeterministicMNStateDiffLegacy, obj) + { + READWRITE(VARINT(obj.fields)); + + bool read_pubkey{false}; + boost::hana::for_each(legacy_members, [&](auto&& member) { + using BaseType = std::decay_t; + if constexpr (BaseType::mask == LegacyField_pubKeyOperator) { + if (obj.fields & member.mask) { + SER_READ(obj, read_pubkey = true); + READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.state.pubKeyOperator), + obj.state.nVersion == ProTxVersion::LegacyBLS)); + } + } else if constexpr (BaseType::mask == LegacyField_netInfo) { + if (obj.fields & member.mask) { + // Legacy format supports non-extended addresses only + READWRITE(NetInfoSerWrapper(const_cast&>(obj.state.netInfo), false)); + } + } else { + if (obj.fields & member.mask) { + READWRITE(member.get(obj.state)); + } + } + }); + + if (read_pubkey) { + SER_READ(obj, obj.fields |= LegacyField_nVersion); + SER_READ(obj, obj.state.pubKeyOperator.SetLegacy(obj.state.nVersion == ProTxVersion::LegacyBLS)); + } + } + + // Convert to new format + CDeterministicMNStateDiff ToNewFormat() const + { + CDeterministicMNStateDiff newDiff; + newDiff.state = state; + + // Convert field bits to new positions +#define LEGACY_DMN_STATE_BITS(name) \ + if (fields & LegacyField_##name) newDiff.fields |= CDeterministicMNStateDiff::Field_##name; + LEGACY_DMN_STATE_BITS(nVersion) + LEGACY_DMN_STATE_BITS(nRegisteredHeight) + LEGACY_DMN_STATE_BITS(nLastPaidHeight) + LEGACY_DMN_STATE_BITS(nPoSePenalty) + LEGACY_DMN_STATE_BITS(nPoSeRevivedHeight) + LEGACY_DMN_STATE_BITS(nPoSeBanHeight) + LEGACY_DMN_STATE_BITS(nRevocationReason) + LEGACY_DMN_STATE_BITS(confirmedHash) + LEGACY_DMN_STATE_BITS(confirmedHashWithProRegTxHash) + LEGACY_DMN_STATE_BITS(keyIDOwner) + LEGACY_DMN_STATE_BITS(pubKeyOperator) + LEGACY_DMN_STATE_BITS(keyIDVoting) + LEGACY_DMN_STATE_BITS(netInfo) + LEGACY_DMN_STATE_BITS(scriptPayout) + LEGACY_DMN_STATE_BITS(scriptOperatorPayout) + LEGACY_DMN_STATE_BITS(nConsecutivePayments) + LEGACY_DMN_STATE_BITS(platformNodeID) + LEGACY_DMN_STATE_BITS(platformP2PPort) + LEGACY_DMN_STATE_BITS(platformHTTPPort) +#undef LEGACY_DMN_STATE_BITS + + return newDiff; + } +}; + #endif // BITCOIN_EVO_DMNSTATE_H From bdb0d000bd983655a6e9247663d8c8eae18a9032 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 11 Aug 2025 15:44:23 +0300 Subject: [PATCH 02/11] feat: move `nVersion` from `0x40000` to `0x0001` (first position) in `CDeterministicMNStateDiff` --- src/evo/dmnstate.h | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 6e0415ccd89f..228e0e658f9d 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -154,25 +154,25 @@ class CDeterministicMNStateDiff { public: enum Field : uint32_t { - Field_nRegisteredHeight = 0x0001, - Field_nLastPaidHeight = 0x0002, - Field_nPoSePenalty = 0x0004, - Field_nPoSeRevivedHeight = 0x0008, - Field_nPoSeBanHeight = 0x0010, - Field_nRevocationReason = 0x0020, - Field_confirmedHash = 0x0040, - Field_confirmedHashWithProRegTxHash = 0x0080, - Field_keyIDOwner = 0x0100, - Field_pubKeyOperator = 0x0200, - Field_keyIDVoting = 0x0400, - Field_netInfo = 0x0800, - Field_scriptPayout = 0x1000, - Field_scriptOperatorPayout = 0x2000, - Field_nConsecutivePayments = 0x4000, - Field_platformNodeID = 0x8000, - Field_platformP2PPort = 0x10000, - Field_platformHTTPPort = 0x20000, - Field_nVersion = 0x40000, + Field_nVersion = 0x0001, + Field_nRegisteredHeight = 0x0002, + Field_nLastPaidHeight = 0x0004, + Field_nPoSePenalty = 0x0008, + Field_nPoSeRevivedHeight = 0x0010, + Field_nPoSeBanHeight = 0x0020, + Field_nRevocationReason = 0x0040, + Field_confirmedHash = 0x0080, + Field_confirmedHashWithProRegTxHash = 0x0100, + Field_keyIDOwner = 0x0200, + Field_pubKeyOperator = 0x0400, + Field_keyIDVoting = 0x0800, + Field_netInfo = 0x1000, + Field_scriptPayout = 0x2000, + Field_scriptOperatorPayout = 0x4000, + Field_nConsecutivePayments = 0x8000, + Field_platformNodeID = 0x10000, + Field_platformP2PPort = 0x20000, + Field_platformHTTPPort = 0x40000, }; private: @@ -186,6 +186,7 @@ class CDeterministicMNStateDiff #define DMN_STATE_MEMBER(name) Member<&CDeterministicMNState::name, Field_##name>{} static constexpr auto members = boost::hana::make_tuple( + DMN_STATE_MEMBER(nVersion), DMN_STATE_MEMBER(nRegisteredHeight), DMN_STATE_MEMBER(nLastPaidHeight), DMN_STATE_MEMBER(nPoSePenalty), @@ -203,8 +204,7 @@ class CDeterministicMNStateDiff DMN_STATE_MEMBER(nConsecutivePayments), DMN_STATE_MEMBER(platformNodeID), DMN_STATE_MEMBER(platformP2PPort), - DMN_STATE_MEMBER(platformHTTPPort), - DMN_STATE_MEMBER(nVersion) + DMN_STATE_MEMBER(platformHTTPPort) ); #undef DMN_STATE_MEMBER From f7963d44ae12f808673dfb89fae68ad0ebab84be Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 11 Aug 2025 15:45:21 +0300 Subject: [PATCH 03/11] feat: implement EvoDB migration logic --- src/evo/deterministicmns.cpp | 128 ++++++++++++++++++++++++++++++++++- src/evo/deterministicmns.h | 32 ++++++++- src/init.cpp | 3 + src/node/chainstate.cpp | 5 ++ src/node/chainstate.h | 1 + 5 files changed, 165 insertions(+), 4 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 9202f1a4eb2d..835d64ad3991 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -27,7 +27,8 @@ #include static const std::string DB_LIST_SNAPSHOT = "dmn_S3"; -static const std::string DB_LIST_DIFF = "dmn_D3"; +static const std::string DB_LIST_DIFF = "dmn_D4"; // Bumped for nVersion-first format +static const std::string DB_LIST_DIFF_LEGACY = "dmn_D3"; // Legacy format key uint64_t CDeterministicMN::GetInternalId() const { @@ -1336,3 +1337,128 @@ void CDeterministicMNManager::DoMaintenance() { CleanupCache(loc_to_cleanup); did_cleanup = loc_to_cleanup; } + +bool CDeterministicMNManager::IsMigrationRequired() const +{ + // Check if there are any legacy format diffs in the database + // by looking for DB_LIST_DIFF_LEGACY entries + LOCK(::cs_main); + + std::unique_ptr pcursor{m_evoDb.GetRawDB().NewIterator()}; + auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256())}; + pcursor->Seek(start); + + // If we find any entries with the legacy key, migration is needed + if (pcursor->Valid()) { + decltype(start) k; + if (pcursor->GetKey(k) && std::get<0>(k) == DB_LIST_DIFF_LEGACY) { + LogPrintf("CDeterministicMNManager::%s -- Migration to nVersion-first format is needed\n", __func__); + pcursor.reset(); + return true; + } + } + + LogPrintf("CDeterministicMNManager::%s -- Migration to nVersion-first format is not needed\n", __func__); + pcursor.reset(); + return false; // No legacy format found +} + +bool CDeterministicMNManager::MigrateLegacyDiffs() +{ + // CRITICAL: This migration converts ALL stored CDeterministicMNListDiff entries + // from legacy database key (DB_LIST_DIFF_LEGACY) to new key (DB_LIST_DIFF) format + + LogPrintf("CDeterministicMNManager::%s -- Starting migration to nVersion-first format\n", __func__); + LOCK(::cs_main); + + std::vector keys_to_erase; + + CDBBatch batch(m_evoDb.GetRawDB()); + std::unique_ptr pcursor{m_evoDb.GetRawDB().NewIterator()}; + auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256())}; + pcursor->Seek(start); + + while (pcursor->Valid()) { + decltype(start) k; + if (!pcursor->GetKey(k) || std::get<0>(k) != DB_LIST_DIFF_LEGACY) { + break; + } + + // Use legacy-aware deserialization for DB_LIST_DIFF_LEGACY entries + CDataStream s(SER_DISK, CLIENT_VERSION); + if (!m_evoDb.GetRawDB().ReadDataStream(k, s)) { + break; + } + + CDeterministicMNListDiff legacyDiff; + legacyDiff.UnserializeLegacyFormat(s); // Use legacy format deserializer + + CDeterministicMNListDiff convertedDiff; + convertedDiff.nHeight = legacyDiff.nHeight; + convertedDiff.addedMNs = legacyDiff.addedMNs; + convertedDiff.removedMns = legacyDiff.removedMns; + + // The conversion is already done by UnserializeLegacyFormat()! + // CDeterministicMNStateDiffLegacy.ToNewFormat() was called during deserialization + // So legacyDiff.updatedMNs already contains properly converted CDeterministicMNStateDiff objects + + // Simply copy the already-converted state diffs + for (const auto& [internalId, stateDiff] : legacyDiff.updatedMNs) { + convertedDiff.updatedMNs.emplace(internalId, stateDiff); + } + + // Write the converted diff to new database key + batch.Write(std::make_pair(DB_LIST_DIFF, std::get<1>(k)), convertedDiff); + keys_to_erase.push_back(std::get<1>(k)); + + if (batch.SizeEstimate() >= (1 << 24)) { + LogPrintf("CDeterministicMNManager::%s -- Writing new diffs...\n", __func__); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + } + + pcursor->Next(); + } + pcursor.reset(); + + LogPrintf("CDeterministicMNManager::%s -- Writing new diffs...\n", __func__); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + + LogPrintf("CDeterministicMNManager::%s -- Erasing %d legacy database entries after successful migration\n", + __func__, keys_to_erase.size()); + + // Delete all legacy format entries + for (const auto& blockHash : keys_to_erase) { + batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, blockHash)); + + if (batch.SizeEstimate() >= (1 << 24)) { + LogPrintf("CDeterministicMNManager::%s -- Erasing legacy diffs...\n", __func__); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + } + } + + LogPrintf("CDeterministicMNManager::%s -- Erasing legacy diffs...\n", __func__); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + + LogPrintf("CDeterministicMNManager::%s -- Compacting database...\n", __func__); + m_evoDb.GetRawDB().CompactFull(); + + // flush it to disk + if (!m_evoDb.CommitRootTransaction()) { + LogPrintf("CDeterministicMNManager::%s -- Failed to commit to evoDB\n", __func__); + return false; + } + + // Clear caches to force reload with new format + LOCK(cs); + mnListsCache.clear(); + mnListDiffsCache.clear(); + + LogPrintf("CDeterministicMNManager::%s -- Successfully migrated %d diffs to nVersion-first format\n", __func__, + keys_to_erase.size()); + + return true; +} diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index e6babc45b478..291e9326c948 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -539,6 +539,20 @@ class CDeterministicMNListDiff template void Unserialize(Stream& s) + { + UnserializeImpl(s, false); // Default: new format + } + + // Legacy-aware unserialize method + template + void UnserializeLegacyFormat(Stream& s) + { + UnserializeImpl(s, true); // Legacy format + } + +private: + template + void UnserializeImpl(Stream& s, bool isLegacyFormat) { // Reset all collections before reading addedMNs.clear(); @@ -551,9 +565,16 @@ class CDeterministicMNListDiff for (size_t to_read = ReadCompactSize(s); to_read > 0; --to_read) { uint64_t internalId = ReadVarInt(s); - // CDeterministicMNState can have newer fields but doesn't need migration logic here as CDeterministicMNStateDiff - // is always serialised using a bitmask and new fields have a new bit guide value, so we are good to continue. - updatedMNs.emplace(internalId, CDeterministicMNStateDiff(deserialize, s)); + + if (isLegacyFormat) { + // Use legacy deserializer for old format + CDeterministicMNStateDiffLegacy legacyDiff(deserialize, s); + // Convert to new format and store + updatedMNs.emplace(internalId, legacyDiff.ToNewFormat()); + } else { + // Use current deserializer for new format + updatedMNs.emplace(internalId, CDeterministicMNStateDiff(deserialize, s)); + } } for (size_t to_read = ReadCompactSize(s); to_read > 0; --to_read) { @@ -562,6 +583,7 @@ class CDeterministicMNListDiff } } +public: bool HasChanges() const { return !addedMNs.empty() || !updatedMNs.empty() || !removedMns.empty(); @@ -633,6 +655,10 @@ class CDeterministicMNManager void DoMaintenance() EXCLUSIVE_LOCKS_REQUIRED(!cs); + // Migration support for nVersion-first CDeterministicMNStateDiff format + [[nodiscard]] bool IsMigrationRequired() const EXCLUSIVE_LOCKS_REQUIRED(!cs); + [[nodiscard]] bool MigrateLegacyDiffs() EXCLUSIVE_LOCKS_REQUIRED(!cs); + private: void CleanupCache(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); CDeterministicMNList GetListForBlockInternal(gsl::not_null pindex) EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/init.cpp b/src/init.cpp index e5bcc2470fb0..8d9acab7bc9f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2043,6 +2043,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) case ChainstateLoadingError::ERROR_COMMITING_EVO_DB: strLoadError = _("Failed to commit Evo database"); break; + case ChainstateLoadingError::ERROR_UPGRADING_EVO_DB: + strLoadError = _("Failed to upgrade Evo database"); + break; case ChainstateLoadingError::ERROR_UPGRADING_SIGNALS_DB: strLoadError = _("Error upgrading evo database for EHF"); break; diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index a84766046519..f64d620881ef 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -192,6 +192,11 @@ std::optional LoadChainstate(bool fReset, return ChainstateLoadingError::ERROR_UPGRADING_SIGNALS_DB; } + // Check if nVersion-first migration is needed and perform it + if (dmnman->IsMigrationRequired() && !dmnman->MigrateLegacyDiffs()) { + return ChainstateLoadingError::ERROR_UPGRADING_EVO_DB; + } + return std::nullopt; } diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 54870a8f7bfa..3279d52b90ec 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -45,6 +45,7 @@ enum class ChainstateLoadingError { ERROR_LOADCHAINTIP_FAILED, ERROR_GENERIC_BLOCKDB_OPEN_FAILED, ERROR_COMMITING_EVO_DB, + ERROR_UPGRADING_EVO_DB, ERROR_UPGRADING_SIGNALS_DB, SHUTDOWN_PROBED, }; From ac6ea47367f603d43af2aaedb336bd5c3cd6489f Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 11 Aug 2025 15:50:33 +0300 Subject: [PATCH 04/11] feat: enforce `nVersion` bit for `pubKeyOperator` and `netInfo` bits in new format --- src/evo/dmnstate.h | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 228e0e658f9d..485a5a33ffbb 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -231,8 +231,8 @@ class CDeterministicMNStateDiff } } }); - if (fields & Field_pubKeyOperator) { - // pubKeyOperator needs nVersion + if ((fields & Field_pubKeyOperator) || (fields & Field_netInfo)) { + // pubKeyOperator and netInfo need nVersion state.nVersion = b.nVersion; fields |= Field_nVersion; } @@ -246,21 +246,21 @@ class CDeterministicMNStateDiff { READWRITE(VARINT(obj.fields)); - // NOTE: reading pubKeyOperator requires nVersion - bool read_pubkey{false}; + if ((obj.fields & Field_pubKeyOperator) || (obj.fields & Field_netInfo)) { + // pubKeyOperator and netInfo need nVersion + assert(obj.fields & Field_nVersion); + } + boost::hana::for_each(members, [&](auto&& member) { using BaseType = std::decay_t; if constexpr (BaseType::mask == Field_pubKeyOperator) { if (obj.fields & member.mask) { - SER_READ(obj, read_pubkey = true); READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.state.pubKeyOperator), obj.state.nVersion == ProTxVersion::LegacyBLS)); } } else if constexpr (BaseType::mask == Field_netInfo) { if (obj.fields & member.mask) { - // As nVersion is stored after netInfo, we use a magic word to determine the underlying implementation - // TODO: Implement this READWRITE(NetInfoSerWrapper(const_cast&>(obj.state.netInfo), - /*is_extended=*/false)); + obj.state.nVersion == ProTxVersion::ExtAddr)); } } else { if (obj.fields & member.mask) { @@ -268,11 +268,6 @@ class CDeterministicMNStateDiff } } }); - - if (read_pubkey) { - SER_READ(obj, obj.fields |= Field_nVersion); - SER_READ(obj, obj.state.pubKeyOperator.SetLegacy(obj.state.nVersion == ProTxVersion::LegacyBLS)); - } } void ApplyToState(CDeterministicMNState& target) const @@ -352,7 +347,14 @@ class CDeterministicMNStateDiffLegacy CDeterministicMNStateDiffLegacy() = default; template - CDeterministicMNStateDiffLegacy(deserialize_type, Stream& s) { s >> *this; } + CDeterministicMNStateDiffLegacy(deserialize_type, Stream& s) + { + s >> *this; + if ((fields & LegacyField_pubKeyOperator) || (fields & LegacyField_netInfo)) { + // pubKeyOperator and netInfo need nVersion + fields |= LegacyField_nVersion; + } + } // Deserialize using legacy format SERIALIZE_METHODS(CDeterministicMNStateDiffLegacy, obj) From f6a4f01446d7f1660e999132196ce65207b4d79f Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 11 Aug 2025 15:49:07 +0300 Subject: [PATCH 05/11] test: add test cases for bit mapping and migration deser logic --- src/test/evo_deterministicmns_tests.cpp | 100 ++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 06e97f8b669b..2dcfdb105d5e 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -1041,4 +1041,104 @@ BOOST_AUTO_TEST_CASE(test_sml_cache_basic) SmlCache(setup); } +BOOST_AUTO_TEST_CASE(field_bit_migration_validation) +{ + // Test individual field mappings for ALL 19 fields + struct FieldMapping { + uint32_t legacyBit; + uint32_t newBit; + std::string name; + }; + + std::vector mappings = { + {0x0001, CDeterministicMNStateDiff::Field_nRegisteredHeight, "nRegisteredHeight"}, + {0x0002, CDeterministicMNStateDiff::Field_nLastPaidHeight, "nLastPaidHeight"}, + {0x0004, CDeterministicMNStateDiff::Field_nPoSePenalty, "nPoSePenalty"}, + {0x0008, CDeterministicMNStateDiff::Field_nPoSeRevivedHeight, "nPoSeRevivedHeight"}, + {0x0010, CDeterministicMNStateDiff::Field_nPoSeBanHeight, "nPoSeBanHeight"}, + {0x0020, CDeterministicMNStateDiff::Field_nRevocationReason, "nRevocationReason"}, + {0x0040, CDeterministicMNStateDiff::Field_confirmedHash, "confirmedHash"}, + {0x0080, CDeterministicMNStateDiff::Field_confirmedHashWithProRegTxHash, "confirmedHashWithProRegTxHash"}, + {0x0100, CDeterministicMNStateDiff::Field_keyIDOwner, "keyIDOwner"}, + {0x0200, CDeterministicMNStateDiff::Field_pubKeyOperator, "pubKeyOperator"}, + {0x0400, CDeterministicMNStateDiff::Field_keyIDVoting, "keyIDVoting"}, + {0x0800, CDeterministicMNStateDiff::Field_netInfo, "netInfo"}, + {0x1000, CDeterministicMNStateDiff::Field_scriptPayout, "scriptPayout"}, + {0x2000, CDeterministicMNStateDiff::Field_scriptOperatorPayout, "scriptOperatorPayout"}, + {0x4000, CDeterministicMNStateDiff::Field_nConsecutivePayments, "nConsecutivePayments"}, + {0x8000, CDeterministicMNStateDiff::Field_platformNodeID, "platformNodeID"}, + {0x10000, CDeterministicMNStateDiff::Field_platformP2PPort, "platformP2PPort"}, + {0x20000, CDeterministicMNStateDiff::Field_platformHTTPPort, "platformHTTPPort"}, + {0x40000, CDeterministicMNStateDiff::Field_nVersion, "nVersion"}, + }; + + // Verify each field mapping is correct + for (const auto& mapping : mappings) { + // Test individual field conversion + CDeterministicMNStateDiffLegacy legacyDiff; + legacyDiff.fields |= mapping.legacyBit; + // Convert to new format + auto newDiff = legacyDiff.ToNewFormat(); + BOOST_CHECK_MESSAGE(newDiff.fields == mapping.newBit, strprintf("Field %s: legacy 0x%x should convert to 0x%x", + mapping.name, mapping.legacyBit, mapping.newBit)); + } + + // Test complex multi-field scenarios + uint32_t complexLegacyFields = 0x0200 | // Legacy Field_pubKeyOperator + 0x0800 | // Legacy Field_netInfo + 0x1000 | // Legacy Field_scriptPayout + 0x40000; // Legacy Field_nVersion + + uint32_t expectedNewFields = CDeterministicMNStateDiff::Field_nVersion | // 0x0001 + CDeterministicMNStateDiff::Field_pubKeyOperator | // 0x0400 (was 0x0200) + CDeterministicMNStateDiff::Field_netInfo | // 0x1000 (was 0x0800) + CDeterministicMNStateDiff::Field_scriptPayout; // 0x2000 (was 0x1000) + + CDeterministicMNStateDiffLegacy legacyDiff; + legacyDiff.fields |= complexLegacyFields; + // Convert to new format + auto newDiff = legacyDiff.ToNewFormat(); + BOOST_CHECK_EQUAL(newDiff.fields, expectedNewFields); + + // Verify no bit conflicts exist in new field layout + std::set usedBits; + for (const auto& mapping : mappings) { + BOOST_CHECK_MESSAGE(usedBits.find(mapping.newBit) == usedBits.end(), + strprintf("Duplicate bit 0x%x found for field %s", mapping.newBit, mapping.name)); + usedBits.insert(mapping.newBit); + } + + // Verify all 19 fields have unique bit assignments + BOOST_CHECK_EQUAL(usedBits.size(), 19); +} + +BOOST_AUTO_TEST_CASE(migration_logic_validation) +{ + // Test the database migration logic for nVersion-first format conversion. + // Migration logic is handled at CDeterministicMNListDiff level + // using CDeterministicMNStateDiffLegacy for legacy format deserialization. + + // Create sample legacy format state diff + CDeterministicMNStateDiffLegacy legacyDiff; + legacyDiff.fields = 0x40000 | 0x0200 | 0x0800; // Legacy: nVersion, pubKeyOperator, netInfo + legacyDiff.state.nVersion = ProTxVersion::BasicBLS; + legacyDiff.state.pubKeyOperator.Set(CBLSPublicKey{}, false); + legacyDiff.state.netInfo = NetInfoInterface::MakeNetInfo(ProTxVersion::BasicBLS); + + // Test legacy class conversion (this would normally be done by CDeterministicMNListDiff) + CDataStream ss(SER_DISK, CLIENT_VERSION); + ss << legacyDiff; + + CDeterministicMNStateDiffLegacy legacyDeserializer(deserialize, ss); + CDeterministicMNStateDiff convertedDiff = legacyDeserializer.ToNewFormat(); + + // Verify conversion worked correctly + uint32_t expectedNewFields = CDeterministicMNStateDiff::Field_nVersion | // 0x0001 + CDeterministicMNStateDiff::Field_pubKeyOperator | // 0x0400 + CDeterministicMNStateDiff::Field_netInfo; // 0x1000 + + BOOST_CHECK_EQUAL(convertedDiff.fields, expectedNewFields); + BOOST_CHECK_EQUAL(convertedDiff.state.nVersion, ProTxVersion::BasicBLS); +} + BOOST_AUTO_TEST_SUITE_END() From 8832a3b3923399cb6aa6f98b0b0bb007cb8a70b8 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 11 Aug 2025 22:01:26 +0300 Subject: [PATCH 06/11] docs: add release notes --- doc/release-notes-6813.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/release-notes-6813.md diff --git a/doc/release-notes-6813.md b/doc/release-notes-6813.md new file mode 100644 index 000000000000..aa677f310ab4 --- /dev/null +++ b/doc/release-notes-6813.md @@ -0,0 +1,4 @@ +EvoDB migration +--------------- + +This release automatically handles migration from legacy masternode state diff format to a new one which should make future state diff upgrades seamless. No manual intervention required for node operators. This operation is irreversible and you'd have to reindex if you decide to downgrade to a previous version. #6813 From 80906fafe00d0666114724ef88c01b6f92acafc2 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Wed, 13 Aug 2025 18:52:54 +0300 Subject: [PATCH 07/11] fix: apply review suggestions --- src/evo/deterministicmns.cpp | 6 ++++-- src/evo/deterministicmns.h | 8 +++++--- src/evo/dmnstate.h | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 835d64ad3991..9fb771cad8d7 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -1342,7 +1342,8 @@ bool CDeterministicMNManager::IsMigrationRequired() const { // Check if there are any legacy format diffs in the database // by looking for DB_LIST_DIFF_LEGACY entries - LOCK(::cs_main); + + AssertLockHeld(::cs_main); std::unique_ptr pcursor{m_evoDb.GetRawDB().NewIterator()}; auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256())}; @@ -1368,8 +1369,9 @@ bool CDeterministicMNManager::MigrateLegacyDiffs() // CRITICAL: This migration converts ALL stored CDeterministicMNListDiff entries // from legacy database key (DB_LIST_DIFF_LEGACY) to new key (DB_LIST_DIFF) format + AssertLockHeld(::cs_main); + LogPrintf("CDeterministicMNManager::%s -- Starting migration to nVersion-first format\n", __func__); - LOCK(::cs_main); std::vector keys_to_erase; diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 291e9326c948..84041740b947 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -565,7 +565,9 @@ class CDeterministicMNListDiff for (size_t to_read = ReadCompactSize(s); to_read > 0; --to_read) { uint64_t internalId = ReadVarInt(s); - + // CDeterministicMNState can have newer fields but doesn't need migration logic here as + // CDeterministicMNStateDiff is always serialised using a bitmask and new fields have a new bit guide value, + // so we are good to continue. We do need migration logic to change field order though. if (isLegacyFormat) { // Use legacy deserializer for old format CDeterministicMNStateDiffLegacy legacyDiff(deserialize, s); @@ -656,8 +658,8 @@ class CDeterministicMNManager void DoMaintenance() EXCLUSIVE_LOCKS_REQUIRED(!cs); // Migration support for nVersion-first CDeterministicMNStateDiff format - [[nodiscard]] bool IsMigrationRequired() const EXCLUSIVE_LOCKS_REQUIRED(!cs); - [[nodiscard]] bool MigrateLegacyDiffs() EXCLUSIVE_LOCKS_REQUIRED(!cs); + [[nodiscard]] bool IsMigrationRequired() const EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); + [[nodiscard]] bool MigrateLegacyDiffs() EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); private: void CleanupCache(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 485a5a33ffbb..0e8ce8155c43 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -246,9 +246,9 @@ class CDeterministicMNStateDiff { READWRITE(VARINT(obj.fields)); - if ((obj.fields & Field_pubKeyOperator) || (obj.fields & Field_netInfo)) { + if (((obj.fields & Field_pubKeyOperator) || (obj.fields & Field_netInfo)) && !(obj.fields & Field_nVersion)) { // pubKeyOperator and netInfo need nVersion - assert(obj.fields & Field_nVersion); + throw std::ios_base::failure("Invalid data, nVersion unset when pubKeyOperator or netInfo set"); } boost::hana::for_each(members, [&](auto&& member) { From efea9317b0925ce8c1bbb8642f9238b3c9925e86 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Thu, 14 Aug 2025 12:25:48 +0300 Subject: [PATCH 08/11] fix: do not trust legacy diffs, detect correct `nVersion` on migration --- src/evo/deterministicmns.cpp | 95 ++++++++++++++++++++++++++---------- src/evo/deterministicmns.h | 2 +- src/evo/dmnstate.h | 17 ++----- src/node/chainstate.cpp | 2 +- 4 files changed, 76 insertions(+), 40 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 9fb771cad8d7..807e9c46fddb 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -1364,7 +1364,7 @@ bool CDeterministicMNManager::IsMigrationRequired() const return false; // No legacy format found } -bool CDeterministicMNManager::MigrateLegacyDiffs() +bool CDeterministicMNManager::MigrateLegacyDiffs(const CBlockIndex* const tip_index) { // CRITICAL: This migration converts ALL stored CDeterministicMNListDiff entries // from legacy database key (DB_LIST_DIFF_LEGACY) to new key (DB_LIST_DIFF) format @@ -1373,30 +1373,40 @@ bool CDeterministicMNManager::MigrateLegacyDiffs() LogPrintf("CDeterministicMNManager::%s -- Starting migration to nVersion-first format\n", __func__); - std::vector keys_to_erase; + std::vector keys_to_erase; CDBBatch batch(m_evoDb.GetRawDB()); std::unique_ptr pcursor{m_evoDb.GetRawDB().NewIterator()}; - auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256())}; - pcursor->Seek(start); - while (pcursor->Valid()) { - decltype(start) k; - if (!pcursor->GetKey(k) || std::get<0>(k) != DB_LIST_DIFF_LEGACY) { + // Keep track of the list to get correct nVersion at the current height + CDeterministicMNList snapshot; + + const auto start_height{Params().GetConsensus().DeploymentHeight(Consensus::DEPLOYMENT_DIP0003)}; + for (auto current_height : irange::range(start_height, tip_index->nHeight + 1)) { + auto current_index = tip_index->GetAncestor(current_height); + auto target_key{std::make_tuple(DB_LIST_DIFF_LEGACY, current_index->GetBlockHash())}; + pcursor->Seek(target_key); + + decltype(target_key) key; + if (!pcursor->Valid() || !pcursor->GetKey(key) || std::get<0>(key) != DB_LIST_DIFF_LEGACY) { break; } + if (std::get<1>(key) != current_index->GetBlockHash()) { + throw std::ios_base::failure("Invalid data, we must have legacy diffs for each height"); + } + // Use legacy-aware deserialization for DB_LIST_DIFF_LEGACY entries CDataStream s(SER_DISK, CLIENT_VERSION); - if (!m_evoDb.GetRawDB().ReadDataStream(k, s)) { + if (!m_evoDb.GetRawDB().ReadDataStream(key, s)) { break; } CDeterministicMNListDiff legacyDiff; legacyDiff.UnserializeLegacyFormat(s); // Use legacy format deserializer + snapshot.ApplyDiff(current_index, legacyDiff); CDeterministicMNListDiff convertedDiff; - convertedDiff.nHeight = legacyDiff.nHeight; convertedDiff.addedMNs = legacyDiff.addedMNs; convertedDiff.removedMns = legacyDiff.removedMns; @@ -1404,46 +1414,81 @@ bool CDeterministicMNManager::MigrateLegacyDiffs() // CDeterministicMNStateDiffLegacy.ToNewFormat() was called during deserialization // So legacyDiff.updatedMNs already contains properly converted CDeterministicMNStateDiff objects - // Simply copy the already-converted state diffs - for (const auto& [internalId, stateDiff] : legacyDiff.updatedMNs) { + // Copy the already-converted state diffs but make sure pubKeyOperator, nVersion and fields are set properly + for (auto& [internalId, stateDiff] : legacyDiff.updatedMNs) { + auto dmn = snapshot.GetMNByInternalId(internalId); + if (!dmn) { + // shouldn't happen + throw std::runtime_error(strprintf("%s: can't find an updated masternode, id=%d", __func__, internalId)); + } + if (!(stateDiff.fields & CDeterministicMNStateDiff::Field_nVersion)) { + if ((stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) || + (stateDiff.fields & CDeterministicMNStateDiff::Field_netInfo)) { + stateDiff.fields |= CDeterministicMNStateDiff::Field_nVersion; + stateDiff.state.nVersion = dmn->pdmnState->nVersion; + } + if (stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) { + stateDiff.state.pubKeyOperator.SetLegacy(stateDiff.state.nVersion == ProTxVersion::LegacyBLS); + } + } convertedDiff.updatedMNs.emplace(internalId, stateDiff); } // Write the converted diff to new database key - batch.Write(std::make_pair(DB_LIST_DIFF, std::get<1>(k)), convertedDiff); - keys_to_erase.push_back(std::get<1>(k)); + batch.Write(std::make_pair(DB_LIST_DIFF, std::get<1>(key)), convertedDiff); + keys_to_erase.push_back(current_index); if (batch.SizeEstimate() >= (1 << 24)) { - LogPrintf("CDeterministicMNManager::%s -- Writing new diffs...\n", __func__); + LogPrintf("CDeterministicMNManager::%s -- Writing new diffs, height=%d...\n", __func__, current_height); m_evoDb.GetRawDB().WriteBatch(batch); batch.Clear(); } - - pcursor->Next(); } pcursor.reset(); - LogPrintf("CDeterministicMNManager::%s -- Writing new diffs...\n", __func__); + LogPrintf("CDeterministicMNManager::%s -- Writing new diffs, height=%d...\n", __func__, tip_index->nHeight); m_evoDb.GetRawDB().WriteBatch(batch); batch.Clear(); + LogPrintf("CDeterministicMNManager::%s -- Wrote %d new diffs\n", __func__, keys_to_erase.size()); - LogPrintf("CDeterministicMNManager::%s -- Erasing %d legacy database entries after successful migration\n", - __func__, keys_to_erase.size()); - - // Delete all legacy format entries - for (const auto& blockHash : keys_to_erase) { - batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, blockHash)); + // Delete all found legacy format entries + for (const auto& index : keys_to_erase) { + batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, index->GetBlockHash())); if (batch.SizeEstimate() >= (1 << 24)) { - LogPrintf("CDeterministicMNManager::%s -- Erasing legacy diffs...\n", __func__); + LogPrintf("CDeterministicMNManager::%s -- Erasing found legacy diffs, height=%d...\n", __func__, + index->nHeight); m_evoDb.GetRawDB().WriteBatch(batch); batch.Clear(); } } - LogPrintf("CDeterministicMNManager::%s -- Erasing legacy diffs...\n", __func__); + LogPrintf("CDeterministicMNManager::%s -- Erasing found legacy diffs, height=%d...\n", __func__, tip_index->nHeight); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + LogPrintf("CDeterministicMNManager::%s -- Erased %d found legacy diffs\n", __func__, keys_to_erase.size()); + + // Delete all dangling legacy format entries + std::unique_ptr pcursor_dangling{m_evoDb.GetRawDB().NewIterator()}; + auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256())}; + pcursor_dangling->Seek(start); + int count{0}; + while (pcursor_dangling->Valid()) { + decltype(start) key; + if (!pcursor_dangling->GetKey(key) || std::get<0>(key) != DB_LIST_DIFF_LEGACY) { + break; + } + LogPrintf("CDeterministicMNManager::%s -- Erasing dangling legacy diff, hash=%s\n", __func__, + std::get<1>(key).ToString()); + batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, std::get<1>(key))); + pcursor_dangling->Next(); + ++count; + } + pcursor_dangling.reset(); + m_evoDb.GetRawDB().WriteBatch(batch); batch.Clear(); + LogPrintf("CDeterministicMNManager::%s -- Erased %d dangling legacy diffs\n", __func__, count); LogPrintf("CDeterministicMNManager::%s -- Compacting database...\n", __func__); m_evoDb.GetRawDB().CompactFull(); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 84041740b947..9c106c5fe941 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -659,7 +659,7 @@ class CDeterministicMNManager // Migration support for nVersion-first CDeterministicMNStateDiff format [[nodiscard]] bool IsMigrationRequired() const EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); - [[nodiscard]] bool MigrateLegacyDiffs() EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); + [[nodiscard]] bool MigrateLegacyDiffs(const CBlockIndex* const tip_index) EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); private: void CleanupCache(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 0e8ce8155c43..f727da4f8523 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -350,10 +350,6 @@ class CDeterministicMNStateDiffLegacy CDeterministicMNStateDiffLegacy(deserialize_type, Stream& s) { s >> *this; - if ((fields & LegacyField_pubKeyOperator) || (fields & LegacyField_netInfo)) { - // pubKeyOperator and netInfo need nVersion - fields |= LegacyField_nVersion; - } } // Deserialize using legacy format @@ -361,19 +357,19 @@ class CDeterministicMNStateDiffLegacy { READWRITE(VARINT(obj.fields)); - bool read_pubkey{false}; boost::hana::for_each(legacy_members, [&](auto&& member) { using BaseType = std::decay_t; if constexpr (BaseType::mask == LegacyField_pubKeyOperator) { if (obj.fields & member.mask) { - SER_READ(obj, read_pubkey = true); + // We'll set proper scheme later in MigrateLegacyDiffs() READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.state.pubKeyOperator), - obj.state.nVersion == ProTxVersion::LegacyBLS)); + /*legacy=*/true)); } } else if constexpr (BaseType::mask == LegacyField_netInfo) { if (obj.fields & member.mask) { // Legacy format supports non-extended addresses only - READWRITE(NetInfoSerWrapper(const_cast&>(obj.state.netInfo), false)); + READWRITE(NetInfoSerWrapper(const_cast&>(obj.state.netInfo), + /*is_extended=*/false)); } } else { if (obj.fields & member.mask) { @@ -381,11 +377,6 @@ class CDeterministicMNStateDiffLegacy } } }); - - if (read_pubkey) { - SER_READ(obj, obj.fields |= LegacyField_nVersion); - SER_READ(obj, obj.state.pubKeyOperator.SetLegacy(obj.state.nVersion == ProTxVersion::LegacyBLS)); - } } // Convert to new format diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index f64d620881ef..6ee51872820e 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -193,7 +193,7 @@ std::optional LoadChainstate(bool fReset, } // Check if nVersion-first migration is needed and perform it - if (dmnman->IsMigrationRequired() && !dmnman->MigrateLegacyDiffs()) { + if (dmnman->IsMigrationRequired() && !dmnman->MigrateLegacyDiffs(chainman.ActiveChainstate().m_chain.Tip())) { return ChainstateLoadingError::ERROR_UPGRADING_EVO_DB; } From f369673aa43de7563556038188ddb905b9a7b903 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 15 Aug 2025 07:50:24 +0300 Subject: [PATCH 09/11] fix: adjust ser/deser condition for netInfo --- src/evo/dmnstate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index f727da4f8523..380bac93ea0e 100644 --- a/src/evo/dmnstate.h +++ b/src/evo/dmnstate.h @@ -260,7 +260,7 @@ class CDeterministicMNStateDiff } else if constexpr (BaseType::mask == Field_netInfo) { if (obj.fields & member.mask) { READWRITE(NetInfoSerWrapper(const_cast&>(obj.state.netInfo), - obj.state.nVersion == ProTxVersion::ExtAddr)); + obj.state.nVersion >= ProTxVersion::ExtAddr)); } } else { if (obj.fields & member.mask) { From 1c50949f1d8a27f560413e0bd2193aa00bf84abc Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 18 Aug 2025 12:34:11 +0300 Subject: [PATCH 10/11] refactor: `uint256()` -> `uint256{}` --- src/evo/deterministicmns.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 807e9c46fddb..1ca0617f8edc 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -1346,7 +1346,7 @@ bool CDeterministicMNManager::IsMigrationRequired() const AssertLockHeld(::cs_main); std::unique_ptr pcursor{m_evoDb.GetRawDB().NewIterator()}; - auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256())}; + auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256{})}; pcursor->Seek(start); // If we find any entries with the legacy key, migration is needed @@ -1470,7 +1470,7 @@ bool CDeterministicMNManager::MigrateLegacyDiffs(const CBlockIndex* const tip_in // Delete all dangling legacy format entries std::unique_ptr pcursor_dangling{m_evoDb.GetRawDB().NewIterator()}; - auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256())}; + auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256{})}; pcursor_dangling->Seek(start); int count{0}; while (pcursor_dangling->Valid()) { From 4d9d8f7e35587eead6febb6082247c55fc8433dd Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 18 Aug 2025 21:36:57 +0300 Subject: [PATCH 11/11] chore: update release notes --- doc/release-notes-6813.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release-notes-6813.md b/doc/release-notes-6813.md index aa677f310ab4..c8697813e73d 100644 --- a/doc/release-notes-6813.md +++ b/doc/release-notes-6813.md @@ -1,4 +1,4 @@ EvoDB migration --------------- -This release automatically handles migration from legacy masternode state diff format to a new one which should make future state diff upgrades seamless. No manual intervention required for node operators. This operation is irreversible and you'd have to reindex if you decide to downgrade to a previous version. #6813 +This release introduces a new internal format for masternode state data to support extended addresses and make future updates of this data seamless. Nodes will automatically migrate to the new format. Downgrading to an earlier version would require a full reindex. #6813