Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1aed37b
feat: add the evo snapshot v3 canonical bounded codec and context-fre…
PastaPastaPasta Aug 13, 2026
67b4ae9
fix: build the ReadFixedBitSet trailing-bits mask without an implicit…
PastaPastaPasta Aug 13, 2026
5bce0fc
test: cover the bounded CRangesSet unserializer
PastaPastaPasta Aug 13, 2026
57dc6b1
fix: allow partial evo snapshot quorum history
PastaPastaPasta Aug 13, 2026
fd07f83
fix: reject full-domain ranges during deserialization
PastaPastaPasta Aug 13, 2026
484fcec
fix: reject noncanonical MNHF signal order during deserialization
PastaPastaPasta Aug 20, 2026
5b9e646
fix: bound commitment bitsets by a format ceiling instead of static L…
PastaPastaPasta Aug 20, 2026
138387f
fix: validate credit-pool amounts and MNHF signal semantics context-free
PastaPastaPasta Aug 20, 2026
18515f4
fix: replace previous contents when deserializing an evo snapshot
PastaPastaPasta Aug 20, 2026
a72328e
fix: bound same-prefix proTxHash runs against HAMT collision blowup
PastaPastaPasta Aug 20, 2026
2de4d28
fix: bind the CbTx height in VerifyEvoSnapshotCbTx
PastaPastaPasta Aug 20, 2026
6041092
fix: replace per-quorum contents when deserializing CQuorumSnapshotData
PastaPastaPasta Aug 20, 2026
d5ea6d5
fix: bound cumulative historical MN-list reconstruction work
PastaPastaPasta Aug 20, 2026
9a20e94
refactor: drop the C prefix from the new evo snapshot types
PastaPastaPasta Aug 28, 2026
0705557
refactor: give every evo snapshot collection one explicit canonical c…
PastaPastaPasta Aug 28, 2026
039ce9e
build: list evo/snapshot.cpp where it is actually used
PastaPastaPasta Aug 28, 2026
48effe4
fix: enforce the decoder's historical operation budget during validation
PastaPastaPasta Aug 28, 2026
4810d04
test: exercise evo per-quorum replacement for every cleared vector
PastaPastaPasta Aug 28, 2026
155ae33
test: name the noncanonical CRangesSet case for its actual defect
PastaPastaPasta Aug 28, 2026
303e5e5
fix: enforce canonical evo snapshot encodings and bounds
PastaPastaPasta Sep 8, 2026
1464506
fix: validate evo snapshot IDs and canonical BLS encodings
PastaPastaPasta Sep 9, 2026
7ca7423
fix: apply evo snapshot diffs atomically and pin lazy BLS and update-…
PastaPastaPasta Sep 11, 2026
5fff4fb
fix: reject revisited blocks and counter growth in the historical dif…
PastaPastaPasta Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ BITCOIN_CORE_H = \
evo/providertx_service.h \
evo/simplifiedmns.h \
evo/smldiff.h \
evo/snapshot.h \
evo/specialtx.h \
evo/specialtx_filter.h \
evo/specialtxman.h \
Expand Down Expand Up @@ -551,6 +552,7 @@ libbitcoin_node_a_SOURCES = \
evo/providertx_service.cpp \
evo/simplifiedmns.cpp \
evo/smldiff.cpp \
evo/snapshot.cpp \
evo/specialtx.cpp \
evo/specialtx_filter.cpp \
evo/specialtxman.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ BITCOIN_TESTS =\
test/evo_mnhf_tests.cpp \
test/evo_netinfo_tests.cpp \
test/evo_simplifiedmns_tests.cpp \
test/evo_snapshot_tests.cpp \
test/evo_trivialvalidation.cpp \
test/evo_utils_tests.cpp \
test/flatfile_tests.cpp \
Expand Down
4 changes: 2 additions & 2 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -882,11 +882,11 @@ class CRegTestParams : public CChainParams {
m_assumeutxo_data = MapAssumeutxo{
{
110,
{AssumeutxoHash{uint256S("0x9b2a277a3e3b979f1a539d57e949495d7f8247312dbc32bce6619128c192b44b")}, 110},
{AssumeutxoHash{uint256S("0x9b2a277a3e3b979f1a539d57e949495d7f8247312dbc32bce6619128c192b44b")}, EvoSnapshotHash{uint256{}}, 110},
},
{
200,
{AssumeutxoHash{uint256S("0x8a5bdd92252fc6b24663244bbe958c947bb036dc1f94ccd15439f48d8d1cb4e3")}, 200},
{AssumeutxoHash{uint256S("0x8a5bdd92252fc6b24663244bbe958c947bb036dc1f94ccd15439f48d8d1cb4e3")}, EvoSnapshotHash{uint256{}}, 200},
},
};

Expand Down
7 changes: 7 additions & 0 deletions src/chainparams.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ struct AssumeutxoHash : public BaseHash<uint256> {
explicit AssumeutxoHash(const uint256& hash) : BaseHash(hash) {}
};

struct EvoSnapshotHash : public BaseHash<uint256> {
explicit EvoSnapshotHash(const uint256& hash) : BaseHash(hash) {}
};

/**
* Holds configuration for use during UTXO snapshot load and validation. The contents
* here are security critical, since they dictate which UTXO snapshots are recognized
Expand All @@ -43,6 +47,9 @@ struct AssumeutxoData {
//! The expected hash of the deserialized UTXO set.
const AssumeutxoHash hash_serialized;

//! The expected single-SHA256 hash of the canonical Dash evo section.
const EvoSnapshotHash evo_hash;

//! Used to populate the nChainTx value, which is used during BlockManager::LoadBlockIndex().
//!
//! We need to hardcode the value here because this is computed cumulatively using block data,
Expand Down
39 changes: 39 additions & 0 deletions src/evo/deterministicmns.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,45 @@ void CDeterministicMNList::ApplyDiff(gsl::not_null<const CBlockIndex*> pindex, c
}
}

void CDeterministicMNList::ApplyDiffForSnapshot(const uint256& block_hash, int height,
uint32_t total_registered_count,
const CDeterministicMNListDiff& diff)
{
if (height < 0) throw std::runtime_error("negative historical MN-list height");
blockHash = block_hash;
nHeight = height;

for (const auto& id : diff.removedMns) {
auto dmn = GetMNByInternalId(id);
if (!dmn) throw std::runtime_error(strprintf("%s: can't find a removed masternode, id=%d", __func__, id));
RemoveMN(dmn->proTxHash);
}
// A historical diff spans many blocks, so surviving MNs can have exchanged
// a unique property (an address, an operator key) between the endpoints,
// and a new registration can hold an address an updated MN gave up.
// Sequential UpdateMN() would reject the first claimant as a duplicate:
// release every updated MN's old properties before any new state claims.
std::vector<CDeterministicMNCPtr> updated;
updated.reserve(diff.updatedMNs.size());
for (const auto& [id, state_diff] : diff.updatedMNs) {
auto dmn = GetMNByInternalId(id);
if (!dmn) throw std::runtime_error(strprintf("%s: can't find an updated masternode, id=%d", __func__, id));
auto new_state = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
state_diff.ApplyToState(*new_state);
auto new_dmn = std::make_shared<CDeterministicMN>(*dmn);
new_dmn->pdmnState = std::move(new_state);
RemoveMN(dmn->proTxHash);
updated.push_back(std::move(new_dmn));
}
for (const auto& dmn : updated) {
AddMN(dmn, /*fBumpTotalCount=*/false);
}
for (const auto& dmn : diff.addedMNs) {
AddMN(dmn, /*fBumpTotalCount=*/false);
Comment on lines +409 to +430

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why diff is applying for snapshot? What is scenario, when diff should be applied for snapshot?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pastaclaw says:

the base snapshot MN list should be a full list. ApplyDiffForSnapshot() is not used to construct that base list from a diff.

The extra diff chain exists only because the evo snapshot contains more than the current MN list. It also contains quorum/rotation state whose work_block_hash may refer to older blocks. To validate that state without accessing chainstate, the snapshot needs the corresponding historical MN-list roots. The current implementation represents those historical lists compactly as:

full MN list at base block

  • historical diffs going backwards
  • canonical hash for each reconstructed historical list

So the flow is:

snapshot.mn_list // full list at base height
historical_mn_list_diffs // reconstruct older lists referenced by quorum data
quorum/rotation entries // refer to those historical work-block lists

ApplyDiffForSnapshot() applies the historical diffs backwards from the full base list; it is not “apply a diff to obtain the snapshot itself.”

That said, the reviewer’s concern is valid from the API/design perspective. A type named ApplyDiffForSnapshot() makes it sound as if the snapshot’s primary list is diff-encoded. I’d clarify this in the code and likely rename it to something like:

ApplyHistoricalDiffForSnapshot(...)

or:

ApplySnapshotHistoricalDiff(...)

The surrounding comment should explicitly say:

The snapshot’s base MN list is serialized in full. This method is only for reconstructing older MN lists referenced by quorum and rotation data, without consulting chainstate or block-index data.

If the intended v3 format does not need to validate historical quorum/rotation state in the same snapshot, then the reviewer is right that the diff chain should be removed and every required list should either be serialized fully or omitted. But with the current stated format—where quorum entries reference historical work blocks—the diffs are a compact representation of additional required historical lists, not a replacement for the snapshot’s full base list


Consider having full snapshots here instead applying it incrementally, it will simplify implementation and avoid having workarounds such as mentioned in PR:

// A historical diff spans many blocks, so surviving MNs can have exchanged
    // a unique property (an address, an operator key) between the endpoints,
    // and a new registration can hold an address an updated MN gave up.
    // Sequential UpdateMN() would reject the first claimant as a duplicate:
    // release every updated MN's old properties before any new state claims.

}
nTotalRegisteredCount = total_registered_count;
}

