// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
/******************************************************************************
 * Copyright © 2014-2019 The SuperNET Developers.                             *
 *                                                                            *
 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at                  *
 * the top-level directory of this distribution for the individual copyright  *
 * holder information and the developer policies on copyright and licensing.  *
 *                                                                            *
 * Unless otherwise agreed in a custom licensing agreement, no part of the    *
 * SuperNET software, including this file may be copied, modified, propagated *
 * or distributed except according to the terms contained in the LICENSE file *
 *                                                                            *
 * Removal or modification of this copyright notice is prohibited.            *
 *                                                                            *
 ******************************************************************************/
#ifndef HUSH_CHAIN_H
#define HUSH_CHAIN_H

class CChainPower;

#include "arith_uint256.h"
#include "primitives/block.h"
#include "pow.h"
#include "tinyformat.h"
#include "uint256.h"
#include "util/strencodings.h"
#include <vector>
#include <boost/foreach.hpp>

extern bool fZindex;
// These version thresholds control whether nSproutValue/nSaplingValue are
// serialized in the block index. They must be <= CLIENT_VERSION or the
// values will never be persisted, causing nChainSaplingValue to reset
// to 0 after node restart. DragonX CLIENT_VERSION is 1030050 (v1.3.0.50).
static const int SPROUT_VALUE_VERSION = 1000000;
static const int SAPLING_VALUE_VERSION = 1000000;
// Block-index records written at >= this version store nSaplingValue as a boost::optional
// (1-byte discriminant + value). Earlier records stored it as a raw 8-byte CAmount; the
// deserializer consumes those bytes but reads the value as boost::none (untrusted) so the
// turnstile guard stays dormant until such a node reindexes. Keep == the CLIENT_VERSION that
// introduced the optional format (v1.0.3, CLIENT_VERSION 1000350).
static const int SAPLING_VALUE_OPTIONAL_VERSION = 1000350;
extern int32_t ASSETCHAINS_LWMAPOS;
extern char SMART_CHAIN_SYMBOL[65];
extern uint64_t ASSETCHAINS_NOTARY_PAY[];

struct CDiskBlockPos
{
    int nFile;
    unsigned int nPos;

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        READWRITE(VARINT(nFile));
        READWRITE(VARINT(nPos));
    }

    CDiskBlockPos() {
        SetNull();
    }

    CDiskBlockPos(int nFileIn, unsigned int nPosIn) {
        nFile = nFileIn;
        nPos = nPosIn;
    }

    friend bool operator==(const CDiskBlockPos &a, const CDiskBlockPos &b) {
        return (a.nFile == b.nFile && a.nPos == b.nPos);
    }

    friend bool operator!=(const CDiskBlockPos &a, const CDiskBlockPos &b) {
        return !(a == b);
    }

    void SetNull() { nFile = -1; nPos = 0; }
    bool IsNull() const { return (nFile == -1); }

    std::string ToString() const
    {
        return strprintf("CBlockDiskPos(nFile=%i, nPos=%i)", nFile, nPos);
    }

};

enum BlockStatus: uint32_t {
    //! Unused.
    BLOCK_VALID_UNKNOWN      =    0,

    //! Parsed, version ok, hash satisfies claimed PoW, 1 <= vtx count <= max, timestamp not in future
    BLOCK_VALID_HEADER       =    1,

    //! All parent headers found, difficulty matches, timestamp >= median previous, checkpoint. Implies all parents
    //! are also at least TREE.
    BLOCK_VALID_TREE         =    2,

    /**
     * Only first tx is coinbase, 2 <= coinbase input script length <= 100, transactions valid, no duplicate txids,
     * sigops, size, merkle root. Implies all parents are at least TREE but not necessarily TRANSACTIONS. When all
     * parent blocks also have TRANSACTIONS, CBlockIndex::nChainTx will be set.
     */
    BLOCK_VALID_TRANSACTIONS =    3,

