Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
38a2351
fix: idle DKG phase threads while snapshot background validation is i…
PastaPastaPasta Nov 4, 2026
c343e3f
refactor(bls): resolve the implicit BLS scheme per thread
PastaPastaPasta Nov 6, 2026
bf86b46
fix(validation): keep a non-active chainstate's BLS scheme on its thread
PastaPastaPasta Nov 6, 2026
d07a3a6
fix(validation): establish the BLS scheme when the active chainstate …
PastaPastaPasta Nov 6, 2026
4fe8b2f
feat(llmq): report whether a mined-commitment scan covered its window
PastaPastaPasta Nov 6, 2026
f317882
feat(interfaces): look up a Platform quorum at its signed Core height
PastaPastaPasta Nov 6, 2026
35eb827
fix(evo): pass snapshot-data and mismatch errors through generic catches
PastaPastaPasta Oct 15, 2026
98416e5
refactor(evo): add EvoDB view tokens and scopes
PastaPastaPasta Oct 15, 2026
81f90bc
feat(evo): store snapshot-identity state in a generation namespace wi…
PastaPastaPasta Oct 15, 2026
108f0d5
fix(evo): partition Evo-derived caches by view
PastaPastaPasta Oct 15, 2026
cd49680
fix(evo): bind non-active paths and dispatcher boundaries explicitly
PastaPastaPasta Oct 15, 2026
c706db2
feat(evo): reject unbound Evo reads during dual operation
PastaPastaPasta Oct 15, 2026
0dfbf69
fix(instantsend): skip only the islocks whose quorum is below the sna…
PastaPastaPasta Oct 31, 2026
3b03112
fix(llmq): treat short scans of types not enabled up to the snapshot …
PastaPastaPasta Oct 31, 2026
5971a1b
fix(interfaces): raise the Evo-unavailable warning once per operation…
PastaPastaPasta Oct 31, 2026
87c9f05
fix(node): keep an earlier invalid snapshot dir when finishing a reje…
PastaPastaPasta Oct 31, 2026
c2f27f9
fix(validation): stop reading a rejected snapshot when its rejection …
PastaPastaPasta Oct 31, 2026
3249380
feat(evo): v4 snapshot with derived rotation dependencies and bootstr…
PastaPastaPasta Oct 15, 2026
8c9b012
feat(evo): carry and consume the credit-pool window
PastaPastaPasta Oct 15, 2026
aa16d98
feat(evo): carry the header-proven base coinbase and use it for B+1 C…
PastaPastaPasta Oct 15, 2026
26118d1
fix(evo): export the raw MNHF signal record at the base and derive th…
PastaPastaPasta Oct 16, 2026
2447de2
fix(llmq): exact work modifiers and no silent degradation under the s…
PastaPastaPasta Oct 15, 2026
4083c95
feat(llmq): prove ChainLock and asset-unlock scan coverage
PastaPastaPasta Oct 15, 2026
ce92b43
feat(rpc): emit the v4 evo section in dumptxoutset
PastaPastaPasta Oct 15, 2026
41b200b
feat(validation): require a matching evo pin; add regtest -assumeutxo…
PastaPastaPasta Oct 15, 2026
5df389d
feat(validation): seed into an unpublished generation, dry-run, then …
PastaPastaPasta Oct 15, 2026
75e85c2
feat(validation): rebuild and compare the full evo section at completion
PastaPastaPasta Nov 10, 2026
fb75777
Merge bitcoin/bitcoin#27596: assumeutxo (2)
PastaPastaPasta Nov 8, 2026
b255dbf
Merge bitcoin/bitcoin#28589: test: assumeutxo func test race fixes
PastaPastaPasta Nov 8, 2026
96dc025
Merge bitcoin/bitcoin#28590: assumeutxo: change getchainstates RPC to…
PastaPastaPasta Nov 8, 2026
f3c1fcd
Merge bitcoin/bitcoin#28562: AssumeUTXO follow-ups
PastaPastaPasta Nov 8, 2026
73d9e23
Merge bitcoin/bitcoin#28625: test: check that loading snapshot not ma…
PastaPastaPasta Nov 8, 2026
ab455e0
Merge bitcoin/bitcoin#28639: refactor: Remove unused nchaintx from Sn…
PastaPastaPasta Nov 8, 2026
fca5f7c
Merge bitcoin/bitcoin#28652: assumeutxo: fail early if snapshot block…
PastaPastaPasta Nov 8, 2026
af5c8eb
Merge bitcoin/bitcoin#28647: test: Add assumeutxo test for wrong hash
PastaPastaPasta Nov 8, 2026
fa19d81
Merge bitcoin/bitcoin#28666: test: assumeutxo file with unknown block…
PastaPastaPasta Nov 8, 2026
fc3424b
Merge bitcoin/bitcoin#28669: test: check assumeutxo file for changed …
PastaPastaPasta Nov 8, 2026
9118796
test: add the feature_assumeutxo.py cases of bitcoin#28685
PastaPastaPasta Nov 8, 2026
dc1ac03
Merge bitcoin/bitcoin#28698: assumeutxo, blockstorage: Prevent core d…
PastaPastaPasta Nov 8, 2026
6f9909e
Merge bitcoin/bitcoin#28835: test: Check error details with assert_de…
PastaPastaPasta Nov 8, 2026
756401f
Merge bitcoin/bitcoin#28838: test: add assumeutxo wallet test
PastaPastaPasta Nov 8, 2026
c0afe79
Merge bitcoin/bitcoin#31556: validation: Send correct notification du…
PastaPastaPasta Nov 8, 2026
ad44c5d
feat(validation): refuse snapshots on pruned nodes, masternodes and w…
PastaPastaPasta Nov 8, 2026
d79eda0
fix(governance): wait for the collateral index to follow the active c…
PastaPastaPasta Nov 8, 2026
1cadb01
fix(validation): serialize snapshot activations
PastaPastaPasta Nov 8, 2026
bf98919
fix(validation): replace a leftover chainstate_todelete at promotion
PastaPastaPasta Nov 8, 2026
0bd012d
fix(validation): keep the validated chainstate's block candidates whe…
PastaPastaPasta Nov 9, 2026
79003ea
Merge bitcoin/bitcoin#30388: validation: Check if mempool exists befo…
PastaPastaPasta Nov 9, 2026
2b96fc3
fix(validation): detach the mempool when a snapshot is rejected
PastaPastaPasta Nov 9, 2026
060fb20
fix(validation): stop loading a mempool file once its chainstate no l…
PastaPastaPasta Nov 9, 2026
f771855
test: retry InstantSend conflicts that a snapshot stall deferred
PastaPastaPasta Nov 9, 2026
8720587
test: add an end-to-end AssumeUTXO test on a Dash masternode network
PastaPastaPasta Nov 9, 2026
b15e459
fix(qt): clear the wizard's state-unavailable label once masternode s…
PastaPastaPasta Nov 9, 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
24 changes: 19 additions & 5 deletions doc/design/assumeutxo.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
# assumeutxo

