title: transaction/PruneEligibility.hpp summary: PRUNE-01 dry-run eligibility evaluator: pure structured result over transaction-key enumeration (D-03).
transaction/PruneEligibility.hpp¶
PRUNE-01 dry-run eligibility evaluator: pure structured result over transaction-key enumeration (D-03). More...
Namespaces¶
| Name |
|---|
| sgns |
| sgns::crdt |
Classes¶
| Name | |
|---|---|
| struct | sgns::PruneCandidateResult Structured verdict for one candidate transaction key. |
| struct | sgns::BlockGateResult Structured verdict of the multi-element block gate (PRUNE-03). |
| struct | sgns::PruneEvaluationResult Structured result of one evaluation pass (D-03). |
| struct | sgns::PruningConfig Pruning policy configuration (GATE-06 policy half). |
| class | sgns::PruneEligibility Read-only prune-eligibility evaluator (PRUNE-01, GATE-01). |
Types¶
| Name | |
|---|---|
| enum class uint8_t | PruneReason { UNSPENT_OUTPUT, SPENDER_NOT_RECORDED, SPENDER_UNRESOLVED, SPEND_WITHIN_WINDOW, NO_OUTPUTS, OUT_OF_PREFIX, DESERIALIZE_FAILED, NON_ELIGIBLE_SIBLING, CID_UNRESOLVED, SPENDER_PRUNED_WINDOW_SATISFIED, BLOCK_ALREADY_ERASED} Why one candidate is (or is not) prune-eligible (D-03 structured result). |
| enum class uint8_t | PrunedLookup { PRUNED, NOT_FOUND, VALUE_PRESENT_INVARIANT_VIOLATION} Tri-state verdict of the pruned-marker lookup (D-04 contract). |
Detailed Description¶
PRUNE-01 dry-run eligibility evaluator: pure structured result over transaction-key enumeration (D-03).
Date: 2026-10-19
Decides, READ-ONLY, which stored transactions are erasable by the Phase-18 TxPruner: every output genuinely consumed (D-C2) and the MOST-RECENT spend beyond the finality window (D-02 — spend recency, never creation age). Construction fails closed unless the node role is Light (GATE-01); the PruningConfig default (disabled) is the consent gate (GATE-06 policy half). The window arithmetic lives in Evaluate() and nowhere else — the TxPruner reuses this evaluator verbatim.
Types Documentation¶
enum PruneReason¶
| Enumerator | Value | Description |
|---|---|---|
| UNSPENT_OUTPUT | An output is not genuinely consumed (D-C2); synthesized zero-amount tombstones land here — they are not spend evidence. | |
| SPENDER_NOT_RECORDED | The output is genuinely consumed but no spending transaction identity was recorded (spent_by_txid unset). | |
| SPENDER_UNRESOLVED | The recorded spender's tx->CID map entry was not found, or its priority could not be read (fail closed; the pruned-marker refinement below narrows this). Existence MUST be established BEFORE any priority read: GetKeyPriority reports 0 for a missing key, which would satisfy the window vacuously. | |
| SPEND_WITHIN_WINDOW | The most-recent spend is not beyond the finality window (maxHeight − spend_height < N). | |
| NO_OUTPUTS | The transaction produces no UTXOs (GetProducedUTXOs nullopt) — no spend evidence can exist (fail closed). | |
| OUT_OF_PREFIX | The candidate key is outside the /bc- |
|
| DESERIALIZE_FAILED | The stored value could not be deserialized into a transaction (fail closed; the scan continues). | |
| NON_ELIGIBLE_SIBLING | PRUNE-03 block gate: a key the delta block carries (element OR tombstone) is allowlist-conformant but not in the eligible set — the block holds a sibling that is not itself prunable, so the BLOCK is not erasable (the tx can still be eligible on its own). | |
| CID_UNRESOLVED | PRUNE-03 block gate: an ELIGIBLE candidate's tx->CID map entry could not be resolved, so no delta-block verdict can be computed — reported as an explicit not-erasable block report (fail closed), never silently dropped from the report (WR-05). | |
| SPENDER_PRUNED_WINDOW_SATISFIED | EVIDENCE code (non-failure reason): the spender's own entries are gone because the spender was pruned (marker present, value absent). A pruned spender's own window elapsed by construction, so this spend is older still — the output's window counts as SATISFIED, explicitly (D-02/B spec point 5, never silent). | |
| BLOCK_ALREADY_ERASED | EVIDENCE code (non-failure reason): the delta block's BYTES are absent while an otherwise-eligible candidate still names it — the sibling group was already erased through the sanctioned path, so this verdict COMPLETES the stranded candidate's erase (value + map entry + marker; block staging is an idempotent no-op on the absent key). The SPENDER_PRUNED_WINDOW_SATISFIED discipline: explicit affirmative evidence, never a silent pass and never a fail-closed stranding. |
Why one candidate is (or is not) prune-eligible (D-03 structured result).
Every ineligible candidate carries at least one of these codes — nothing is silently dropped (repudiability). Codes are fail-closed: each one names a check whose failure must block erasure, EXCEPT the two affirmative-EVIDENCE codes, SPENDER_PRUNED_WINDOW_SATISFIED and BLOCK_ALREADY_ERASED (they appear on ELIGIBLE/erasable verdicts so the evidence behind the verdict is explicit, never silent).
enum PrunedLookup¶
| Enumerator | Value | Description |
|---|---|---|
| PRUNED | Marker present AND value absent: the transaction's history was physically pruned on this node. | |
| NOT_FOUND | No marker AND no value: the transaction is simply unknown here. | |
| VALUE_PRESENT_INVARIANT_VIOLATION | Marker AND value both present: violates the value-XOR-marker invariant (Phase 18 writes both sides in ONE batch, so this state signals corruption — report, never guess which side is right). |
Tri-state verdict of the pruned-marker lookup (D-04 contract).
The PRUNED-vs-NOT_FOUND distinction is Phase 19 SURV-02's answer source: "history pruned on this node" is a distinct, reportable state — never conflated with plain not-found. The invariant violation (marker AND value both present) is a state bug and is reported loudly — never guessed away.
Source code¶
#ifndef _SGNS_PRUNE_ELIGIBILITY_HPP_
#define _SGNS_PRUNE_ELIGIBILITY_HPP_
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <rapidjson/document.h>
#include "account/NodeType.hpp"
#include "account/proto/SGTransaction.pb.h"
#include "base/buffer.hpp"
#include "base/logger.hpp"
#include "outcome/outcome.hpp"
namespace sgns
{
namespace crdt
{
class GlobalDB;
}
class TransactionManager;
class UTXOManager;
enum class PruneReason : uint8_t
{
UNSPENT_OUTPUT,
SPENDER_NOT_RECORDED,
SPENDER_UNRESOLVED,
SPEND_WITHIN_WINDOW,
NO_OUTPUTS,
OUT_OF_PREFIX,
DESERIALIZE_FAILED,
NON_ELIGIBLE_SIBLING,
CID_UNRESOLVED,
SPENDER_PRUNED_WINDOW_SATISFIED,
BLOCK_ALREADY_ERASED,
};
enum class PrunedLookup : uint8_t
{
PRUNED,
NOT_FOUND,
VALUE_PRESENT_INVARIANT_VIOLATION,
};
struct PruneCandidateResult
{
std::string tx_path;
std::optional<std::string> cid;
bool eligible = false;
std::vector<PruneReason> reasons;
std::optional<uint64_t> spend_height;
};
struct BlockGateResult
{
std::string cid;
bool erasable = false;
std::vector<PruneReason> reasons;
};
struct PruneEvaluationResult
{
std::vector<PruneCandidateResult> candidates;
std::vector<BlockGateResult> block_reports;
size_t EligibleCount() const
{
return static_cast<size_t>( std::count_if( candidates.begin(),
candidates.end(),
[]( const PruneCandidateResult &c ) { return c.eligible; } ) );
}
};
struct PruningConfig
{
bool enabled = false;
std::optional<uint64_t> finality_window;
bool dry_run = false;
uint64_t max_erases_per_cycle = 100;
uint64_t settle_seconds = 60;
std::optional<std::string> disabled_reason;
static PruningConfig FromJSON( const rapidjson::Value &pruning_object );
};
class PruneEligibility
{
public:
static outcome::result<std::shared_ptr<PruneEligibility>> New( crdt::GlobalDB &globaldb,
TransactionManager &transaction_manager,
UTXOManager &utxo_manager,
NodeType node_type,
const PruningConfig &config );
bool IsAllowlistedTxKey( const std::string &key ) const;
outcome::result<PruneEvaluationResult> Evaluate() const;
outcome::result<BlockGateResult> EvaluateDeltaBlockErasable( const std::string &cid_string,
const PruneEvaluationResult &eligible ) const;
//
// Pruned-marker contract (D-04 design — Phase 17 delivers format +
// lookup semantics; the production WRITE joins Phase 18's erase batch).
//
static constexpr std::string_view PRUNE_MARKER_PREFIX = "/prune/tx/";
static std::string BuildPruneMarkerKey( const std::string &tx_hash );
static outcome::result<std::pair<PrunedLookup, std::optional<SGTransaction::PrunedTransactionRecord>>>
LookupPrunedEntry( crdt::GlobalDB &db, const std::string &tx_hash );
static std::pair<base::Buffer, base::Buffer> BuildPruneMarkerEntry( const std::string &tx_hash,
const std::string &resolved_tx_path );
static outcome::result<void> WritePrunedEntryForTest( crdt::GlobalDB &db,
const std::string &tx_hash,
const SGTransaction::PrunedTransactionRecord &record );
private:
PruneEligibility( crdt::GlobalDB &globaldb,
TransactionManager &transaction_manager,
UTXOManager &utxo_manager,
const PruningConfig &config );
PruneCandidateResult EvaluateCandidate( const std::string &key,
const base::Buffer &value,
bool allowlisted,
uint64_t max_height,
uint64_t window ) const;
crdt::GlobalDB &globaldb_m;
TransactionManager &transaction_manager_m;
UTXOManager &utxo_manager_m;
PruningConfig config_m;
base::Logger logger_ = base::createLogger( "PruneEligibility" );
};
} // namespace sgns
#endif
Updated on 2026-10-22 at 19:28:25 +0000