    //! Outputs do not overspend inputs, no double spends, coinbase output ok, no immature coinbase spends, BIP30.
    //! Implies all parents are also at least CHAIN.
    BLOCK_VALID_CHAIN        =    4,

    //! Scripts & signatures ok. Implies all parents are also at least SCRIPTS.
    BLOCK_VALID_SCRIPTS      =    5,

    //! All validity bits.
    //! NOTE: the levels above are sequential VALUES occupying this 3-bit field, not independent
    //! bits, so any flag stored in nStatus must live entirely outside this mask.
    BLOCK_VALID_MASK         =   BLOCK_VALID_HEADER | BLOCK_VALID_TREE | BLOCK_VALID_TRANSACTIONS |
                                 BLOCK_VALID_CHAIN | BLOCK_VALID_SCRIPTS,

    BLOCK_HAVE_DATA          =    8, //! full block available in blk*.dat
    BLOCK_HAVE_UNDO          =   16, //! undo data available in rev*.dat
    BLOCK_HAVE_MASK          =   BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO,

    BLOCK_FAILED_VALID       =   32, //! stage after last reached validness failed
    BLOCK_FAILED_CHILD       =   64, //! descends from failed block
    BLOCK_FAILED_MASK        =   BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD,

    BLOCK_ACTIVATES_UPGRADE  =   128, //! block activates a network upgrade
    BLOCK_IN_TMPFILE         =   256,

    //! ContextualCheckBlock already passed in AcceptBlock, so ConnectBlock may skip re-running it.
    //! Was 6 until v1.3.0, which put it INSIDE BLOCK_VALID_MASK (1|2|3|4|5 == 7): `nStatus |=
    //! BLOCK_VALID_CONTEXT` then overwrote the validity level rather than setting a flag, so a
    //! block that was only written to disk read back as BLOCK_VALID_SCRIPTS and every later
    //! RaiseValidity() silently no-opped. Detected by CheckBlockIndex's "CHAIN valid implies all
    //! parents are CHAIN valid" assert, which aborts any node doing out-of-order block download
    //! (regtest only, where fDefaultConsistencyChecks is true). Validity was only ever inflated,
    //! never deflated, so ConnectBlock's full validation was never skipped -- see git history.
    //! Legacy block indexes still carry the polluted low bits; they resolve on reindex, and until
    //! then simply re-run the contextual check they used to skip.
    BLOCK_VALID_CONTEXT      =   512
};

//! The validity level is a small integer packed into BLOCK_VALID_MASK, so every other nStatus flag
//! must be disjoint from it. Enforced here so this class of bug cannot be reintroduced silently.
static_assert((BLOCK_VALID_CONTEXT     & BLOCK_VALID_MASK) == 0, "BLOCK_VALID_CONTEXT overlaps the validity-level field");
static_assert((BLOCK_HAVE_MASK         & BLOCK_VALID_MASK) == 0, "BLOCK_HAVE_MASK overlaps the validity-level field");
static_assert((BLOCK_FAILED_MASK       & BLOCK_VALID_MASK) == 0, "BLOCK_FAILED_MASK overlaps the validity-level field");
static_assert((BLOCK_ACTIVATES_UPGRADE & BLOCK_VALID_MASK) == 0, "BLOCK_ACTIVATES_UPGRADE overlaps the validity-level field");
static_assert((BLOCK_IN_TMPFILE        & BLOCK_VALID_MASK) == 0, "BLOCK_IN_TMPFILE overlaps the validity-level field");

//! Short-hand for the highest consensus validity we implement.
//! Blocks with this validity are assumed to satisfy all consensus rules.
static const BlockStatus BLOCK_VALID_CONSENSUS = BLOCK_VALID_SCRIPTS;

class CBlockIndex;

// This class provides an accumulator for chainwork
class CChainPower
{
    public:
        arith_uint256 chainWork;
        int32_t nHeight;