void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTotalCount)
{
assert(dmn != nullptr);
Expand Down
13 changes: 11 additions & 2 deletions src/evo/deterministicmns.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class CDeterministicMN
}
}

[[nodiscard]] bool HasInternalId() const { return internalId != std::numeric_limits<uint64_t>::max(); }
[[nodiscard]] uint64_t GetInternalId() const;

[[nodiscard]] CSimplifiedMNListEntry to_sml_entry() const;
Expand Down Expand Up @@ -339,6 +340,8 @@ class CDeterministicMNList
assert(nHeight >= 0);
return nHeight;
}
/** Snapshot hashing also covers the pre-DIP3 default list (height -1). */
[[nodiscard]] int GetHeightForSnapshotCodec() const noexcept { return nHeight; }
void SetHeight(int _height)
{
assert(_height >= 0);
Expand Down Expand Up @@ -423,6 +426,11 @@ class CDeterministicMNList
void ApplyDiff(gsl::not_null<const CBlockIndex*> pindex, const CDeterministicMNListDiff& diff)
EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);

/** Apply a snapshot-local historical diff without dereferencing block data. */
void ApplyDiffForSnapshot(const uint256& block_hash, int height, uint32_t total_registered_count,
const CDeterministicMNListDiff& diff)
EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);

void AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTotalCount = true) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
void UpdateMN(const CDeterministicMN& oldDmn, const std::shared_ptr<const CDeterministicMNState>& pdmnState)
EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
Expand Down Expand Up @@ -625,8 +633,9 @@ class CDeterministicMNListDiff
int nHeight{-1}; //memory only

std::vector<CDeterministicMNCPtr> addedMNs;
// keys are all relating to the internalId of MNs
std::unordered_map<uint64_t, CDeterministicMNStateDiff> updatedMNs;
// keys are all relating to the internalId of MNs. Keys can come from
// untrusted input (evo snapshots), so the hash is salted against flooding.
std::unordered_map<uint64_t, CDeterministicMNStateDiff, StaticSaltedHasher> updatedMNs;
std::set<uint64_t> removedMns;

template<typename Stream>
Expand Down
Loading
Loading