Assumeutxo is a feature that allows fast bootstrapping of a validating dashd
instance with a very similar security model to assumevalid.
instance.

## Loading a snapshot

There is currently no canonical source for snapshots, but any downloaded snapshot
will be checked against a hash that's been hardcoded in source code.

Once you've obtained the snapshot, you can use the RPC command `loadtxoutset`
(yet to be merged) to load it.
to load it. `loadtxoutset` and `getchainstates` are hidden and available on
regtest only.

A masternode does not load snapshots, and a node does not start with
`-masternodeblsprivkey` while it has a snapshot chainstate, validated or not,
until a restart has promoted it.

### Pruning

A pruned node can load a snapshot. To save space, it's possible to delete the
snapshot file as soon as `loadtxoutset` finishes.
A pruned node cannot load a snapshot, and a node does not start with `-prune`
while it has a snapshot chainstate, validated or not, until a restart has
promoted it. It's possible to delete the snapshot file as soon as
`loadtxoutset` finishes.

The minimum `-dbcache` setting is 550 MiB, but this functionality ignores that
minimum and uses at least 1100 MiB.
Expand All @@ -25,10 +32,17 @@ downloaded snapshot).

### Indexes

Indexes work but don't take advantage of this feature. They always start building
A node with `-addressindex`, `-spentindex` or `-timestampindex` cannot load a
snapshot, and does not start with them while it has a snapshot chainstate.

Other indexes work but don't take advantage of this feature. They always start building
from the genesis block. Once the background validation reaches the snapshot block,
indexes will continue to build all the way to the tip.

Governance waits until background validation has completed and the governance
collateral index has caught up: until then the node neither processes nor relays
governance objects, and `gobject prepare` and `gobject submit` refuse.

For indexes that support pruning, note that no pruning will take place between
the snapshot and the tip, until the background sync has completed - after which
everything is pruned. Depending on how old the snapshot is, this may temporarily
Expand Down
16 changes: 16 additions & 0 deletions doc/release-notes-7593.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Updated RPCs
------------