        CChainPower() : nHeight(0), chainWork(0) {}
        CChainPower(CBlockIndex *pblockIndex);
        CChainPower(CBlockIndex *pblockIndex, const arith_uint256 &work);
        CChainPower(int32_t height) : nHeight(height), chainWork(0) {}
        CChainPower(int32_t height, const arith_uint256 &work) : 
                    nHeight(height),  chainWork(work) {}

        CChainPower &operator=(const CChainPower &chainPower)
        {
            chainWork = chainPower.chainWork;
            nHeight = chainPower.nHeight;
            return *this;
        }

        CChainPower &operator+=(const CChainPower &chainPower)
        {
            this->chainWork += chainPower.chainWork;
            return *this;
        }

        friend CChainPower operator+(const CChainPower &chainPowerA, const CChainPower &chainPowerB)
        {
            CChainPower result = CChainPower(chainPowerA);
            result.chainWork += chainPowerB.chainWork;
            return result;
        }

        friend CChainPower operator-(const CChainPower &chainPowerA, const CChainPower &chainPowerB)
        {
            CChainPower result = CChainPower(chainPowerA);
            result.chainWork -= chainPowerB.chainWork;
            return result;
        }

        friend CChainPower operator*(const CChainPower &chainPower, int32_t x)
        {
            CChainPower result = CChainPower(chainPower);
            result.chainWork *= x;
            return result;
        }

        CChainPower &addWork(const arith_uint256 &nChainWork)
        {
            chainWork += nChainWork;
            return *this;
        }

        friend bool operator==(const CChainPower &p1, const CChainPower &p2);

        friend bool operator!=(const CChainPower &p1, const CChainPower &p2)
        {
            return !(p1 == p2);
        }

        friend bool operator<(const CChainPower &p1, const CChainPower &p2);

        friend bool operator<=(const CChainPower &p1, const CChainPower &p2);

        friend bool operator>(const CChainPower &p1, const CChainPower &p2)
        {
            return !(p1 <= p2);
        }

        friend bool operator>=(const CChainPower &p1, const CChainPower &p2)
        {
            return !(p1 < p2);
        }
};

/** The block chain is a tree shaped structure starting with the
 * genesis block at the root, with each block potentially having multiple
 * candidates to be the next block. A blockindex may have multiple pprev pointing
 * to it, but at most one of them can be part of the currently active branch.
 */
class CBlockIndex
{
public:
    //! pointer to the hash of the block, if any. Memory is owned by this CBlockIndex
    const uint256* phashBlock;

    //! pointer to the index of the predecessor of this block
    CBlockIndex* pprev;

    //! pointer to the index of some further predecessor of this block
    CBlockIndex* pskip;

    //! height of the entry in the chain. The genesis block has height 0
    int64_t newcoins,zfunds,sproutfunds,nNotaryPay; int8_t segid; // jl777 fields
    //! Which # file this block is stored in (blk?????.dat)
    int nFile;

    //! Byte offset within blk?????.dat where this block's data is stored
    unsigned int nDataPos;

    //! Byte offset within rev?????.dat where this block's undo data is stored
    unsigned int nUndoPos;

    //! (memory only) Total amount of work (expected number of hashes) in the chain up to and including this block
    CChainPower chainPower;

    //! Number of transactions in this block.
    //! Note: in a potential headers-first mode, this number cannot be relied upon
    unsigned int nTx;

    //! Number of notarization transactions in this block.
    int64_t nNotarizations;

    //! (memory only) Number of payments (shielded or transparent) in the block
    //! up to and including this block.  One transaction can contain one or more
    //! payments. This stat allows us to calculate ratios of shielded/transparent
    //! when combined with shielded payment stats
    int64_t nPayments;

    //! (memory only) Number of shielded transactions (of any kind) in the block up to and including this block.
    //! A shielded transaction is defined as a transaction that contains at least 1 ShieldedInput or ShieldedOutput
    //! shielding/de-shielding and other complex transaction possibilties including multiple taddrs/zaddrs as
    //! inputs and outputs.
    int64_t nShieldedTx;

    //! (memory only) Number of shielded outputs
    int64_t nShieldedOutputs;

