//------------------------------------------------------------------------------
/*
    This file is part of rippled: https://github.com/ripple/rippled
    Copyright (c) 2012, 2013 Ripple Labs Inc.

    Permission to use, copy, modify, and/or distribute this software for any
    purpose  with  or without fee is hereby granted, provided that the above
    copyright notice and this permission notice appear in all copies.

    THE  SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    WITH  REGARD  TO  THIS  SOFTWARE  INCLUDING  ALL  IMPLIED  WARRANTIES  OF
    MERCHANTABILITY  AND  FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    ANY  SPECIAL ,  DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    WHATSOEVER  RESULTING  FROM  LOSS  OF USE, DATA OR PROFITS, WHETHER IN AN
    ACTION  OF  CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================

#include <ripple/protocol/Feature.h>

#include <ripple/basics/Slice.h>
#include <ripple/basics/contract.h>
#include <ripple/protocol/digest.h>
#include <boost/container_hash/hash.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/key_extractors.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index_container.hpp>
#include <cstring>

namespace ripple {

inline std::size_t
hash_value(ripple::uint256 const& feature)
{
    std::size_t seed = 0;
    using namespace boost;
    for (auto const& n : feature)
        hash_combine(seed, n);
    return seed;
}

namespace {

enum class Supported : bool { no = false, yes };

// *NOTE*
//
// Features, or Amendments as they are called elsewhere, are enabled on the
// network at some specific time based on Validator voting.  Features are
// enabled using run-time conditionals based on the state of the amendment.
// There is value in retaining that conditional code for some time after
// the amendment is enabled to make it simple to replay old transactions.
// However, once an amendment has been enabled for, say, more than two years
// then retaining that conditional code has less value since it is
// uncommon to replay such old transactions.
//
// Starting in January of 2020 Amendment conditionals from before January
// 2018 are being removed.  So replaying any ledger from before January
// 2018 needs to happen on an older version of the server code.  There's
// a log message in Application.cpp that warns about replaying old ledgers.
//
// At some point in the future someone may wish to remove amendment
// conditional code for amendments that were enabled after January 2018.
// When that happens then the log message in Application.cpp should be
// updated.
//
// Generally, amendments which introduce new features should be set as
// "VoteBehavior::DefaultNo" whereas in rare cases, amendments that fix
// critical bugs should be set as "VoteBehavior::DefaultYes", if off-chain
// consensus is reached amongst reviewers, validator operators, and other
// participants.

class FeatureCollections
{
    struct Feature
    {
        std::string name;
        uint256 feature;

        Feature() = delete;
        explicit Feature(std::string const& name_, uint256 const& feature_)
            : name(name_), feature(feature_)
        {
        }

        // These structs are used by the `features` multi_index_container to
        // provide access to the features collection by size_t index, string
        // name, and uint256 feature identifier
        struct byIndex
        {
        };
        struct byName
        {
        };
        struct byFeature
        {
        };
    };

    // Intermediate types to help with readability
    template <class tag, typename Type, Type Feature::*PtrToMember>
    using feature_hashed_unique = boost::multi_index::hashed_unique<
        boost::multi_index::tag<tag>,
        boost::multi_index::member<Feature, Type, PtrToMember>>;

    // Intermediate types to help with readability
    using feature_indexing = boost::multi_index::indexed_by<
        boost::multi_index::random_access<
            boost::multi_index::tag<Feature::byIndex>>,
        feature_hashed_unique<Feature::byFeature, uint256, &Feature::feature>,
        feature_hashed_unique<Feature::byName, std::string, &Feature::name>>;

    // This multi_index_container provides access to the features collection by
    // name, index, and uint256 feature identifier
    boost::multi_index::multi_index_container<Feature, feature_indexing>
        features;
    std::map<std::string, VoteBehavior> supported;
    std::size_t upVotes = 0;
    std::size_t downVotes = 0;
    mutable std::atomic<bool> readOnly = false;

    // These helper functions provide access to the features collection by name,
    // index, and uint256 feature identifier, so the details of
    // multi_index_container can be hidden
    Feature const&
    getByIndex(size_t i) const
    {
        if (i >= features.size())
            LogicError("Invalid FeatureBitset index");
        const auto& sequence = features.get<Feature::byIndex>();
        return sequence[i];
    }
    size_t
    getIndex(Feature const& feature) const
    {
        const auto& sequence = features.get<Feature::byIndex>();
        auto const it_to = sequence.iterator_to(feature);
        return it_to - sequence.begin();
    }
    Feature const*
    getByFeature(uint256 const& feature) const
    {
        const auto& feature_index = features.get<Feature::byFeature>();
        auto const feature_it = feature_index.find(feature);
        return feature_it == feature_index.end() ? nullptr : &*feature_it;
    }
    Feature const*
    getByName(std::string const& name) const
    {
        const auto& name_index = features.get<Feature::byName>();
        auto const name_it = name_index.find(name);
        return name_it == name_index.end() ? nullptr : &*name_it;
    }

public:
    FeatureCollections();

    std::optional<uint256>
    getRegisteredFeature(std::string const& name) const;

    uint256
    registerFeature(
        std::string const& name,
        Supported support,
        VoteBehavior vote);

    /** Tell FeatureCollections when registration is complete. */
    bool
    registrationIsDone();

    std::size_t
    featureToBitsetIndex(uint256 const& f) const;

    uint256 const&
    bitsetIndexToFeature(size_t i) const;

    std::string
    featureToName(uint256 const& f) const;

    /** Amendments that this server supports.
    Whether they are enabled depends on the Rules defined in the validated
    ledger */
    std::map<std::string, VoteBehavior> const&
    supportedAmendments() const
    {
        return supported;
    }

    /** Amendments that this server WON'T vote for by default. */
    std::size_t
    numDownVotedAmendments() const
    {
        return downVotes;
    }

    /** Amendments that this server WILL vote for by default. */
    std::size_t
    numUpVotedAmendments() const
    {
        return upVotes;
    }
};