- On a pruned node, `quorum info`, `quorum memberof` and `quorum selectquorum`
fail with `RPC_INTERNAL_ERROR` for a quorum whose members this node cannot
compute because the block that fixes them was pruned. They used to work with
members computed from a substitute value, which could be wrong. (#7593)

Other changes
-------------

- A pruned node no longer substitutes the block hash for the quorum score
modifier of a pruned work block. It keeps such a quorum's public key, from
its mined commitment, without members, so it still verifies signatures
by that quorum (for example on masternode hard-fork signals) and never
stalls on them. (#7593)
39 changes: 39 additions & 0 deletions doc/release-notes-7887.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Updated RPCs
------------

- An RPC that fails with an error because this node cannot read the
masternode, quorum or other Evo state it needs, for example because the
block data it is derived from was pruned, now returns error code
`RPC_INTERNAL_ERROR` (-32603) instead of `RPC_MISC_ERROR` (-1). The request
was not at fault. The package RPCs report such a failure as described
below. (#7887)

- `testmempoolaccept` with more than one transaction reports such a failure as
the `package-error` `evo-local-unavailable`, and `submitpackage` fails with
that reason. (#7887)

Other changes
-------------

- A block this node has stored but cannot read back while deriving the credit
pool or the EHF signals no longer makes the block being validated invalid.
The node shuts down with a "Failed to read block" error instead, as it does
when it cannot read a block it is connecting. (#7887)

- On a node without an assumeutxo snapshot, a warning about Evo state that
cannot be derived because block data was pruned now says so and suggests
restarting with `-reindex`. Startup still fails when the EHF signals at the
tip cannot be rebuilt. (#7887)

- On a pruned node, a block whose masternode, quorum or other Evo state
depends on block data that was pruned is no longer marked invalid, which
left the node on a local fork until `reconsiderblock`. The node now stops
advancing at that block, logs an error and shows a warning that block data
was pruned; restart with `-reindex` to download it again. (#7887)

- Developers: on regtest, every Evo database access must now be bound to a
chainstate's view (the hidden `-checkevobinding` option, on by default
there). A code path that reads Evo state without a binding fails with an
internal error, a dropped task or a stalled chainstate instead of reading
the default view. Local regtest networks run with it unless started with
`-checkevobinding=0`. (#7887)
4 changes: 2 additions & 2 deletions doc/zmq.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ Where the 8-byte uints correspond to the mempool sequence number.
| hashtx | <32-byte transaction hash in Little Endian> | <uint32 sequence number in Little Endian>


`rawblock`: Notifies when the chain tip is updated. Messages are ZMQ multipart messages with three parts. The first part is the topic (`rawblock`), the second part is the serialized block, and the last part is a sequence number (representing the message count to detect lost messages).
`rawblock`: Notifies when the chain tip is updated. When assumeutxo is in use, this notification will not be issued for historical blocks connected to the background validation chainstate. Messages are ZMQ multipart messages with three parts. The first part is the topic (`rawblock`), the second part is the serialized block, and the last part is a sequence number (representing the message count to detect lost messages).

| rawblock | <serialized block> | <uint32 sequence number in Little Endian>

`hashblock`: Notifies when the chain tip is updated. Messages are ZMQ multipart messages with three parts. The first part is the topic (`hashblock`), the second part is the 32-byte block hash, and the last part is a sequence number (representing the message count to detect lost messages).
`hashblock`: Notifies when the chain tip is updated. When assumeutxo is in use, this notification will not be issued for historical blocks connected to the background validation chainstate. Messages are ZMQ multipart messages with three parts. The first part is the topic (`hashblock`), the second part is the 32-byte block hash, and the last part is a sequence number (representing the message count to detect lost messages).

| hashblock | <32-byte block hash in Little Endian> | <uint32 sequence number in Little Endian>

Expand Down
8 changes: 8 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,13 @@ BITCOIN_CORE_H = \
evo/simplifiedmns.h \
evo/smldiff.h \
evo/snapshot.h \
evo/snapshot_config.h \
evo/snapshot_types.h \
evo/specialtx.h \
evo/specialtx_filter.h \
evo/specialtxman.h \
evo/types.h \
evo/viewcache.h \
external_signer.h \
flat-database.h \
flatfile.h \
Expand Down Expand Up @@ -581,6 +584,8 @@ libbitcoin_node_a_SOURCES = \
evo/simplifiedmns.cpp \
evo/smldiff.cpp \
evo/snapshot.cpp \
evo/snapshot_chain.cpp \
evo/snapshot_config.cpp \
evo/specialtx.cpp \
evo/specialtx_filter.cpp \
evo/specialtxman.cpp \
Expand Down Expand Up @@ -1322,6 +1327,9 @@ libdashkernel_la_SOURCES = \
evo/providertx_util.cpp \
evo/simplifiedmns.cpp \
evo/smldiff.cpp \
evo/snapshot.cpp \
evo/snapshot_chain.cpp \
evo/snapshot_config.cpp \
evo/specialtx.cpp \
evo/specialtx_filter.cpp \
evo/specialtxman.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.test_util.include
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ TEST_UTIL_H = \
test/util/blockfilter.h \
test/util/chainstate.h \
test/util/coins.h \
test/util/evo_snapshot.h \
test/util/json.h \
test/util/index.h \
test/util/llmq_tests.h \
Expand Down
12 changes: 12 additions & 0 deletions src/active/dkgsessionhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <util/time.h>
#include <validation.h>

#include <utility>

namespace llmq {
ActiveDKGSessionHandler::ActiveDKGSessionHandler(
CBLSWorker& bls_worker, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman,
Expand Down Expand Up @@ -114,8 +116,18 @@ void ActiveDKGSessionHandler::WaitForNextPhase(std::optional<QuorumPhase> curPha
{
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - starting, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, curPhase.has_value() ? std23::to_underlying(*curPhase) : -1, std23::to_underlying(nextPhase));

bool logged_snapshot_wait{false};
while (true) {
if (m_chainman.IsSnapshotActiveAndUnvalidated()) {
if (!curPhase.has_value() && !stopRequested) {
// No round can start before background validation completes, so there is nothing to abort
if (!std::exchange(logged_snapshot_wait, true)) {
LogPrint(BCLog::LLMQ_DKG, "%s -- %s qi[%d] - waiting for snapshot background validation\n",
__func__, params.name, quorumIndex);
}
UninterruptibleSleep(std::chrono::milliseconds{100});
continue;
}
LogPrint(BCLog::LLMQ_DKG, "ActiveDKGSessionHandler::%s -- %s qi[%d] - aborting because snapshot background validation is incomplete\n", __func__, params.name, quorumIndex);
throw AbortPhaseException();
}
Expand Down
2 changes: 1 addition & 1 deletion src/bench/wallet_create_tx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void generateFakeBlock(const CChainParams& params,

// notify wallet
const auto& pindex = WITH_LOCK(::cs_main, return context.chainman->ActiveChain().Tip());
wallet.blockConnected(kernel::MakeBlockInfo(pindex, &block));
wallet.blockConnected(ChainstateRole::NORMAL, kernel::MakeBlockInfo(pindex, &block));
}

struct PreSelectInputs {
Expand Down
60 changes: 51 additions & 9 deletions src/bls/bls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,40 @@
#include <cstring>

namespace bls {
std::atomic<bool> bls_legacy_scheme = std::atomic<bool>(true);
std::atomic<bool> bls_legacy_scheme = std::atomic<bool>(true);

namespace {
//! Trivially destructible, like util/threadnames.cpp's thread-local name.
thread_local std::optional<bool> g_thread_legacy_scheme;
} // namespace

bool EffectiveLegacyScheme() { return g_thread_legacy_scheme ? *g_thread_legacy_scheme : bls_legacy_scheme.load(); }

void SetEffectiveLegacyScheme(bool legacy)
{
if (g_thread_legacy_scheme) {
g_thread_legacy_scheme = legacy;
} else {
bls_legacy_scheme.store(legacy);
}
}

ScopedLegacySchemeOverride::ScopedLegacySchemeOverride(bool legacy) noexcept :
m_saved{g_thread_legacy_scheme}
{
g_thread_legacy_scheme = legacy;
}

ScopedLegacySchemeOverride::~ScopedLegacySchemeOverride() noexcept
{
if (!m_committed || !m_saved) {
g_thread_legacy_scheme = m_saved;
}
}

bool ScopedLegacySchemeOverride::Active() noexcept { return g_thread_legacy_scheme.has_value(); }
} // namespace bls

static const std::unique_ptr<bls::CoreMPL> pSchemeLegacy{std::make_unique<bls::LegacySchemeMPL>()};
static const std::unique_ptr<bls::CoreMPL> pScheme(std::make_unique<bls::BasicSchemeMPL>());

Expand Down Expand Up @@ -145,7 +176,7 @@ void CBLSPublicKey::AggregateInsecure(const CBLSPublicKey& o)
{
assert(IsValid() && o.IsValid());
try {
impl = Scheme(bls::bls_legacy_scheme.load())->Aggregate({impl, o.impl});
impl = Scheme(bls::EffectiveLegacyScheme())->Aggregate({impl, o.impl});
} catch (...) {
fValid = false;
}
Expand All @@ -166,7 +197,7 @@ CBLSPublicKey CBLSPublicKey::AggregateInsecure(Span<CBLSPublicKey> pks)

CBLSPublicKey ret;
try {
ret.impl = Scheme(bls::bls_legacy_scheme.load())->Aggregate(vecPublicKeys);
ret.impl = Scheme(bls::EffectiveLegacyScheme())->Aggregate(vecPublicKeys);
ret.fValid = true;
} catch (...) {
ret.fValid = false;
Expand Down Expand Up @@ -230,7 +261,7 @@ void CBLSSignature::AggregateInsecure(const CBLSSignature& o)
{
assert(IsValid() && o.IsValid());
try {
impl = Scheme(bls::bls_legacy_scheme.load())->Aggregate({impl, o.impl});
impl = Scheme(bls::EffectiveLegacyScheme())->Aggregate({impl, o.impl});
} catch (...) {
fValid = false;
}
Expand All @@ -251,7 +282,7 @@ CBLSSignature CBLSSignature::AggregateInsecure(Span<CBLSSignature> sigs)

CBLSSignature ret;
try {
ret.impl = Scheme(bls::bls_legacy_scheme.load())->Aggregate(v);
ret.impl = Scheme(bls::EffectiveLegacyScheme())->Aggregate(v);
ret.fValid = true;
} catch (...) {
ret.fValid = false;
Expand Down Expand Up @@ -283,7 +314,7 @@ CBLSSignature CBLSSignature::AggregateSecure(Span<CBLSSignature> sigs,

CBLSSignature ret;
try {
ret.impl = Scheme(bls::bls_legacy_scheme.load())->AggregateSecure(vecPublicKeys, vecSignatures, bls::Bytes(hash.begin(), hash.size()));
ret.impl = Scheme(bls::EffectiveLegacyScheme())->AggregateSecure(vecPublicKeys, vecSignatures, bls::Bytes(hash.begin(), hash.size()));
ret.fValid = true;
} catch (...) {
ret.fValid = false;
Expand Down Expand Up @@ -322,10 +353,16 @@ bool CBLSSignature::VerifyInsecure(const CBLSPublicKey& pubKey, const uint256& h

bool CBLSSignature::VerifyInsecure(const CBLSPublicKey& pubKey, const uint256& hash) const
{
return VerifyInsecure(pubKey, hash, bls::bls_legacy_scheme.load());
return VerifyInsecure(pubKey, hash, bls::EffectiveLegacyScheme());
}

bool CBLSSignature::VerifyInsecureAggregated(Span<CBLSPublicKey> pubKeys, Span<uint256> hashes) const
{
return VerifyInsecureAggregated(pubKeys, hashes, bls::EffectiveLegacyScheme());
}

bool CBLSSignature::VerifyInsecureAggregated(Span<CBLSPublicKey> pubKeys, Span<uint256> hashes,
const bool specificLegacyScheme) const
{
if (!IsValid()) {
return false;
Expand All @@ -346,13 +383,18 @@ bool CBLSSignature::VerifyInsecureAggregated(Span<CBLSPublicKey> pubKeys, Span<u
}

try {
return Scheme(bls::bls_legacy_scheme.load())->AggregateVerify(pubKeyVec, hashes2, impl);
return Scheme(specificLegacyScheme)->AggregateVerify(pubKeyVec, hashes2, impl);
} catch (...) {
return false;
}
}

bool CBLSSignature::VerifySecureAggregated(Span<CBLSPublicKey> pks, const uint256& hash) const
{
return VerifySecureAggregated(pks, hash, bls::EffectiveLegacyScheme());
}

bool CBLSSignature::VerifySecureAggregated(Span<CBLSPublicKey> pks, const uint256& hash, const bool specificLegacyScheme) const
{
if (pks.empty()) {
return false;
Expand All @@ -365,7 +407,7 @@ bool CBLSSignature::VerifySecureAggregated(Span<CBLSPublicKey> pks, const uint25
}

try {
return Scheme(bls::bls_legacy_scheme.load())->VerifySecure(vecPublicKeys, impl, bls::Bytes(hash.begin(), hash.size()));
return Scheme(specificLegacyScheme)->VerifySecure(vecPublicKeys, impl, bls::Bytes(hash.begin(), hash.size()));
} catch (...) {
return false;
}
Expand Down
Loading
Loading