Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
487cd61
fix(evo): pass snapshot-data and mismatch errors through generic catches
PastaPastaPasta Oct 15, 2026
4158f5b
refactor(evo): add EvoDB view tokens and scopes
PastaPastaPasta Oct 15, 2026
ca6a1b9
feat(evo): store snapshot-identity state in a generation namespace wi…
PastaPastaPasta Oct 15, 2026
c42edcf
fix(evo): partition Evo-derived caches by view
PastaPastaPasta Oct 15, 2026
1bd2c26
fix(evo): bind non-active paths and dispatcher boundaries explicitly
PastaPastaPasta Oct 15, 2026
87c5a9e
feat(evo): reject unbound Evo reads during dual operation
PastaPastaPasta Oct 15, 2026
3d54024
feat(evo): v4 snapshot with derived rotation dependencies and bootstr…
PastaPastaPasta Oct 15, 2026
8c8faaa
feat(evo): carry and consume the credit-pool window
PastaPastaPasta Oct 15, 2026
bf26101
feat(evo): carry the header-proven base coinbase and use it for B+1 C…
PastaPastaPasta Oct 15, 2026
8315c86
fix(evo): export the raw MNHF signal record at the base
PastaPastaPasta Oct 16, 2026
f86b477
fix(llmq): exact work modifiers and no silent degradation under the s…
PastaPastaPasta Oct 15, 2026
ae40017
feat(llmq): prove ChainLock and asset-unlock scan coverage
PastaPastaPasta Oct 15, 2026
a81015b
feat(rpc): emit the v4 evo section in dumptxoutset
PastaPastaPasta Oct 15, 2026
c23d576
feat(validation): require a matching evo pin; add regtest -assumeutxo…
PastaPastaPasta Oct 15, 2026
0d30fd8
feat(validation): seed into an unpublished generation, dry-run, then …
PastaPastaPasta Oct 15, 2026
0c4a36b
feat(validation): rebuild and compare the full evo section at completion
PastaPastaPasta Oct 15, 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
8 changes: 8 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,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 @@ -570,6 +573,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 @@ -1299,6 +1304,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
35 changes: 21 additions & 14 deletions src/chainlock/handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ ChainlockHandler::ChainlockHandler(chainlock::Chainlocks& chainlocks, Chainstate
std::make_unique<std::thread>(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))},
seenChainLocks{SEEN_CHAINLOCKS_CUTOFF_SIZE, SEEN_CHAINLOCKS_MAX_SIZE}
{
// Tasks are scheduled only from Start(), after this is set.
scheduler->SetTaskWrapper(
[this](const CScheduler::Function& task) { RunActiveEvoRound(m_chainman.EvoDb(), "cl-schdlr", task); });
}