//------------------------------------------------------------------------------

FeatureCollections::FeatureCollections()
{
    features.reserve(ripple::detail::numFeatures);
}

std::optional<uint256>
FeatureCollections::getRegisteredFeature(std::string const& name) const
{
    assert(readOnly);
    Feature const* feature = getByName(name);
    if (feature)
        return feature->feature;
    return std::nullopt;
}

void
check(bool condition, const char* logicErrorMessage)
{
    if (!condition)
        LogicError(logicErrorMessage);
}

uint256
FeatureCollections::registerFeature(
    std::string const& name,
    Supported support,
    VoteBehavior vote)
{
    check(!readOnly, "Attempting to register a feature after startup.");
    check(
        support == Supported::yes || vote == VoteBehavior::DefaultNo,
        "Invalid feature parameters. Must be supported to be up-voted.");
    Feature const* i = getByName(name);
    if (!i)
    {
        // If this check fails, and you just added a feature, increase the
        // numFeatures value in Feature.h
        check(
            features.size() < detail::numFeatures,
            "More features defined than allocated. Adjust numFeatures in "
            "Feature.h.");

        auto const f = sha512Half(Slice(name.data(), name.size()));

        features.emplace_back(name, f);

        if (support == Supported::yes)
        {
            supported.emplace(name, vote);

            if (vote == VoteBehavior::DefaultYes)
                ++upVotes;
            else
                ++downVotes;
        }
        check(
            upVotes + downVotes == supported.size(),
            "Feature counting logic broke");
        check(
            supported.size() <= features.size(),
            "More supported features than defined features");
        return f;
    }
    else
        // Each feature should only be registered once
        LogicError("Duplicate feature registration");
}

/** Tell FeatureCollections when registration is complete. */
bool
FeatureCollections::registrationIsDone()
{
    readOnly = true;
    return true;
}

size_t
FeatureCollections::featureToBitsetIndex(uint256 const& f) const
{
    assert(readOnly);

    Feature const* feature = getByFeature(f);
    if (!feature)
        LogicError("Invalid Feature ID");

    return getIndex(*feature);
}

uint256 const&
FeatureCollections::bitsetIndexToFeature(size_t i) const
{
    assert(readOnly);
    Feature const& feature = getByIndex(i);
    return feature.feature;
}

