Skip to content

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-/tx/ allowlist (defensive; the enumeration prefix already excludes such keys — reported, never dropped).
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