diff --git a/doc/release-notes-6813.md b/doc/release-notes-6813.md new file mode 100644 index 000000000000..c8697813e73d --- /dev/null +++ b/doc/release-notes-6813.md @@ -0,0 +1,4 @@ +EvoDB migration +--------------- + +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 diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 9202f1a4eb2d..1ca0617f8edc 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,175 @@ 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 + + AssertLockHeld(::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(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 + + AssertLockHeld(::cs_main); + + LogPrintf("CDeterministicMNManager::%s -- Starting migration to nVersion-first format\n", __func__); + + std::vector keys_to_erase; + + CDBBatch batch(m_evoDb.GetRawDB()); + std::unique_ptr pcursor{m_evoDb.GetRawDB().NewIterator()}; + + // 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(key, s)) { + break; + } + + CDeterministicMNListDiff legacyDiff; + legacyDiff.UnserializeLegacyFormat(s); // Use legacy format deserializer + snapshot.ApplyDiff(current_index, legacyDiff); + + CDeterministicMNListDiff convertedDiff; + 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 + + // 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>(key)), convertedDiff); + keys_to_erase.push_back(current_index); + + if (batch.SizeEstimate() >= (1 << 24)) { + LogPrintf("CDeterministicMNManager::%s -- Writing new diffs, height=%d...\n", __func__, current_height); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + } + } + pcursor.reset(); + + 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()); + + // 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 found legacy diffs, height=%d...\n", __func__, + index->nHeight); + m_evoDb.GetRawDB().WriteBatch(batch); + batch.Clear(); + } + } + + 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(); + + // 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..9c106c5fe941 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,18 @@ 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)); + // 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); + // 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 +585,7 @@ class CDeterministicMNListDiff } } +public: bool HasChanges() const { return !addedMNs.empty() || !updatedMNs.empty() || !removedMns.empty(); @@ -633,6 +657,10 @@ class CDeterministicMNManager void DoMaintenance() EXCLUSIVE_LOCKS_REQUIRED(!cs); + // Migration support for nVersion-first CDeterministicMNStateDiff format + [[nodiscard]] bool IsMigrationRequired() const 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); CDeterministicMNList GetListForBlockInternal(gsl::not_null pindex) EXCLUSIVE_LOCKS_REQUIRED(cs); diff --git a/src/evo/dmnstate.h b/src/evo/dmnstate.h index 9d2f45f3eb9d..380bac93ea0e 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}; @@ -153,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: @@ -185,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), @@ -202,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 @@ -230,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; } @@ -245,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)) && !(obj.fields & Field_nVersion)) { + // pubKeyOperator and netInfo need nVersion + throw std::ios_base::failure("Invalid data, nVersion unset when pubKeyOperator or netInfo set"); + } + 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) { @@ -267,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 @@ -284,4 +280,137 @@ 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)); + + boost::hana::for_each(legacy_members, [&](auto&& member) { + using BaseType = std::decay_t; + if constexpr (BaseType::mask == LegacyField_pubKeyOperator) { + if (obj.fields & member.mask) { + // We'll set proper scheme later in MigrateLegacyDiffs() + READWRITE(CBLSLazyPublicKeyVersionWrapper(const_cast(obj.state.pubKeyOperator), + /*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), + /*is_extended=*/false)); + } + } else { + if (obj.fields & member.mask) { + READWRITE(member.get(obj.state)); + } + } + }); + } + + // 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 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..6ee51872820e 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(chainman.ActiveChainstate().m_chain.Tip())) { + 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, }; 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()