    //! (memory only) Number of shielded spends
    int64_t nShieldedSpends;

    //! (memory only) Number of fully shielded transactions. A fully shielded transaction is defined
    //! as a transaction containing only shielded inputs and outputs, i.e. no transparent
    // inputs or outputs: z->z or z->(z,z) or z->(z,z,z,) etc...
    int64_t nFullyShieldedTx;

    //! (memory only) Number of shielding payments. A shielding payment is defined
    //! as having a shielded output but transparent input: t->z
    int64_t nShieldingPayments;

    //! (memory only) Number of shielded payments. A shielded payment is defined
    //! as having a shielded input or output: t->z or z->t
    int64_t nShieldedPayments;

    //! (memory only) Number of fully shielded payments. A fully shielded payment is defined
    //! as having a shielded input and shielded output: z->z
    int64_t nFullyShieldedPayments;

    //! (memory only) Number of deshielding transactions. A deshielding transaction is defined
    //! as a transaction containing ShieldedInputs and at least one transparent output.
    int64_t nDeshieldingTx;

    //! (memory only) Number of deshielding payments. A deshielding payment is defined
    //! as one transparent input and one shielded output: z->t
    int64_t nDeshieldingPayments;

    //! (memory only) Number of shielding transactions. A shielding transaction is defined
    //! as a transaction containing ShieldedOutputs and at least one transparent input
    // i.e. t->z or t->(z,t) or z->(z,z,t)
    int64_t nShieldingTx;

    //! (memory only) Number of transactions in the chain up to and including this block.
    //! This value will be non-zero only if and only if transactions for this block and all its parents are available.
    //! Change to 64-bit type when necessary; won't happen before 2030
    unsigned int nChainTx;

    //! Number of notarization transactions in this chain
    int64_t nChainNotarizations;

    //! (memory only) Number of payments (shielded or transparent) in the chain
    //! up to and including this block.  One transaction can contain one or more
    //! payments. This stat allows us to calculate ratios of shielded/transparent
    //! when combined with shielded payment stats
    int64_t nChainPayments;

    //! (memory only) Number of shielded transactions (of any kind) in the chain up to and including this block.
    //! A shielded transaction is defined as a transaction that contains at least 1 ShieldedInput or ShieldedOutput
    //! shielding/de-shielding and other complex transaction possibilties including multiple taddrs/zaddrs as
    //! inputs and outputs.
    int64_t nChainShieldedTx;

    //! (memory only) Number of shielded outputs in the chain up to and including this block.
    int64_t nChainShieldedOutputs;

    //! (memory only) Number of shielded spends in the chain up to and including this block.
    int64_t nChainShieldedSpends;

    //! (memory only) Number of fully shielded transactions. A fully shielded transaction is defined
    //! as a transaction containing and only shielded inputs and outputs, i.e. no transparent
    // inputs or outputs: z->z or z->(z,z) or z->(z,z,z,) etc...
    int64_t nChainFullyShieldedTx;

    //! (memory only) Number of shielding payments. A shielding payment is defined
    //! as having a shielded output but transparent input: t->z
    int64_t nChainShieldingPayments;

    //! (memory only) Number of shielded payments. A shielded payment is defined
    //! as having a shielded input or output: t->z or z->t
    int64_t nChainShieldedPayments;

    //! (memory only) Number of fully shielded payments. A fully shielded payment is defined
    //! as having a shielded input and shielded output: z->z
    int64_t nChainFullyShieldedPayments;

    //! (memory only) Number of deshielding transactions. A deshielding transaction is defined
    //! as a transaction containing ShieldedInputs and at least one transparent output.
    int64_t nChainDeshieldingTx;

    //! (memory only) Number of deshielding payments. A deshielding payment is defined
    //! as one transparent input and one shielded output: z->t
    int64_t nChainDeshieldingPayments;

    //! (memory only) Number of shielding transactions. A shielding transaction is defined
    //! as a transaction containing ShieldedOutputs and at least one transparent input
    // i.e. t->z or t->(z,t) or z->(z,z,t)
    int64_t nChainShieldingTx;