std::string
FeatureCollections::featureToName(uint256 const& f) const
{
    assert(readOnly);
    Feature const* feature = getByFeature(f);
    return feature ? feature->name : to_string(f);
}

static FeatureCollections featureCollections;

}  // namespace

/** Amendments that this server supports.
   Whether they are enabled depends on the Rules defined in the validated
   ledger */
std::map<std::string, VoteBehavior> const&
detail::supportedAmendments()
{
    return featureCollections.supportedAmendments();
}

/** Amendments that this server won't vote for by default. */
std::size_t
detail::numDownVotedAmendments()
{
    return featureCollections.numDownVotedAmendments();
}

/** Amendments that this server will vote for by default. */
std::size_t
detail::numUpVotedAmendments()
{
    return featureCollections.numUpVotedAmendments();
}

//------------------------------------------------------------------------------

std::optional<uint256>
getRegisteredFeature(std::string const& name)
{
    return featureCollections.getRegisteredFeature(name);
}

uint256
registerFeature(std::string const& name, Supported support, VoteBehavior vote)
{
    return featureCollections.registerFeature(name, support, vote);
}

// Retired features are in the ledger and have no code controlled by the
// feature. They need to be supported, but do not need to be voted on.
uint256
retireFeature(std::string const& name)
{
    return registerFeature(name, Supported::yes, VoteBehavior::Obsolete);
}

/** Tell FeatureCollections when registration is complete. */
bool
registrationIsDone()
{
    return featureCollections.registrationIsDone();
}

size_t
featureToBitsetIndex(uint256 const& f)
{
    return featureCollections.featureToBitsetIndex(f);
}

uint256
bitsetIndexToFeature(size_t i)
{
    return featureCollections.bitsetIndexToFeature(i);
}

std::string
featureToName(uint256 const& f)
{
    return featureCollections.featureToName(f);
}

#pragma push_macro("REGISTER_FEATURE")
#undef REGISTER_FEATURE