ChainlockHandler::~ChainlockHandler()
Expand Down Expand Up @@ -168,12 +171,11 @@ MessageProcessingResult ChainlockHandler::ProcessNewChainLock(const NodeId from,
if (!m_chainlocks.UpdateBestChainlock(hash, clsig, pindex)) return {};

if (pindex) {
scheduler->scheduleFromNow(
[&]() {
CheckActiveState();
EnforceBestChainLock();
},
std::chrono::seconds{0});
scheduler->scheduleFromNow(WithCapturedEvoView([&]() {
CheckActiveState();
EnforceBestChainLock();
}),
std::chrono::seconds{0});

LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__,
clsig.ToString(), from);
Expand All @@ -195,14 +197,19 @@ void ChainlockHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBloc
// atomic[If tryLockChainTipScheduled is false, do (set it to true] and schedule signing).

if (bool expected = false; tryLockChainTipScheduled.compare_exchange_strong(expected, true)) {
scheduler->scheduleFromNow(
[&]() {
CheckActiveState();
EnforceBestChainLock();
Cleanup();
tryLockChainTipScheduled = false;
},
std::chrono::seconds{0});
const auto unschedule{[this] { tryLockChainTipScheduled = false; }};
scheduler->scheduleFromNow(WithCapturedEvoView(
[&, unschedule]() {
const struct Unschedule {
decltype(unschedule) f;
~Unschedule() { f(); }
} done{unschedule};
CheckActiveState();
EnforceBestChainLock();
Cleanup();
},
unschedule),
std::chrono::seconds{0});
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/chainlock/signing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ ChainLockSigner::ChainLockSigner(const ChainstateManager& chainman, const chainl
m_scheduler_thread{
std::make_unique<std::thread>(std::thread(util::TraceThread, "cls-schdlr", [&] { m_scheduler->serviceQueue(); }))}
{
// Tasks are scheduled only from Start(), after this is set.
m_scheduler->SetTaskWrapper(
[this](const CScheduler::Function& task) { RunActiveEvoRound(m_chainman.EvoDb(), "cls-schdlr", task); });
}

ChainLockSigner::~ChainLockSigner() { Stop(); }
Expand Down
27 changes: 27 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,31 @@ static void ReadRegTestLLMQInstantSendDIP0024Args(const ArgsManager& args, CChai
options.llmq_dip0024_instantsend = args.GetArg("-llmqtestinstantsenddip0024", "");
}

static void ReadRegTestAssumeutxoArgs(const ArgsManager& args, CChainParams::RegTestOptions& options)
{
for (const std::string& arg : args.GetArgs("-assumeutxodata")) {
const std::vector<std::string> fields{SplitString(arg, ':')};
const auto valid_hash = [](const std::string& value) {
return value.size() == uint256::size() * 2 && IsHex(value) && !uint256S(value).IsNull();
};
int32_t height;
uint32_t n_chain_tx;
if (fields.size() != 5 || !ParseInt32(fields[0], &height) || height <= 0 || !valid_hash(fields[1]) ||
!valid_hash(fields[2]) || !ParseUInt32(fields[3], &n_chain_tx) || n_chain_tx == 0 ||
!valid_hash(fields[4])) {
throw std::runtime_error(strprintf(
"Invalid value (%s) for -assumeutxodata=<height>:<hash_serialized>:<evo_hash>:<nchaintx>:<blockhash>",
arg));
}
options.assumeutxo_data.emplace_back(height, AssumeutxoData{
.hash_serialized = AssumeutxoHash{uint256S(fields[1])},
.evo_hash = EvoSnapshotHash{uint256S(fields[2])},
.nChainTx = n_chain_tx,
.blockhash = uint256S(fields[4]),
});
}
}

void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& options)
{
if (auto value = args.GetBoolArg("-fastprune")) options.fastprune = *value;
Expand All @@ -206,6 +231,7 @@ void ReadRegTestArgs(const ArgsManager& args, CChainParams::RegTestOptions& opti
ReadRegTestLLMQTestArgs(args, options, Consensus::LLMQType::LLMQ_TEST_INSTANTSEND);
ReadRegTestLLMQTestArgs(args, options, Consensus::LLMQType::LLMQ_TEST_PLATFORM);
ReadRegTestLLMQInstantSendDIP0024Args(args, options);
ReadRegTestAssumeutxoArgs(args, options);
}

static void ReadDevNetSubsidyAndDiffArgs(const ArgsManager& args, CChainParams::DevNetOptions& options)
Expand Down Expand Up @@ -323,6 +349,7 @@ void SetupChainParamsOptions(ArgsManager& argsman)
{
SetupChainParamsBaseOptions(argsman);

argsman.AddArg("-assumeutxodata=<height>:<hash_serialized>:<evo_hash>:<nchaintx>:<blockhash>", "Add an exact AssumeUTXO snapshot authorization (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-budgetparams=<masternode>:<budget>:<superblock>", "Override masternode, budget and superblock start heights (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-dip3params=<activation>:<enforcement>", "Override DIP3 activation and enforcement heights (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS);
argsman.AddArg("-highsubsidyblocks=<n>", "The number of blocks with a higher than normal subsidy to mine at the start of a chain. Block after that height will have fixed subsidy base. (default: 0, devnet-only)", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS);
Expand Down
23 changes: 22 additions & 1 deletion src/consensus/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,28 @@ class ValidationState
}
};

/** Why a block could not be processed against this node's own Evo state. This
* never describes the block itself, so it accompanies an error, not a
* BlockValidationResult. */
enum class LocalEvoStateResult {
NONE = 0, //!< not a local Evo state error
UNAVAILABLE, //!< state the block needs is not available to this chainstate
BINDING, //!< the operation was not bound to this chainstate's Evo view
MISMATCH, //!< state disagrees with the snapshot, which could not be rejected
};

class TxValidationState : public ValidationState<TxValidationResult> {};
class BlockValidationState : public ValidationState<BlockValidationResult> {};
class BlockValidationState : public ValidationState<BlockValidationResult>
{
LocalEvoStateResult m_local_evo_result{LocalEvoStateResult::NONE};

public:
bool LocalEvoStateError(LocalEvoStateResult result, const std::string& reject_reason)
{
if (IsValid()) m_local_evo_result = result;
return Error(reject_reason);
}
LocalEvoStateResult GetLocalEvoStateResult() const { return m_local_evo_result; }
};

#endif // BITCOIN_CONSENSUS_VALIDATION_H
52 changes: 44 additions & 8 deletions src/evo/cbtx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <evo/cbtx.h>

#include <evo/evodb.h>
#include <evo/snapshot_types.h>
#include <evo/specialtx.h>
#include <llmq/blockprocessor.h>
#include <llmq/commitment.h>
Expand Down Expand Up @@ -177,6 +179,29 @@ std::string CCbTx::ToString() const
creditPoolBalance / COIN, creditPoolBalance % COIN, merkleRootAssetUnlocks.ToString());
}

std::optional<CCbTx> DecodeCbTxPayload(const CTransaction& tx, bool legacy_bls_scheme)
{
if (!tx.IsCoinBase() || tx.nType != TRANSACTION_COINBASE) return std::nullopt;
CDataStream ds(tx.vExtraPayload, SER_NETWORK, PROTOCOL_VERSION);
CCbTx cbtx;
try {
ds >> cbtx.nVersion >> cbtx.nHeight >> cbtx.merkleRootMNList;
if (cbtx.nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) {
ds >> cbtx.merkleRootQuorums;
if (cbtx.nVersion >= CCbTx::Version::CLSIG_AND_BALANCE) {
ds >> COMPACTSIZE(cbtx.bestCLHeightDiff);
ds >> CBLSSignatureVersionWrapper(cbtx.bestCLSignature, legacy_bls_scheme);
ds >> cbtx.creditPoolBalance;
if (cbtx.nVersion >= CCbTx::Version::MERKLE_ROOT_ASSETUNLOCKS) ds >> cbtx.merkleRootAssetUnlocks;
}
}
} catch (const std::exception&) {
return std::nullopt;
}
if (!ds.empty()) return std::nullopt;
return cbtx;
}

std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex)
{
if (pindex == nullptr) {
Expand All @@ -190,20 +215,31 @@ std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(co

CBlock block;
if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
// A snapshot view lacks the bodies at or below its base, where a
// coinbase ChainLock that cannot be read cannot be told from none.
if (const auto view{EvoViewScope::InnermostAny()};
view && view->identity == EvoDbIdentity::SNAPSHOT && pindex->nHeight <= view->base_height &&
!WITH_LOCK(::cs_main, return pindex->nStatus & BLOCK_HAVE_DATA)) {
throw BlockDataUnavailableError(
strprintf("coinbase of block %s at or below the snapshot base is not available",
pindex->GetBlockHash().ToString()));
}
return std::nullopt;
}

return GetNonNullCoinbaseChainlock(block, pindex->nHeight);
return GetNonNullCoinbaseChainlock(block, *pindex, Params().GetConsensus());
}

std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlock& block, int32_t height)
std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlock& block, const CBlockIndex& index,
const Consensus::Params& consensus)
{
if (block.vtx.empty() || !block.vtx[0]->IsCoinBase() || block.vtx[0]->nType != TRANSACTION_COINBASE) {
return std::nullopt;
}
const auto payload = GetTxPayload<CCbTx>(*block.vtx[0]);
if (!payload || payload->nVersion < CCbTx::Version::CLSIG_AND_BALANCE || payload->nHeight != height ||
!payload->bestCLSignature.IsValid() || payload->bestCLHeightDiff >= uint32_t(height)) {
if (block.vtx.empty()) return std::nullopt;
// The signature encoding is the block's own scheme, not the one this node
// happens to be in (a background chainstate connects pre-v19 blocks in
// the legacy scheme while other threads read v19 blocks).
const auto payload{DecodeCbTxPayload(*block.vtx[0], !DeploymentActiveAt(index, consensus, Consensus::DEPLOYMENT_V19))};
if (!payload || payload->nVersion < CCbTx::Version::CLSIG_AND_BALANCE || payload->nHeight != index.nHeight ||
!payload->bestCLSignature.IsValid() || payload->bestCLHeightDiff >= static_cast<uint32_t>(index.nHeight)) {
return std::nullopt;
}
return std::make_pair(payload->bestCLSignature, payload->bestCLHeightDiff);
Expand Down
11 changes: 10 additions & 1 deletion src/evo/cbtx.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
class BlockValidationState;
class CBlock;
class CBlockIndex;
namespace Consensus {
struct Params;
} // namespace Consensus
class CDeterministicMNList;
class TxValidationState;
struct RPCResult;
Expand Down Expand Up @@ -83,7 +86,13 @@ bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPre
BlockValidationState& state);
uint256 CalcCbTxMerkleRootAssetUnlocks(const CBlock& block);

/** Decode a coinbase's CbTx payload with an explicit BLS scheme for its
* ChainLock signature, independent of the scheme the node is in. */
std::optional<CCbTx> DecodeCbTxPayload(const CTransaction& tx, bool legacy_bls_scheme);
std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);
std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlock& block, int32_t height);
/** The non-null ChainLock in `block`'s coinbase, decoded with the BLS scheme in
* force at `index`, the block's own index. */
std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlock& block, const CBlockIndex& index,
const Consensus::Params& consensus);

#endif // BITCOIN_EVO_CBTX_H
Loading
Loading