    //! Verification status of this block. See enum BlockStatus
    unsigned int nStatus;

    //! Branch ID corresponding to the consensus rules used to validate this block.
    //! Only cached if block validity is BLOCK_VALID_CONSENSUS.
    //! Persisted at each activation height, memory-only for intervening blocks.
    boost::optional<uint32_t> nCachedBranchId;

    //! The anchor for the tree state up to the start of this block
    uint256 hashSproutAnchor;

    //! (memory only) The anchor for the tree state up to the end of this block
    uint256 hashFinalSproutRoot;

    //! Change in value held by the Sprout circuit over this block.
    //! Will be boost::none for older blocks on old nodes until a reindex has taken place.
    boost::optional<CAmount> nSproutValue;

    //! (memory only) Total value held by the Sprout circuit up to and including this block.
    //! Will be boost::none for on old nodes until a reindex has taken place.
    //! Will be boost::none if nChainTx is zero.
    boost::optional<CAmount> nChainSproutValue;

    //! Change in value held by the Sapling circuit over this block. boost::none for blocks
    //! before nSaplingValue was tracked, or on nodes that loaded an older-format block index
    //! (see SAPLING_VALUE_OPTIONAL_VERSION) -- propagates to nChainSaplingValue == none.
    boost::optional<CAmount> nSaplingValue;

    //! (memory only) Total value held by the Sapling circuit up to and including this block.
    //! Will be boost::none if nChainTx is zero.
    boost::optional<CAmount> nChainSaplingValue;

    //! block header
    int nVersion;
    uint256 hashMerkleRoot;
    uint256 hashFinalSaplingRoot;
    unsigned int nTime;
    unsigned int nBits;
    uint256 nNonce;
protected:
    // The Equihash solution, if it is stored. Once we know that the block index
    // entry is present in leveldb, this field can be cleared via the TrimSolution
    // method to save memory.
    std::vector<unsigned char> nSolution;

public:

    //! (memory only) Sequential id assigned to distinguish order in which blocks are received.
    uint32_t nSequenceId;

    //! (memory only) Set true once this block's RandomX PoW has been verified by the parallel
    //! pre-verification pool, letting the inline check in CheckBlockHeader skip the recompute.
    //! Written by exactly one pre-verify worker (1:1 with the block) and read by the connect
    //! thread only AFTER the pool barrier (CCheckQueue::Wait provides the happens-before), so a
    //! plain bool is race-free here. NOT serialized — a pure optimization hint; the inline
    //! CheckRandomXSolution remains the consensus authority. (Plain bool, not std::atomic, so
    //! CBlockIndex stays copyable for CDiskBlockIndex's `CBlockIndex(*pindex)` construction.)
    bool fRandomXVerified;

    void SetNull()
    {
        phashBlock = NULL;
        newcoins = zfunds = 0;
        segid = -2;
        nNotaryPay = 0;
        pprev = NULL;
        pskip = NULL;
        nFile = 0;
        nDataPos = 0;
        nUndoPos = 0;
        chainPower = CChainPower();
        nTx = 0;
        nChainTx = 0;
        fRandomXVerified = false;

        // Shieldex Index chain stats
        nChainPayments = 0;
        nChainShieldedTx = 0;
        nChainShieldingTx = 0;
        nChainDeshieldingTx = 0;
        nChainNotarizations = 0;
        nChainFullyShieldedTx = 0;
        nChainShieldedOutputs = 0;
        nChainShieldedSpends  = 0;
        nChainShieldedPayments = 0;
        nChainShieldingPayments = 0;
        nChainDeshieldingPayments = 0;
        nChainFullyShieldedPayments = 0;

        // Shielded Index stats
        nPayments = 0;
        nShieldedTx = 0;
        nShieldingTx = 0;
        nNotarizations = 0;
        nDeshieldingTx = 0;
        nShieldedOutputs = 0;
        nShieldedSpends  = 0;
        nFullyShieldedTx = 0;
        nShieldedPayments = 0;
        nShieldingPayments = 0;
        nDeshieldingPayments = 0;
        nFullyShieldedPayments = 0;

        nStatus = 0;
        nCachedBranchId = boost::none;
        hashSproutAnchor = uint256();
        hashFinalSproutRoot = uint256();
        nSequenceId = 0;
        nSproutValue = boost::none;
        nChainSproutValue = boost::none;
        nSaplingValue = boost::none;
        nChainSaplingValue = boost::none;

        nVersion       = 0;
        hashMerkleRoot = uint256();
        hashFinalSaplingRoot   = uint256();
        nTime          = 0;
        nBits          = 0;
        nNonce         = uint256();
        nSolution.clear();
    }