/**
Takes the name of a feature, whether it's supported, and the default vote. Will
register the feature, and create a variable whose name is "feature" plus the
feature name.
*/
#define REGISTER_FEATURE(fName, supported, votebehavior) \
    uint256 const feature##fName =                       \
        registerFeature(#fName, supported, votebehavior)

#pragma push_macro("REGISTER_FIX")
#undef REGISTER_FIX

/**
Takes the name of a feature, whether it's supported, and the default vote. Will
register the feature, and create a variable whose name is the unmodified feature
name.
*/
#define REGISTER_FIX(fName, supported, votebehavior) \
    uint256 const fName = registerFeature(#fName, supported, votebehavior)

// clang-format off

// All known amendments must be registered either here or below with the
// "retired" amendments
REGISTER_FEATURE(OwnerPaysFee,                  Supported::no,  VoteBehavior::DefaultNo);
REGISTER_FIX    (fixTrustLinesToSelf,           Supported::no,  VoteBehavior::DefaultNo);
REGISTER_FEATURE(Flow,                          Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(FlowCross,                     Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fix1513,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(DepositAuth,                   Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(Checks,                        Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fix1571,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fix1543,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fix1623,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(DepositPreauth,                Supported::yes, VoteBehavior::DefaultYes);
// Use liquidity from strands that consume max offers, but mark as dry
REGISTER_FIX    (fix1515,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fix1578,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(MultiSignReserve,              Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixTakerDryOfferRemoval,       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixMasterKeyAsRegularKey,      Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixCheckThreading,             Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixPayChanRecipientOwnerDir,   Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(DeletableAccounts,             Supported::yes, VoteBehavior::DefaultNo);
// fixQualityUpperBound should be activated before FlowCross
REGISTER_FIX    (fixQualityUpperBound,          Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(RequireFullyCanonicalSig,      Supported::yes, VoteBehavior::DefaultYes);
// fix1781: XRPEndpointSteps should be included in the circular payment check
REGISTER_FIX    (fix1781,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(HardenedValidations,           Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixAmendmentMajorityCalc,      Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(NegativeUNL,                   Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(TicketBatch,                   Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(FlowSortStrands,               Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixSTAmountCanonicalize,       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixRmSmallIncreasedQOffers,    Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(CheckCashMakesTrustLine,       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(ExpandedSignerList,            Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(NonFungibleTokensV1_1,         Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixRemoveNFTokenAutoTrustLine, Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(ImmediateOfferKilled,          Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(DisallowIncoming,              Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(XRPFees,                       Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixUniversalNumber,            Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixNonFungibleTokensV1_2,      Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixNFTokenRemint,              Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Hooks,                         Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(BalanceRewards,                Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(PaychanAndEscrowForTokens,     Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(URIToken,                      Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(Import,                        Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(XahauGenesis,                  Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(HooksUpdate1,                  Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixXahauV1,                    Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixXahauV2,                    Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Remit,                         Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(ZeroB2M,                       Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixNSDelete,                   Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fix240819,                     Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixPageCap,                    Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fix240911,                     Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixFloatDivide,                Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(Remarks,                       Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Touch,                         Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixReduceImport,               Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fixXahauV3,                    Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FIX    (fix20250131,                   Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(HookCanEmit,                   Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixRewardClaimFlags,           Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(Clawback,                      Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixProvisionalDoubleThreading, Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(DeepFreeze,                    Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(IOUIssuerWeakTSH,              Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FEATURE(Cron,                          Supported::yes, VoteBehavior::DefaultNo);
REGISTER_FIX    (fixInvalidTxFlags,             Supported::yes, VoteBehavior::DefaultYes);
REGISTER_FEATURE(ExtendedHookState,             Supported::yes, VoteBehavior::DefaultNo);

// The following amendments are obsolete, but must remain supported
// because they could potentially get enabled.
//
// Obsolete features are (usually) not in the ledger, and may have code
// controlled by the feature. They need to be supported because at some
// time in the past, the feature was supported and votable, but never
// passed. So the feature needs to be supported in case it is ever
// enabled (added to the ledger).
//
// If a feature remains obsolete for long enough that no clients are able
// to vote for it, the feature can be removed (entirely?) from the code.
REGISTER_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete);
REGISTER_FEATURE(NonFungibleTokensV1,   Supported::yes, VoteBehavior::Obsolete);
REGISTER_FIX    (fixNFTokenDirV1,       Supported::yes, VoteBehavior::Obsolete);
REGISTER_FIX    (fixNFTokenNegOffer,    Supported::yes, VoteBehavior::Obsolete);

// The following amendments have been active for at least two years. Their
// pre-amendment code has been removed and the identifiers are deprecated.
// All known amendments and amendments that may appear in a validated
// ledger must be registered either here or above with the "active" amendments
[[deprecated("The referenced amendment has been retired"), maybe_unused]]
uint256 const
    retiredMultiSign         = retireFeature("MultiSign"),
    retiredTrustSetAuth      = retireFeature("TrustSetAuth"),
    retiredFeeEscalation     = retireFeature("FeeEscalation"),
    retiredPayChan           = retireFeature("PayChan"),
    retiredCryptoConditions  = retireFeature("CryptoConditions"),
    retiredTickSize          = retireFeature("TickSize"),
    retiredFix1368           = retireFeature("fix1368"),
    retiredEscrow            = retireFeature("Escrow"),
    retiredFix1373           = retireFeature("fix1373"),
    retiredEnforceInvariants = retireFeature("EnforceInvariants"),
    retiredSortedDirectories = retireFeature("SortedDirectories"),
    retiredFix1201           = retireFeature("fix1201"),
    retiredFix1512           = retireFeature("fix1512"),
    retiredFix1523           = retireFeature("fix1523"),
    retiredFix1528           = retireFeature("fix1528");

// clang-format on

#undef REGISTER_FIX
#pragma pop_macro("REGISTER_FIX")

#undef REGISTER_FEATURE
#pragma pop_macro("REGISTER_FEATURE")

// All of the features should now be registered, since variables in a cpp file
// are initialized from top to bottom.
//
// Use initialization of one final static variable to set
// featureCollections::readOnly.
[[maybe_unused]] static const bool readOnlySet =
    featureCollections.registrationIsDone();

}  // namespace ripple
