From a4c5114906db2b3ac9ef05d7151726beb9064227 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Fri, 29 Sep 2023 12:25:24 -0400 Subject: [PATCH] Add payload and registry machinery for SideStake class This adds the machinery to support mandatory sidestakes in the future. It does not include the contract handling part. --- src/gridcoin/contract/payload.h | 2 + src/gridcoin/sidestake.cpp | 373 +++++++++++++++++++++++++- src/gridcoin/sidestake.h | 454 ++++++++++++++++++++++++++++++-- 3 files changed, 798 insertions(+), 31 deletions(-) diff --git a/src/gridcoin/contract/payload.h b/src/gridcoin/contract/payload.h index ed48ef0240..6a1c7bde7b 100644 --- a/src/gridcoin/contract/payload.h +++ b/src/gridcoin/contract/payload.h @@ -65,6 +65,7 @@ enum class ContractType SCRAPER, //!< Scraper node authorization grants and revocations. VOTE, //!< A vote cast by a wallet for a poll. MRC, //!< A manual rewards claim (MRC) request to pay rewards + SIDESTAKE, //!< Mandatory sidestakes OUT_OF_BOUND, //!< Marker value for the end of the valid range. }; @@ -82,6 +83,7 @@ static constexpr GRC::ContractType CONTRACT_TYPES[] = { ContractType::SCRAPER, ContractType::VOTE, ContractType::MRC, + ContractType::SIDESTAKE, ContractType::OUT_OF_BOUND }; diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp index 4301ca53a6..02f25fd853 100644 --- a/src/gridcoin/sidestake.cpp +++ b/src/gridcoin/sidestake.cpp @@ -6,12 +6,26 @@ #include "node/ui_interface.h" using namespace GRC; +using LogFlags = BCLog::LogFlags; + +namespace { +SideStakeRegistry g_sidestake_entries; +} // anonymous namespace + +// ----------------------------------------------------------------------------- +// Global Functions +// ----------------------------------------------------------------------------- + +SideStakeRegistry& GRC::GetSideStakeRegistry() +{ + return g_sidestake_entries; +} // ----------------------------------------------------------------------------- // Class: SideStake // ----------------------------------------------------------------------------- SideStake::SideStake() - : m_address() + : m_key() , m_allocation() , m_timestamp(0) , m_hash() @@ -19,8 +33,8 @@ SideStake::SideStake() , m_status(SideStakeStatus::UNKNOWN) {} -SideStake::SideStake(CBitcoinAddress address, double allocation) - : m_address(address) +SideStake::SideStake(CBitcoinAddressForStorage address, double allocation) + : m_key(address) , m_allocation(allocation) , m_timestamp(0) , m_hash() @@ -28,8 +42,8 @@ SideStake::SideStake(CBitcoinAddress address, double allocation) , m_status(SideStakeStatus::UNKNOWN) {} -SideStake::SideStake(CBitcoinAddress address, double allocation, int64_t timestamp, uint256 hash) - : m_address(address) +SideStake::SideStake(CBitcoinAddressForStorage address, double allocation, int64_t timestamp, uint256 hash) + : m_key(address) , m_allocation(allocation) , m_timestamp(timestamp) , m_hash(hash) @@ -39,12 +53,17 @@ SideStake::SideStake(CBitcoinAddress address, double allocation, int64_t timesta bool SideStake::WellFormed() const { - return m_address.IsValid() && m_allocation >= 0.0 && m_allocation <= 1.0; + return m_key.IsValid() && m_allocation >= 0.0 && m_allocation <= 1.0; +} + +CBitcoinAddressForStorage SideStake::Key() const +{ + return m_key; } std::pair SideStake::KeyValueToString() const { - return std::make_pair(m_address.ToString(), StatusToString()); + return std::make_pair(m_key.ToString(), StatusToString()); } std::string SideStake::StatusToString() const @@ -88,7 +107,7 @@ bool SideStake::operator==(SideStake b) { bool result = true; - result &= (m_address == b.m_address); + result &= (m_key == b.m_key); result &= (m_allocation == b.m_allocation); result &= (m_timestamp == b.m_timestamp); result &= (m_hash == b.m_hash); @@ -102,3 +121,341 @@ bool SideStake::operator!=(SideStake b) { return !(*this == b); } + +// ----------------------------------------------------------------------------- +// Class: SideStakePayload +// ----------------------------------------------------------------------------- + +constexpr uint32_t SideStakePayload::CURRENT_VERSION; // For clang + +SideStakePayload::SideStakePayload(uint32_t version) + : IContractPayload() + , m_version(version) +{ +} + +SideStakePayload::SideStakePayload(const uint32_t version, CBitcoinAddressForStorage key, double value, SideStakeStatus status) + : IContractPayload() + , m_version(version) + , m_entry(SideStake(key, value, status)) +{ +} + +SideStakePayload::SideStakePayload(const uint32_t version, SideStake entry) + : IContractPayload() + , m_version(version) + , m_entry(std::move(entry)) +{ +} + +SideStakePayload::SideStakePayload(SideStake entry) + : SideStakePayload(CURRENT_VERSION, std::move(entry)) +{ +} + +// ----------------------------------------------------------------------------- +// Class: SideStakeRegistry +// ----------------------------------------------------------------------------- +const SideStakeRegistry::SideStakeMap& SideStakeRegistry::SideStakeEntries() const +{ + return m_sidestake_entries; +} + +SideStakeOption SideStakeRegistry::Try(const CBitcoinAddressForStorage& key) const +{ + LOCK(cs_lock); + + const auto iter = m_sidestake_entries.find(key); + + if (iter == m_sidestake_entries.end()) { + return nullptr; + } + + return iter->second; +} + +SideStakeOption SideStakeRegistry::TryActive(const CBitcoinAddressForStorage& key) const +{ + LOCK(cs_lock); + + if (const SideStakeOption SideStake_entry = Try(key)) { + if (SideStake_entry->m_status == SideStakeStatus::ACTIVE || SideStake_entry->m_status == SideStakeStatus::MANDATORY) { + return SideStake_entry; + } + } + + return nullptr; +} + +void SideStakeRegistry::Reset() +{ + LOCK(cs_lock); + + m_sidestake_entries.clear(); + m_sidestake_db.clear(); +} + +void SideStakeRegistry::AddDelete(const ContractContext& ctx) +{ + // Poor man's mock. This is to prevent the tests from polluting the LevelDB database + int height = -1; + + if (ctx.m_pindex) + { + height = ctx.m_pindex->nHeight; + } + + SideStakePayload payload = ctx->CopyPayloadAs(); + + // Fill this in from the transaction context because these are not done during payload + // initialization. + payload.m_entry.m_hash = ctx.m_tx.GetHash(); + payload.m_entry.m_timestamp = ctx.m_tx.nTime; + + // Ensure status is DELETED if the contract action was REMOVE, regardless of what was actually + // specified. + if (ctx->m_action == ContractAction::REMOVE) { + payload.m_entry.m_status = SideStakeStatus::DELETED; + } + + LOCK(cs_lock); + + auto sidestake_entry_pair_iter = m_sidestake_entries.find(payload.m_entry.m_key); + + SideStake_ptr current_sidestake_entry_ptr = nullptr; + + // Is there an existing SideStake entry in the map? + bool current_sidestake_entry_present = (sidestake_entry_pair_iter != m_sidestake_entries.end()); + + // If so, then get a smart pointer to it. + if (current_sidestake_entry_present) { + current_sidestake_entry_ptr = sidestake_entry_pair_iter->second; + + // Set the payload m_entry's prev entry ctx hash = to the existing entry's hash. + payload.m_entry.m_previous_hash = current_sidestake_entry_ptr->m_hash; + } else { // Original entry for this SideStake entry key + payload.m_entry.m_previous_hash = uint256 {}; + } + + LogPrint(LogFlags::CONTRACT, "INFO: %s: SideStake entry add/delete: contract m_version = %u, payload " + "m_version = %u, key = %s, value = %f, m_timestamp = %" PRId64 ", " + "m_hash = %s, m_previous_hash = %s, m_status = %s", + __func__, + ctx->m_version, + payload.m_version, + payload.m_entry.m_key.ToString(), + payload.m_entry.m_allocation, + payload.m_entry.m_timestamp, + payload.m_entry.m_hash.ToString(), + payload.m_entry.m_previous_hash.ToString(), + payload.m_entry.StatusToString() + ); + + SideStake& historical = payload.m_entry; + + if (!m_sidestake_db.insert(ctx.m_tx.GetHash(), height, historical)) + { + LogPrint(LogFlags::CONTRACT, "INFO: %s: In recording of the SideStake entry for key %s, value %f, hash %s, " + "the SideStake entry db record already exists. This can be expected on a restart " + "of the wallet to ensure multiple contracts in the same block get stored/replayed.", + __func__, + historical.m_key.ToString(), + historical.m_allocation, + historical.m_hash.GetHex()); + } + + // Finally, insert the new SideStake entry (payload) smart pointer into the m_sidestake_entries map. + m_sidestake_entries[payload.m_entry.m_key] = m_sidestake_db.find(ctx.m_tx.GetHash())->second; + + return; +} + +void SideStakeRegistry::NonContractAdd(SideStake& sidestake) +{ + // Using this form of insert because we want the latest record with the same key to override any previous one. + m_sidestake_entries[sidestake.m_key] = std::make_shared(sidestake); +} + +void SideStakeRegistry::Add(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void SideStakeRegistry::NonContractDelete(CBitcoinAddressForStorage& address) +{ + auto sidestake_entry_pair_iter = m_sidestake_entries.find(address); + + if (sidestake_entry_pair_iter != m_sidestake_entries.end()) { + m_sidestake_entries.erase(sidestake_entry_pair_iter); + } +} + +void SideStakeRegistry::Delete(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void SideStakeRegistry::Revert(const ContractContext& ctx) +{ + const auto payload = ctx->SharePayloadAs(); + + // For SideStake entries, both adds and removes will have records to revert in the m_sidestake_entries map, + // and also, if not the first entry for that SideStake key, will have a historical record to + // resurrect. + LOCK(cs_lock); + + auto entry_to_revert = m_sidestake_entries.find(payload->m_entry.m_key); + + if (entry_to_revert == m_sidestake_entries.end()) { + error("%s: The SideStake entry for key %s to revert was not found in the SideStake entry map.", + __func__, + entry_to_revert->second->m_key.ToString()); + + // If there is no record in the current m_sidestake_entries map, then there is nothing to do here. This + // should not occur. + return; + } + + // If this is not a null hash, then there will be a prior entry to resurrect. + CBitcoinAddressForStorage key = entry_to_revert->second->m_key; + uint256 resurrect_hash = entry_to_revert->second->m_previous_hash; + + // Revert the ADD or REMOVE action. Unlike the beacons, this is symmetric. + if (ctx->m_action == ContractAction::ADD || ctx->m_action == ContractAction::REMOVE) { + // Erase the record from m_sidestake_entries. + if (m_sidestake_entries.erase(payload->m_entry.m_key) == 0) { + error("%s: The SideStake entry to erase during a SideStake entry revert for key %s was not found.", + __func__, + key.ToString()); + // If the record to revert is not found in the m_sidestake_entries map, no point in continuing. + return; + } + + // Also erase the record from the db. + if (!m_sidestake_db.erase(ctx.m_tx.GetHash())) { + error("%s: The db entry to erase during a SideStake entry revert for key %s was not found.", + __func__, + key.ToString()); + + // Unlike the above we will keep going even if this record is not found, because it is identical to the + // m_sidestake_entries record above. This should not happen, because during contract adds and removes, + // entries are made simultaneously to the m_sidestake_entries and m_sidestake_db. + } + + if (resurrect_hash.IsNull()) { + return; + } + + auto resurrect_entry = m_sidestake_db.find(resurrect_hash); + + if (resurrect_entry == m_sidestake_db.end()) { + error("%s: The prior entry to resurrect during a SideStake entry ADD revert for key %s was not found.", + __func__, + key.ToString()); + return; + } + + // Resurrect the entry prior to the reverted one. It is safe to use the bracket form here, because of the protection + // of the logic above. There cannot be any entry in m_sidestake_entries with that key value left if we made it here. + m_sidestake_entries[resurrect_entry->second->m_key] = resurrect_entry->second; + } +} + +bool SideStakeRegistry::Validate(const Contract& contract, const CTransaction& tx, int &DoS) const +{ + if (contract.m_version < 1) { + return true; + } + + const auto payload = contract.SharePayloadAs(); + + if (contract.m_version >= 3 && payload->m_version < 2) { + DoS = 25; + error("%s: Legacy SideStake entry contract in contract v3", __func__); + return false; + } + + if (!payload->WellFormed(contract.m_action.Value())) { + DoS = 25; + error("%s: Malformed SideStake entry contract", __func__); + return false; + } + + return true; +} + +bool SideStakeRegistry::BlockValidate(const ContractContext& ctx, int& DoS) const +{ + return Validate(ctx.m_contract, ctx.m_tx, DoS); +} + +int SideStakeRegistry::Initialize() +{ + LOCK(cs_lock); + + int height = m_sidestake_db.Initialize(m_sidestake_entries, m_pending_sidestake_entries); + + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_sidestake_db size after load: %u", __func__, m_sidestake_db.size()); + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_sidestake_entries size after load: %u", __func__, m_sidestake_entries.size()); + + return height; +} + +void SideStakeRegistry::SetDBHeight(int& height) +{ + LOCK(cs_lock); + + m_sidestake_db.StoreDBHeight(height); +} + +int SideStakeRegistry::GetDBHeight() +{ + int height = 0; + + LOCK(cs_lock); + + m_sidestake_db.LoadDBHeight(height); + + return height; +} + +void SideStakeRegistry::ResetInMemoryOnly() +{ + LOCK(cs_lock); + + m_sidestake_entries.clear(); + m_sidestake_db.clear_in_memory_only(); +} + +uint64_t SideStakeRegistry::PassivateDB() +{ + LOCK(cs_lock); + + return m_sidestake_db.passivate_db(); +} + +SideStakeRegistry::SideStakeDB &SideStakeRegistry::GetSideStakeDB() +{ + return m_sidestake_db; +} + +// This is static and called by the scheduler. +void SideStakeRegistry::RunDBPassivation() +{ + TRY_LOCK(cs_main, locked_main); + + if (!locked_main) + { + return; + } + + SideStakeRegistry& SideStake_entries = GetSideStakeRegistry(); + + SideStake_entries.PassivateDB(); +} + +template<> const std::string SideStakeRegistry::SideStakeDB::KeyType() +{ + return std::string("SideStake"); +} + diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h index d32bd7214c..59893cbaf2 100644 --- a/src/gridcoin/sidestake.h +++ b/src/gridcoin/sidestake.h @@ -6,11 +6,31 @@ #define GRIDCOIN_SIDESTAKE_H #include "base58.h" +#include "gridcoin/contract/handler.h" +#include "gridcoin/contract/payload.h" +#include "gridcoin/contract/registry_db.h" #include "gridcoin/support/enumbytes.h" #include "serialize.h" namespace GRC { +class CBitcoinAddressForStorage : public CBitcoinAddress +{ +public: + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + // Note that (de)serializing the raw underlying vector char data for the address is safe here + // because this is only used in this module and validations were performed before serialization into + // storage. + READWRITE(vchData); + } +}; + + enum class SideStakeStatus { UNKNOWN, @@ -21,21 +41,36 @@ enum class SideStakeStatus OUT_OF_BOUND }; +//! +//! \brief The SideStake class. This class formalizes the "sidestake", which is a directive to apportion +//! a percentage of the total stake value to a designated address. This address must be a valid address, but +//! may or may not be owned by the staker. This is the primary mechanism to do automatic "donations" to +//! defined network addresses. +//! +//! The class supports two modes of operation. Local (voluntary) entries will be picked up from the config file(s) +//! and will be managed dynamically based on the initial load of the config file + the r-w file + any changes to +//! in any GUI implementation on top of this. Mandatory entries will be picked up by contract handlers similar to +//! other contract types (cf. protocol entries). +//! class SideStake { +public: + //! + //! \brief Wrapped Enumeration of sidestake entry status, mainly for serialization/deserialization. + //! using Status = EnumByte; - CBitcoinAddress m_address; + CBitcoinAddressForStorage m_key; //!< The key here is the Gridcoin Address of the sidestake destination. - double m_allocation; + double m_allocation; //!< The allocation is a double precision floating point between 0.0 and 1.0 inclusive - int64_t m_timestamp; //!< Time of the sidestake contract transaction. + int64_t m_timestamp; //!< Time of the sidestake contract transaction. - uint256 m_hash; //!< The hash of the transaction that contains a mandatory sidestake. + uint256 m_hash; //!< The hash of the transaction that contains a mandatory sidestake. - uint256 m_previous_hash; //!< The m_hash of the previous mandatory sidestake allocation with the same address. + uint256 m_previous_hash; //!< The m_hash of the previous mandatory sidestake allocation with the same address. - Status m_status; //!< The status of the sidestake. It is of type int instead of enum for serialization. + Status m_status; //!< The status of the sidestake. It is of type int instead of enum for serialization. //! //! \brief Initialize an empty, invalid sidestake instance. @@ -49,10 +84,19 @@ class SideStake //! \param address //! \param allocation //! - SideStake(CBitcoinAddress address, double allocation); + SideStake(CBitcoinAddressForStorage address, double allocation); + + //! + //! \brief Initialize a sidestake instance with the provided parameters. + //! + //! \param address + //! \param allocation + //! \param status + //! + SideStake(CBitcoinAddressForStorage address, double allocation, SideStakeStatus status); //! - //! \brief Initial a sidestake instance with the provided parameters. This form is normally used to construct a + //! \brief Initialize a sidestake instance with the provided parameters. This form is normally used to construct a //! mandatory sidestake from a contract. //! //! \param address @@ -60,7 +104,7 @@ class SideStake //! \param timestamp //! \param hash //! - SideStake(CBitcoinAddress address, double allocation, int64_t timestamp, uint256 hash); + SideStake(CBitcoinAddressForStorage address, double allocation, int64_t timestamp, uint256 hash); //! //! \brief Determine whether a sidestake contains each of the required elements. @@ -68,27 +112,35 @@ class SideStake //! bool WellFormed() const; + //! + //! \brief This is the standardized method that returns the key value for the sidestake entry (for + //! the registry_db.h template.) + //! + //! \return CBitcoinAddress key value for the sidestake entry + //! + CBitcoinAddressForStorage Key() const; + //! //! \brief Provides the sidestake address and status (value) as a pair of strings. //! \return std::pair of strings //! std::pair KeyValueToString() const; - //! - //! \brief Returns the string representation of the current sidestake status - //! - //! \return Translated string representation of sidestake status - //! + //! + //! \brief Returns the string representation of the current sidestake status + //! + //! \return Translated string representation of sidestake status + //! std::string StatusToString() const; - //! - //! \brief Returns the translated or untranslated string of the input sidestake status - //! - //! \param status. SideStake status - //! \param translated. True for translated, false for not translated. Defaults to true. - //! - //! \return SideStake status string. - //! + //! + //! \brief Returns the translated or untranslated string of the input sidestake status + //! + //! \param status. SideStake status + //! \param translated. True for translated, false for not translated. Defaults to true. + //! + //! \return SideStake status string. + //! std::string StatusToString(const SideStakeStatus& status, const bool& translated = true) const; //! @@ -116,7 +168,7 @@ class SideStake template inline void SerializationOp(Stream& s, Operation ser_action) { - READWRITE(m_address); + READWRITE(m_key); READWRITE(m_allocation); READWRITE(m_timestamp); READWRITE(m_hash); @@ -135,7 +187,363 @@ typedef std::shared_ptr SideStake_ptr; //! typedef const SideStake_ptr SideStakeOption; +//! +//! \brief The body of a sidestake entry contract. Note that this body is bimodal. It +//! supports both the personality of the "LegacyPayload", and also the new native +//! sidestakeEntry format. In the Contract::Body::ConvertFromLegacy call, by the time +//! this call has been reached, the contract will have already been deserialized. +//! This will follow the legacy mode. For contracts at version 3+, the +//! Contract::SharePayload() will NOT call the ConvertFromLegacy. Note that because +//! the existing legacyPayloads are not versioned, the deserialization of +//! the payload first (de)serializes m_key, which is guaranteed to exist in either +//! legacy or native. If the key is empty, then payload v2+ is being deserialized +//! and the m_version and m_value are (de)serialized. This is ugly +//! but necessary to deal with the unversioned Legacy Payloads and maintain +//! compatibility. +//! +class SideStakePayload : public IContractPayload +{ +public: + //! + //! \brief Version number of the current format for a serialized sidestake entry. + //! + //! CONSENSUS: Increment this value when introducing a breaking change and + //! ensure that the serialization/deserialization routines also handle all + //! of the previous versions. + //! + static constexpr uint32_t CURRENT_VERSION = 1; + + //! + //! \brief Version number of the serialized sidestake entry format. + //! + //! Version 1: Initial version: + //! + uint32_t m_version = CURRENT_VERSION; + + SideStake m_entry; //!< The sidestake entry in the payload. + + //! + //! \brief Initialize an empty, invalid sidestake entry payload. + //! + SideStakePayload(uint32_t version = CURRENT_VERSION); + + //! + //! \brief Initialize a sidestakeEntryPayload from a sidestake entry constructed from + //! string key and value. Not to be used for version 1 payloads. Will assert. Does NOT + //! initialize hash fields. + //! + //! \param key. Key string for the sidestake entry + //! \param value. Value string for the sidestake entry + //! \param status. Status of the sidestake entry + //! + SideStakePayload(const uint32_t version, CBitcoinAddressForStorage key, double value, SideStakeStatus status); + + //! + //! \brief Initialize a sidestake entry payload from the given sidestake entry + //! with the provided version number (and format). + //! + //! \param version Version of the serialized sidestake entry format. + //! \param sidestake_entry The sidestake entry itself. + //! + SideStakePayload(const uint32_t version, SideStake sidestake_entry); + + //! + //! \brief Initialize a sidestake entry payload from the given sidestake entry + //! with the CURRENT_VERSION. + //! + //! \param sidestake_entry The sidestake entry itself. + //! + SideStakePayload(SideStake sidestake_entry); + + //! + //! \brief Get the type of contract that this payload contains data for. + //! + GRC::ContractType ContractType() const override + { + return GRC::ContractType::SIDESTAKE; + } + + //! + //! \brief Determine whether the instance represents a complete payload. + //! + //! \return \c true if the payload contains each of the required elements. + //! + bool WellFormed(const ContractAction action) const override + { + if (m_version <= 0 || m_version > CURRENT_VERSION) { + return false; + } + + return m_entry.WellFormed(); + } + + //! + //! \brief Get a string for the key used to construct a legacy contract. + //! + std::string LegacyKeyString() const override + { + return m_entry.m_key.ToString(); + } + + //! + //! \brief Get a string for the value used to construct a legacy contract. + //! + std::string LegacyValueString() const override + { + return ToString(m_entry.m_allocation); + } + + //! + //! \brief Get the burn fee amount required to send a particular contract. This + //! is the same as the LegacyPayload to insure compatibility between the sidestake + //! registry and non-upgraded nodes before the block v13/contract version 3 height + //! + //! \return Burn fee in units of 1/100000000 GRC. + //! + CAmount RequiredBurnAmount() const override + { + return Contract::STANDARD_BURN_AMOUNT; + } + + ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; + + template + inline void SerializationOp( + Stream& s, + Operation ser_action, + const ContractAction contract_action) + { + READWRITE(m_version); + READWRITE(m_entry); + } +}; // SideStakePayload +//! +//! \brief Stores and manages sidestake entries. +//! +class SideStakeRegistry : public IContractHandler +{ +public: + //! + //! \brief sidestakeRegistry constructor. The parameter is the version number of the underlying + //! sidestake entry db. This must be incremented when implementing format changes to the sidestake + //! entries to force a reinit. + //! + //! Version 1: TBD. + //! + SideStakeRegistry() + : m_sidestake_db(1) + { + }; + + //! + //! \brief The type that keys sidestake entries by their key strings. Note that the entries + //! in this map are actually smart shared pointer wrappers, so that the same actual object + //! can be held by both this map and the historical map without object duplication. + //! + typedef std::map SideStakeMap; + + //! + //! \brief PendingSideStakeMap. This is not actually used but defined to satisfy the template. + //! + typedef SideStakeMap PendingSideStakeMap; + + //! + //! \brief The type that keys historical sidestake entries by the contract hash (txid). + //! Note that the entries in this map are actually smart shared pointer wrappers, so that + //! the same actual object can be held by both this map and the (current) sidestake entry map + //! without object duplication. + //! + typedef std::map HistoricalSideStakeMap; + + //! + //! \brief Get the collection of current sidestake entries. Note that this INCLUDES deleted + //! sidestake entries. + //! + //! \return \c A reference to the current sidestake entries stored in the registry. + //! + const SideStakeMap& SideStakeEntries() const; + + //! + //! \brief Get the current sidestake entry for the specified key string. + //! + //! \param key The key string of the sidestake entry. + //! + //! \return An object that either contains a reference to some sidestake entry if it exists + //! for the key or does not. + //! + SideStakeOption Try(const CBitcoinAddressForStorage& key) const; + + //! + //! \brief Get the current sidestake entry for the specified key string if it has a status of ACTIVE or MANDATORY. + //! + //! \param key The key string of the sidestake entry. + //! + //! \return An object that either contains a reference to some sidestake entry if it exists + //! for the key and is in the required status or does not. + //! + SideStakeOption TryActive(const CBitcoinAddressForStorage& key) const; + + //! + //! \brief Destroy the contract handler state in case of an error in loading + //! the sidestake entry registry state from LevelDB to prepare for reload from contract + //! replay. This is not used for sidestake entries, unless -clearSideStakehistory is specified + //! as a startup argument, because contract replay storage and full reversion has + //! been implemented for sidestake entries. + //! + void Reset() override; + + //! + //! \brief Determine whether a sidestake entry contract is valid. + //! + //! \param contract Contains the sidestake entry contract to validate. + //! \param tx Transaction that contains the contract. + //! \param DoS Misbehavior out. + //! + //! \return \c true if the contract contains a valid sidestake entry. + //! + bool Validate(const Contract& contract, const CTransaction& tx, int& DoS) const override; + + //! + //! \brief Determine whether a sidestake entry contract is valid including block context. This is used + //! in ConnectBlock. Note that for sidestake entries this simply calls Validate as there is no + //! block level specific validation to be done. + //! + //! \param ctx ContractContext containing the sidestake entry data to validate. + //! \param DoS Misbehavior score out. + //! + //! \return \c false If the contract fails validation. + //! + bool BlockValidate(const ContractContext& ctx, int& DoS) const override; + + //! + //! \brief Allows local (voluntary) sidestakes to be added to the in-memory map and not persisted to + //! the registry db. + //! \param SideStake object to add + //! + void NonContractAdd(SideStake& sidestake); + + //! + //! \brief Add a sidestake entry to the registry from contract data. For the sidestake registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! \param ctx + //! + void Add(const ContractContext& ctx) override; + + //! + //! \brief Provides for deletion of local (voluntary) sidestakes from the in-memory map that are not persisted + //! to the registry db. Deletion is by the map key (CBitcoinAddress). + //! \param address + //! + void NonContractDelete(CBitcoinAddressForStorage& address); + + //! + //! \brief Mark a sidestake entry deleted in the registry from contract data. For the sidestake registry + //! both Add and Delete actually call a common helper function AddDelete, because the action + //! is actually symmetric to both. + //! \param ctx + //! + void Delete(const ContractContext& ctx) override; + + //! + //! \brief Revert the registry state for the sidestake entry to the state prior + //! to this ContractContext application. This is typically an issue + //! during reorganizations, where blocks are disconnected. + //! + //! \param ctx References the sidestake entry contract and associated context. + //! + void Revert(const ContractContext& ctx) override; + + //! + //! \brief Initialize the sidestakeRegistry, which now includes restoring the state of the sidestakeRegistry from + //! LevelDB on wallet start. + //! + //! \return Block height of the database restored from LevelDB. Zero if no LevelDB sidestake entry data is found or + //! there is some issue in LevelDB sidestake entry retrieval. (This will cause the contract replay to change scope + //! and initialize the sidestakeRegistry from contract replay and store in LevelDB.) + //! + int Initialize() override; + + //! + //! \brief Gets the block height through which is stored in the sidestake entry registry database. + //! + //! \return block height. + //! + int GetDBHeight() override; + + //! + //! \brief Function normally only used after a series of reverts during block disconnects, because + //! block disconnects are done in groups back to a common ancestor, and will include a series of reverts. + //! This is essentially atomic, and therefore the final (common) height only needs to be set once. TODO: + //! reversion should be done with a vector argument of the contract contexts, along with a final height to + //! clean this up and move the logic to here from the calling function. + //! + //! \param height to set the storage DB bookmark. + //! + void SetDBHeight(int& height) override; + + //! + //! \brief Resets the maps in the sidestakeRegistry but does not disturb the underlying LevelDB + //! storage. This is only used during testing in the testing harness. + //! + void ResetInMemoryOnly(); + + //! + //! \brief Passivates the elements in the sidestake db, which means remove from memory elements in the + //! historical map that are not referenced by the active entry map. The backing store of the element removed + //! from memory is retained and will be transparently restored if find() is called on the hash key + //! for the element. + //! + //! \return The number of elements passivated. + //! + uint64_t PassivateDB(); + + //! + //! \brief A static function that is called by the scheduler to run the sidestake entry database passivation. + //! + static void RunDBPassivation(); + + //! + //! \brief Specializes the template RegistryDB for the SideStake class + //! + typedef RegistryDB SideStakeDB; + +private: + //! + //! \brief Protects the registry with multithreaded access. This is implemented INTERNAL to the registry class. + //! + mutable CCriticalSection cs_lock; + + //! + //! \brief Private helper method for the Add and Delete methods above. They both use identical code (with + //! different input statuses). + //! + //! \param ctx The contract context for the add or delete. + //! + void AddDelete(const ContractContext& ctx); + + SideStakeMap m_sidestake_entries; //!< Contains the current sidestake entries including entries marked DELETED. + PendingSideStakeMap m_pending_sidestake_entries {}; //!< Not used. Only to satisfy the template. + + SideStakeDB m_sidestake_db; + +public: + + SideStakeDB& GetSideStakeDB(); +}; // sidestakeRegistry + +//! +//! \brief Get the global sidestake entry registry. +//! +//! \return Current global sidestake entry registry instance. +//! +SideStakeRegistry& GetSideStakeRegistry(); } // namespace GRC #endif // GRIDCOIN_SIDESTAKE_H