    CBlockIndex()
    {
        SetNull();
    }

    CBlockIndex(const CBlockHeader& block)
    {
        SetNull();

        nVersion       = block.nVersion;
        hashMerkleRoot = block.hashMerkleRoot;
        hashFinalSaplingRoot   = block.hashFinalSaplingRoot;
        nTime          = block.nTime;
        nBits          = block.nBits;
        nNonce         = block.nNonce;
        nSolution      = block.nSolution;
    }

    void SetHeight(int32_t height)
    {
        this->chainPower.nHeight = height;
    }

    inline int32_t GetHeight() const
    {
        return this->chainPower.nHeight;
    }

    CDiskBlockPos GetBlockPos() const {
        CDiskBlockPos ret;
        if (nStatus & BLOCK_HAVE_DATA) {
            ret.nFile = nFile;
            ret.nPos  = nDataPos;
        }
        return ret;
    }

    CDiskBlockPos GetUndoPos() const {
        CDiskBlockPos ret;
        if (nStatus & BLOCK_HAVE_UNDO) {
            ret.nFile = nFile;
            ret.nPos  = nUndoPos;
        }
        return ret;
    }

    //! Get the block header for this block index. Requires cs_main.
    CBlockHeader GetBlockHeader() const;

    //! Clear the Equihash solution to save memory. Requires cs_main.
    void TrimSolution();

    uint256 GetBlockHash() const
    {
        assert(phashBlock);
        return *phashBlock;
    }

    int64_t GetBlockTime() const
    {
        return (int64_t)nTime;
    }

    enum { nMedianTimeSpan=11 };

    int64_t GetMedianTimePast() const
    {
        int64_t pmedian[nMedianTimeSpan];
        int64_t* pbegin = &pmedian[nMedianTimeSpan];
        int64_t* pend = &pmedian[nMedianTimeSpan];

        const CBlockIndex* pindex = this;
        for (int i = 0; i < nMedianTimeSpan && pindex; i++, pindex = pindex->pprev)
            *(--pbegin) = pindex->GetBlockTime();

        std::sort(pbegin, pend);
        return pbegin[(pend - pbegin)/2];
    }

    std::string ToString() const
    {
        return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s, HasSolution=%s)",
            pprev, this->chainPower.nHeight,
            hashMerkleRoot.ToString(),
            phashBlock ? GetBlockHash().ToString() : "(nil)",
            HasSolution());
    }

    //! Check whether this block index entry is valid up to the passed validity level.
    bool IsValid(enum BlockStatus nUpTo = BLOCK_VALID_TRANSACTIONS) const
    {
        assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
        if (nStatus & BLOCK_FAILED_MASK)
            return false;
        return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
    }

    //! Is the Equihash solution stored?
    bool HasSolution() const
    {
        return !nSolution.empty();
    }

    //! Raise the validity level of this block index entry.
    //! Returns true if the validity was changed.
    bool RaiseValidity(enum BlockStatus nUpTo)
    {
        assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
        if (nStatus & BLOCK_FAILED_MASK)
            return false;
        if ((nStatus & BLOCK_VALID_MASK) < nUpTo) {
            nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo;
            return true;
        }
        return false;
    }

    //! Build the skiplist pointer for this entry.
    void BuildSkip();

    //! Efficiently find an ancestor of this block.
    CBlockIndex* GetAncestor(int height);
    const CBlockIndex* GetAncestor(int height) const;

};

/** Used to marshal pointers into hashes for db storage. */
class CDiskBlockIndex : public CBlockIndex
{
public:
    uint256 hashPrev;

    CDiskBlockIndex() : CBlockIndex() {
        hashPrev = uint256();
    }

    explicit CDiskBlockIndex(const CBlockIndex* pindex, std::function<std::vector<unsigned char>()> getSolution) : CBlockIndex(*pindex) {
        hashPrev = (pprev ? pprev->GetBlockHash() : uint256());
        if (!HasSolution()) {
            nSolution = getSolution();
        }
    }

    ADD_SERIALIZE_METHODS;

    template <typename Stream, typename Operation>
    inline void SerializationOp(Stream& s, Operation ser_action) {
        int nVersion = s.GetVersion();
        if (!(s.GetType() & SER_GETHASH))
            READWRITE(VARINT(nVersion));

        if (ser_action.ForRead()) {
            chainPower = CChainPower();
        }
        READWRITE(VARINT(chainPower.nHeight));
        READWRITE(VARINT(nStatus));
        READWRITE(VARINT(nTx));


        if (nStatus & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO))
            READWRITE(VARINT(nFile));
        if (nStatus & BLOCK_HAVE_DATA)
            READWRITE(VARINT(nDataPos));
        if (nStatus & BLOCK_HAVE_UNDO)
            READWRITE(VARINT(nUndoPos));
        if (nStatus & BLOCK_ACTIVATES_UPGRADE) {
            if (ser_action.ForRead()) {
                uint32_t branchId;
                READWRITE(branchId);
                nCachedBranchId = branchId;
            } else {
                // nCachedBranchId must always be set if BLOCK_ACTIVATES_UPGRADE is set.
                assert(nCachedBranchId);
                uint32_t branchId = *nCachedBranchId;
                READWRITE(branchId);
            }
        }
        READWRITE(hashSproutAnchor);

        // block header
        READWRITE(this->nVersion);
        READWRITE(hashPrev);
        READWRITE(hashMerkleRoot);
        READWRITE(hashFinalSaplingRoot);
        READWRITE(nTime);
        READWRITE(nBits);
        READWRITE(nNonce);
        READWRITE(nSolution);

        // Only read/write nSproutValue if the client version used to create
        // this index was storing them.
        if ((s.GetType() & SER_DISK) && (nVersion >= SPROUT_VALUE_VERSION)) {
            READWRITE(nSproutValue);
        }

        // nSaplingValue is a boost::optional so "not reliably tracked" reads back as none,
        // keeping the turnstile guard dormant on old/snapshot-bootstrapped DBs. Records written
        // before SAPLING_VALUE_OPTIONAL_VERSION stored it as a raw 8-byte CAmount: consume those
        // bytes for alignment but DISCARD the value (read as none), since it may be understated on
        // a node that never tracked the pool from genesis. That node stays dormant until it
        // reindexes, which rewrites records at the current version -> the optional path below.
        if (s.GetType() & SER_DISK) {
            if (nVersion >= SAPLING_VALUE_OPTIONAL_VERSION) {
                READWRITE(nSaplingValue);              // new format: boost::optional<CAmount>
            } else if (nVersion >= SAPLING_VALUE_VERSION) {
                CAmount nLegacySaplingValue = 0;       // old format: raw 8 bytes present in the stream
                READWRITE(nLegacySaplingValue);        // consume for alignment; value is discarded
                if (ser_action.ForRead())
                    nSaplingValue = boost::none;
            }
            // else (< SAPLING_VALUE_VERSION): field was never stored -> nSaplingValue stays none
        }

        // These values only serialized when -zindex enabled
        // Order is important!
        if((s.GetType() & SER_DISK) && fZindex) {
            READWRITE(nShieldedTx);
            READWRITE(nShieldingTx);
            READWRITE(nDeshieldingTx);
            READWRITE(nFullyShieldedTx);
            READWRITE(nPayments);
            READWRITE(nNotarizations);
            READWRITE(nShieldedPayments);
            READWRITE(nShieldingPayments);
            READWRITE(nDeshieldingPayments);
            READWRITE(nFullyShieldedPayments);
            READWRITE(nShieldedOutputs);
            READWRITE(nShieldedSpends);
        }
    }

    uint256 GetBlockHash() const
    {
        return GetBlockHeader().GetHash();
    }

    //! Get the block header for this block index.
    CBlockHeader GetBlockHeader() const
    {
        CBlockHeader header;
        header.nVersion             = nVersion;
        header.hashPrevBlock        = hashPrev;
        header.hashMerkleRoot       = hashMerkleRoot;
        header.hashFinalSaplingRoot = hashFinalSaplingRoot;
        header.nTime                = nTime;
        header.nBits                = nBits;
        header.nNonce               = nNonce;
        header.nSolution            = nSolution;
        return header;
    }

    std::vector<unsigned char> GetSolution() const
    {
        assert(HasSolution());
        return nSolution;
    }

    std::string ToString() const
    {
        std::string str = "CDiskBlockIndex(";
        str += CBlockIndex::ToString();
        str += strprintf("\n                hashBlock=%s, hashPrev=%s)",
            GetBlockHash().ToString(),
            hashPrev.ToString());
        return str;
    }

private:
    //! This method should not be called on a CDiskBlockIndex.
    void TrimSolution()
    {
        assert(!"called CDiskBlockIndex::TrimSolution");
    }
};

/** An in-memory indexed chain of blocks. */
class CChain {
private:
    std::vector<CBlockIndex*> vChain;
    CBlockIndex *lastTip;

public:
    /** Returns the index entry for the genesis block of this chain, or NULL if none. */
    CBlockIndex *Genesis() const {
        return vChain.size() > 0 ? vChain[0] : NULL;
    }

    /** Returns the index entry for the tip of this chain, or NULL if none. */
    CBlockIndex *Tip() const {
        return vChain.size() > 0 ? vChain[vChain.size() - 1] : NULL;
    }
    
    /** Returns the last tip of the chain, or NULL if none. */
    CBlockIndex *LastTip() const {
        return vChain.size() > 0 ? lastTip : NULL;
    }

    /** Returns the index entry at a particular height in this chain, or NULL if no such height exists. */
    CBlockIndex *operator[](int nHeight) const {
        if (nHeight < 0 || nHeight >= (int)vChain.size())
            return NULL;
        return vChain[nHeight];
    }

    /** Compare two chains efficiently. */
    friend bool operator==(const CChain &a, const CChain &b) {
        return a.vChain.size() == b.vChain.size() &&
               a.vChain[a.vChain.size() - 1] == b.vChain[b.vChain.size() - 1];
    }

    /** Efficiently check whether a block is present in this chain. */
    bool Contains(const CBlockIndex *pindex) const {
        return (*this)[pindex->GetHeight()] == pindex;
    }

    /** Find the successor of a block in this chain, or NULL if the given index is not found or is the tip. */
    CBlockIndex *Next(const CBlockIndex *pindex) const {
        if (Contains(pindex))
            return (*this)[pindex->GetHeight() + 1];
        else
            return NULL;
    }

    /** Return the maximal height in the chain. Is equal to chain.Tip() ? chain.Tip()->GetHeight() : -1. */
    int Height() const {
        return vChain.size() - 1;
    }

    /** Set/initialize a chain with a given tip. */
    void SetTip(CBlockIndex *pindex);

    /** Return a CBlockLocator that refers to a block in this chain (by default the tip). */
    CBlockLocator GetLocator(const CBlockIndex *pindex = NULL) const;

    /** Find the last common block between this chain and a block index entry. */
    const CBlockIndex *FindFork(const CBlockIndex *pindex) const;
};

#endif // HUSH_CHAIN_H
