diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp index fd4c9030e6..27b5c4159e 100644 --- a/src/gridcoin/sidestake.cpp +++ b/src/gridcoin/sidestake.cpp @@ -45,34 +45,34 @@ CBitcoinAddressForStorage::CBitcoinAddressForStorage(CBitcoinAddress address) {} // ----------------------------------------------------------------------------- -// Class: SideStake +// Class: MandatorySideStake // ----------------------------------------------------------------------------- -SideStake::SideStake() +MandatorySideStake::MandatorySideStake() : m_address() , m_allocation() , m_description() , m_timestamp(0) , m_hash() , m_previous_hash() - , m_status(SideStakeStatus::UNKNOWN) + , m_status(MandatorySideStakeStatus::UNKNOWN) {} -SideStake::SideStake(CBitcoinAddressForStorage address, double allocation, std::string description) +MandatorySideStake::MandatorySideStake(CBitcoinAddressForStorage address, double allocation, std::string description) : m_address(address) , m_allocation(allocation) , m_description(description) , m_timestamp(0) , m_hash() , m_previous_hash() - , m_status(SideStakeStatus::UNKNOWN) + , m_status(MandatorySideStakeStatus::UNKNOWN) {} -SideStake::SideStake(CBitcoinAddressForStorage address, - double allocation, - std::string description, - int64_t timestamp, - uint256 hash, - SideStakeStatus status) +MandatorySideStake::MandatorySideStake(CBitcoinAddressForStorage address, + double allocation, + std::string description, + int64_t timestamp, + uint256 hash, + MandatorySideStakeStatus status) : m_address(address) , m_allocation(allocation) , m_description(description) @@ -82,48 +82,44 @@ SideStake::SideStake(CBitcoinAddressForStorage address, , m_status(status) {} -bool SideStake::WellFormed() const +bool MandatorySideStake::WellFormed() const { return m_address.IsValid() && m_allocation >= 0.0 && m_allocation <= 1.0; } -CBitcoinAddressForStorage SideStake::Key() const +CBitcoinAddressForStorage MandatorySideStake::Key() const { return m_address; } -std::pair SideStake::KeyValueToString() const +std::pair MandatorySideStake::KeyValueToString() const { return std::make_pair(m_address.ToString(), StatusToString()); } -std::string SideStake::StatusToString() const +std::string MandatorySideStake::StatusToString() const { return StatusToString(m_status.Value()); } -std::string SideStake::StatusToString(const SideStakeStatus& status, const bool& translated) const +std::string MandatorySideStake::StatusToString(const MandatorySideStakeStatus& status, const bool& translated) const { if (translated) { switch(status) { - case SideStakeStatus::UNKNOWN: return _("Unknown"); - case SideStakeStatus::ACTIVE: return _("Active"); - case SideStakeStatus::INACTIVE: return _("Inactive"); - case SideStakeStatus::DELETED: return _("Deleted"); - case SideStakeStatus::MANDATORY: return _("Mandatory"); - case SideStakeStatus::OUT_OF_BOUND: break; + case MandatorySideStakeStatus::UNKNOWN: return _("Unknown"); + case MandatorySideStakeStatus::DELETED: return _("Deleted"); + case MandatorySideStakeStatus::MANDATORY: return _("Mandatory"); + case MandatorySideStakeStatus::OUT_OF_BOUND: break; } assert(false); // Suppress warning } else { // The untranslated versions are really meant to serve as the string equivalent of the enum values. switch(status) { - case SideStakeStatus::UNKNOWN: return "Unknown"; - case SideStakeStatus::ACTIVE: return "Active"; - case SideStakeStatus::INACTIVE: return "Inactive"; - case SideStakeStatus::DELETED: return "Deleted"; - case SideStakeStatus::MANDATORY: return "Mandatory"; - case SideStakeStatus::OUT_OF_BOUND: break; + case MandatorySideStakeStatus::UNKNOWN: return "Unknown"; + case MandatorySideStakeStatus::DELETED: return "Deleted"; + case MandatorySideStakeStatus::MANDATORY: return "Mandatory"; + case MandatorySideStakeStatus::OUT_OF_BOUND: break; } assert(false); // Suppress warning @@ -134,12 +130,13 @@ std::string SideStake::StatusToString(const SideStakeStatus& status, const bool& return std::string{}; } -bool SideStake::operator==(SideStake b) +bool MandatorySideStake::operator==(MandatorySideStake b) { bool result = true; result &= (m_address == b.m_address); result &= (m_allocation == b.m_allocation); + result &= (m_description == b.m_description); result &= (m_timestamp == b.m_timestamp); result &= (m_hash == b.m_hash); result &= (m_previous_hash == b.m_previous_hash); @@ -148,11 +145,155 @@ bool SideStake::operator==(SideStake b) return result; } -bool SideStake::operator!=(SideStake b) +bool MandatorySideStake::operator!=(MandatorySideStake b) { return !(*this == b); } +// ----------------------------------------------------------------------------- +// Class: LocalSideStake +// ----------------------------------------------------------------------------- +LocalSideStake::LocalSideStake() + : m_address() + , m_allocation() + , m_description() + , m_status(LocalSideStakeStatus::UNKNOWN) +{} + +LocalSideStake::LocalSideStake(CBitcoinAddressForStorage address, double allocation, std::string description) + : m_address(address) + , m_allocation(allocation) + , m_description(description) + , m_status(LocalSideStakeStatus::UNKNOWN) +{} + +LocalSideStake::LocalSideStake(CBitcoinAddressForStorage address, + double allocation, + std::string description, + LocalSideStakeStatus status) + : m_address(address) + , m_allocation(allocation) + , m_description(description) + , m_status(status) +{} + +bool LocalSideStake::WellFormed() const +{ + return m_address.IsValid() && m_allocation >= 0.0 && m_allocation <= 1.0; +} + +std::string LocalSideStake::StatusToString() const +{ + return StatusToString(m_status.Value()); +} + +std::string LocalSideStake::StatusToString(const LocalSideStakeStatus& status, const bool& translated) const +{ + if (translated) { + switch(status) { + case LocalSideStakeStatus::UNKNOWN: return _("Unknown"); + case LocalSideStakeStatus::ACTIVE: return _("Active"); + case LocalSideStakeStatus::INACTIVE: return _("Inactive"); + case LocalSideStakeStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } else { + // The untranslated versions are really meant to serve as the string equivalent of the enum values. + switch(status) { + case LocalSideStakeStatus::UNKNOWN: return "Unknown"; + case LocalSideStakeStatus::ACTIVE: return "Active"; + case LocalSideStakeStatus::INACTIVE: return "Inactive"; + case LocalSideStakeStatus::OUT_OF_BOUND: break; + } + + assert(false); // Suppress warning + } + + // This will never be reached. Put it in anyway to prevent control reaches end of non-void function warning + // from some compiler versions. + return std::string{}; +} + +bool LocalSideStake::operator==(LocalSideStake b) +{ + bool result = true; + + result &= (m_address == b.m_address); + result &= (m_allocation == b.m_allocation); + result &= (m_description == b.m_description); + result &= (m_status == b.m_status); + + return result; +} + +bool LocalSideStake::operator!=(LocalSideStake b) +{ + return !(*this == b); +} + +// ----------------------------------------------------------------------------- +// Class: SideStake +// ----------------------------------------------------------------------------- +SideStake::SideStake() + : m_local_sidestake_ptr(nullptr) + , m_mandatory_sidestake_ptr(nullptr) + , m_mandatory(false) +{} + +SideStake::SideStake(LocalSideStake_ptr sidestake_ptr) + : m_local_sidestake_ptr(sidestake_ptr) + , m_mandatory_sidestake_ptr(nullptr) + , m_mandatory(false) +{} + +SideStake::SideStake(MandatorySideStake_ptr sidestake_ptr) + : m_local_sidestake_ptr(nullptr) + , m_mandatory_sidestake_ptr(sidestake_ptr) + , m_mandatory(true) +{} + +bool SideStake::IsMandatory() const +{ + return m_mandatory; +} + +CBitcoinAddress SideStake::GetAddress() const +{ + if (IsMandatory()) { + return m_mandatory_sidestake_ptr->m_address; + } else { + return m_local_sidestake_ptr->m_address; + } +} + +double SideStake::GetAllocation() const +{ + if (IsMandatory()) { + return m_mandatory_sidestake_ptr->m_allocation; + } else { + return m_local_sidestake_ptr->m_allocation; + } +} + +std::string SideStake::GetDescription() const +{ + if (IsMandatory()) { + return m_mandatory_sidestake_ptr->m_description; + } else { + return m_local_sidestake_ptr->m_description; + } +} + +std::string SideStake::StatusToString() const +{ + if (IsMandatory()) { + return m_mandatory_sidestake_ptr->StatusToString(); + } else { + return m_local_sidestake_ptr->StatusToString(); + } +} + // ----------------------------------------------------------------------------- // Class: SideStakePayload // ----------------------------------------------------------------------------- @@ -169,21 +310,21 @@ SideStakePayload::SideStakePayload(const uint32_t version, CBitcoinAddressForStorage address, double allocation, std::string description, - SideStakeStatus status) + MandatorySideStake::MandatorySideStakeStatus status) : IContractPayload() , m_version(version) - , m_entry(SideStake(address, allocation, description, 0, uint256{}, status)) + , m_entry(MandatorySideStake(address, allocation, description, 0, uint256{}, status)) { } -SideStakePayload::SideStakePayload(const uint32_t version, SideStake entry) +SideStakePayload::SideStakePayload(const uint32_t version, MandatorySideStake entry) : IContractPayload() , m_version(version) , m_entry(std::move(entry)) { } -SideStakePayload::SideStakePayload(SideStake entry) +SideStakePayload::SideStakePayload(MandatorySideStake entry) : SideStakePayload(CURRENT_VERSION, std::move(entry)) { } @@ -197,12 +338,12 @@ const std::vector SideStakeRegistry::SideStakeEntries() const LOCK(cs_lock); - for (const auto& entry : m_sidestake_entries) { - sidestakes.push_back(entry.second); + for (const auto& entry : m_mandatory_sidestake_entries) { + sidestakes.push_back(std::make_shared(entry.second)); } for (const auto& entry : m_local_sidestake_entries) { - sidestakes.push_back(entry.second); + sidestakes.push_back(std::make_shared(entry.second)); } return sidestakes; @@ -223,11 +364,12 @@ const std::vector SideStakeRegistry::ActiveSideStakeEntries(const // Do mandatory sidestakes first. if (!local_only) { - for (const auto& entry : m_sidestake_entries) + for (const auto& entry : m_mandatory_sidestake_entries) { - if (entry.second->m_status == SideStakeStatus::MANDATORY && allocation_sum + entry.second->m_allocation <= 1.0) { + if (entry.second->m_status == MandatorySideStake::MandatorySideStakeStatus::MANDATORY + && allocation_sum + entry.second->m_allocation <= 1.0) { if ((include_zero_alloc && entry.second->m_allocation == 0.0) || entry.second->m_allocation > 0.0) { - sidestakes.push_back(entry.second); + sidestakes.push_back(std::make_shared(entry.second)); allocation_sum += entry.second->m_allocation; } } @@ -242,9 +384,10 @@ const std::vector SideStakeRegistry::ActiveSideStakeEntries(const for (const auto& entry : m_local_sidestake_entries) { - if (entry.second->m_status == SideStakeStatus::ACTIVE && allocation_sum + entry.second->m_allocation <= 1.0) { + if (entry.second->m_status == LocalSideStake::LocalSideStakeStatus::ACTIVE + && allocation_sum + entry.second->m_allocation <= 1.0) { if ((include_zero_alloc && entry.second->m_allocation == 0.0) || entry.second->m_allocation > 0.0) { - sidestakes.push_back(entry.second); + sidestakes.push_back(std::make_shared(entry.second)); allocation_sum += entry.second->m_allocation; } } @@ -261,17 +404,17 @@ std::vector SideStakeRegistry::Try(const CBitcoinAddressForStorag std::vector result; if (!local_only) { - const auto mandatory_entry = m_sidestake_entries.find(key); + const auto mandatory_entry = m_mandatory_sidestake_entries.find(key); - if (mandatory_entry != m_sidestake_entries.end()) { - result.push_back(mandatory_entry->second); + if (mandatory_entry != m_mandatory_sidestake_entries.end()) { + result.push_back(std::make_shared(mandatory_entry->second)); } } const auto local_entry = m_local_sidestake_entries.find(key); if (local_entry != m_local_sidestake_entries.end()) { - result.push_back(local_entry->second); + result.push_back(std::make_shared(local_entry->second)); } return result; @@ -284,8 +427,14 @@ std::vector SideStakeRegistry::TryActive(const CBitcoinAddressFor std::vector result; for (const auto& iter : Try(key, local_only)) { - if (iter->m_status == SideStakeStatus::MANDATORY || iter->m_status == SideStakeStatus::ACTIVE) { - result.push_back(iter); + if (iter->IsMandatory()) { + if (iter->m_mandatory_sidestake_ptr->m_status == MandatorySideStake::MandatorySideStakeStatus::MANDATORY) { + result.push_back(iter); + } + } else { + if (iter->m_local_sidestake_ptr->m_status == LocalSideStake::LocalSideStakeStatus::ACTIVE) { + result.push_back(iter); + } } } @@ -296,7 +445,7 @@ void SideStakeRegistry::Reset() { LOCK(cs_lock); - m_sidestake_entries.clear(); + m_mandatory_sidestake_entries.clear(); m_sidestake_db.clear(); } @@ -320,17 +469,17 @@ void SideStakeRegistry::AddDelete(const ContractContext& ctx) // 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; + payload.m_entry.m_status = MandatorySideStake::MandatorySideStakeStatus::DELETED; } LOCK(cs_lock); - auto sidestake_entry_pair_iter = m_sidestake_entries.find(payload.m_entry.m_address); + auto sidestake_entry_pair_iter = m_mandatory_sidestake_entries.find(payload.m_entry.m_address); - SideStake_ptr current_sidestake_entry_ptr = nullptr; + MandatorySideStake_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()); + bool current_sidestake_entry_present = (sidestake_entry_pair_iter != m_mandatory_sidestake_entries.end()); // If so, then get a smart pointer to it. if (current_sidestake_entry_present) { @@ -356,7 +505,7 @@ void SideStakeRegistry::AddDelete(const ContractContext& ctx) payload.m_entry.StatusToString() ); - SideStake& historical = payload.m_entry; + MandatorySideStake& historical = payload.m_entry; if (!m_sidestake_db.insert(ctx.m_tx.GetHash(), height, historical)) { @@ -370,28 +519,33 @@ void SideStakeRegistry::AddDelete(const ContractContext& ctx) } // Finally, insert the new SideStake entry (payload) smart pointer into the m_sidestake_entries map. - m_sidestake_entries[payload.m_entry.m_address] = m_sidestake_db.find(ctx.m_tx.GetHash())->second; + m_mandatory_sidestake_entries[payload.m_entry.m_address] = m_sidestake_db.find(ctx.m_tx.GetHash())->second; return; } -void SideStakeRegistry::NonContractAdd(const SideStake& sidestake, const bool& save_to_file) +void SideStakeRegistry::Add(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void SideStakeRegistry::Delete(const ContractContext& ctx) +{ + AddDelete(ctx); +} + +void SideStakeRegistry::NonContractAdd(const LocalSideStake& sidestake, const bool& save_to_file) { LOCK(cs_lock); - // Using this form of insert because we want the latest record with the same key to override any previous one. - m_local_sidestake_entries[sidestake.m_address] = std::make_shared(sidestake); + // Using this form of insert because we want the latest record with the same key to override any previous one. + m_local_sidestake_entries[sidestake.m_address] = std::make_shared(sidestake); if (save_to_file) { SaveLocalSideStakesToConfig(); } } -void SideStakeRegistry::Add(const ContractContext& ctx) -{ - AddDelete(ctx); -} - void SideStakeRegistry::NonContractDelete(const CBitcoinAddressForStorage& address, const bool& save_to_file) { LOCK(cs_lock); @@ -407,11 +561,6 @@ void SideStakeRegistry::NonContractDelete(const CBitcoinAddressForStorage& addre } } -void SideStakeRegistry::Delete(const ContractContext& ctx) -{ - AddDelete(ctx); -} - void SideStakeRegistry::Revert(const ContractContext& ctx) { const auto payload = ctx->SharePayloadAs(); @@ -421,9 +570,9 @@ void SideStakeRegistry::Revert(const ContractContext& ctx) // resurrect. LOCK(cs_lock); - auto entry_to_revert = m_sidestake_entries.find(payload->m_entry.m_address); + auto entry_to_revert = m_mandatory_sidestake_entries.find(payload->m_entry.m_address); - if (entry_to_revert == m_sidestake_entries.end()) { + if (entry_to_revert == m_mandatory_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_address.ToString()); @@ -440,7 +589,7 @@ void SideStakeRegistry::Revert(const ContractContext& ctx) // 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_address) == 0) { + if (m_mandatory_sidestake_entries.erase(payload->m_entry.m_address) == 0) { error("%s: The SideStake entry to erase during a SideStake entry revert for key %s was not found.", __func__, key.ToString()); @@ -474,7 +623,7 @@ void SideStakeRegistry::Revert(const ContractContext& ctx) // 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_address] = resurrect_entry->second; + m_mandatory_sidestake_entries[resurrect_entry->second->m_address] = resurrect_entry->second; } } @@ -506,12 +655,12 @@ int SideStakeRegistry::Initialize() { LOCK(cs_lock); - int height = m_sidestake_db.Initialize(m_sidestake_entries, m_pending_sidestake_entries); + int height = m_sidestake_db.Initialize(m_mandatory_sidestake_entries, m_pending_sidestake_entries); SubscribeToCoreSignals(); 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()); + LogPrint(LogFlags::CONTRACT, "INFO: %s: m_sidestake_entries size after load: %u", __func__, m_mandatory_sidestake_entries.size()); // Add the local sidestakes specified in the config file(s) to the local sidestakes map. LoadLocalSideStakesFromConfig(); @@ -544,7 +693,7 @@ void SideStakeRegistry::ResetInMemoryOnly() LOCK(cs_lock); m_local_sidestake_entries.clear(); - m_sidestake_entries.clear(); + m_mandatory_sidestake_entries.clear(); m_sidestake_db.clear_in_memory_only(); } @@ -567,7 +716,7 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig() return; } - std::vector vSideStakes; + std::vector vLocalSideStakes; std::vector> raw_vSideStakeAlloc; double dSumAllocation = 0.0; @@ -631,8 +780,9 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig() // First, determine allocation already taken by mandatory sidestakes, because they must be allocated first. for (const auto& entry : SideStakeEntries()) { - if (entry->m_status == SideStakeStatus::MANDATORY) { - dSumAllocation += entry->m_allocation; + if (entry->IsMandatory() + && entry->m_mandatory_sidestake_ptr->m_status == MandatorySideStake::MandatorySideStakeStatus::MANDATORY) { + dSumAllocation += entry->m_mandatory_sidestake_ptr->m_allocation; } } @@ -679,19 +829,17 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig() break; } - SideStake sidestake(static_cast(address), - dAllocation, - std::get<2>(entry), - 0, - uint256{}, - SideStakeStatus::ACTIVE); + LocalSideStake sidestake(static_cast(address), + dAllocation, + std::get<2>(entry), + LocalSideStake::LocalSideStakeStatus::ACTIVE); // This will add or update (replace) a non-contract entry in the registry for the local sidestake. NonContractAdd(sidestake, false); // This is needed because we need to detect entries in the registry map that are no longer in the config file to mark // them deleted. - vSideStakes.push_back(sidestake); + vLocalSideStakes.push_back(sidestake); LogPrint(BCLog::LogFlags::MINER, "INFO: %s: SideStakeAlloc Address %s, Allocation %f", __func__, sAddress, dAllocation); @@ -700,13 +848,13 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig() for (auto& entry : m_local_sidestake_entries) { // Only look at active entries. The others are NA for this alignment. - if (entry.second->m_status == SideStakeStatus::ACTIVE) { - auto iter = std::find(vSideStakes.begin(), vSideStakes.end(), *entry.second); + if (entry.second->m_status == LocalSideStake::LocalSideStakeStatus::ACTIVE) { + auto iter = std::find(vLocalSideStakes.begin(), vLocalSideStakes.end(), *entry.second); - if (iter == vSideStakes.end()) { + if (iter == vLocalSideStakes.end()) { // Entry in map is no longer found in config files, so mark map entry inactive. - entry.second->m_status = SideStakeStatus::INACTIVE; + entry.second->m_status = LocalSideStake::LocalSideStakeStatus::INACTIVE; } } } diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h index 6705b1c527..6a2601d5cd 100644 --- a/src/gridcoin/sidestake.h +++ b/src/gridcoin/sidestake.h @@ -39,35 +39,146 @@ class CBitcoinAddressForStorage : public CBitcoinAddress } }; - -enum class SideStakeStatus +//! +//! \brief The LocalSideStake class. This class formalizes the local 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. +//! +//! 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 in any GUI implementation on top of this. +//! +class LocalSideStake { - UNKNOWN, - ACTIVE, //!< A user specified sidestake that is active - INACTIVE, //!< A user specified sidestake that is inactive - DELETED, //!< A mandatory sidestake that has been deleted by contract - MANDATORY, //!< An active mandatory sidetake by contract - OUT_OF_BOUND +public: + enum class LocalSideStakeStatus + { + UNKNOWN, + ACTIVE, //!< A user specified sidestake that is active + INACTIVE, //!< A user specified sidestake that is inactive + OUT_OF_BOUND + }; + + //! + //! \brief Wrapped Enumeration of sidestake entry status, mainly for serialization/deserialization. + //! + using Status = EnumByte; + + CBitcoinAddressForStorage m_address; //!< The Gridcoin Address of the sidestake destination. + + double m_allocation; //!< The allocation is a double precision floating point between 0.0 and 1.0 inclusive + + std::string m_description; //!< The description of the sidestake (optional) + + 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. + //! + LocalSideStake(); + + //! + //! \brief Initialize a sidestake instance with the provided address and allocation. This is used to construct a user + //! specified sidestake. + //! + //! \param address + //! \param allocation + //! \param description (optional) + //! + LocalSideStake(CBitcoinAddressForStorage address, double allocation, std::string description); + + //! + //! \brief Initialize a sidestake instance with the provided parameters. + //! + //! \param address + //! \param allocation + //! \param description (optional) + //! \param status + //! + LocalSideStake(CBitcoinAddressForStorage address, double allocation, std::string description, LocalSideStakeStatus status); + + //! + //! \brief Determine whether a sidestake contains each of the required elements. + //! \return true if the sidestake is well-formed. + //! + bool WellFormed() const; + + //! + //! \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. + //! + std::string StatusToString(const LocalSideStakeStatus& status, const bool& translated = true) const; + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side sidestake to compare for equality. + //! + //! \return Equal or not. + //! + bool operator==(LocalSideStake b); + + //! + //! \brief Comparison operator overload used in the unit test harness. + //! + //! \param b The right hand side sidestake to compare for equality. + //! + //! \return Equal or not. + //! + bool operator!=(LocalSideStake b); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_address); + READWRITE(m_allocation); + READWRITE(m_description); + READWRITE(m_status); + } }; //! -//! \brief The SideStake class. This class formalizes the "sidestake", which is a directive to apportion +//! \brief The type that defines a shared pointer to a local sidestake +//! +typedef std::shared_ptr LocalSideStake_ptr; + +//! +//! \brief The MandatorySideStake class. This class formalizes the mandatory 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). +//! Mandatory entries will be picked up by contract handlers similar to other contract types (cf. protocol entries). //! -class SideStake +class MandatorySideStake { public: + enum class MandatorySideStakeStatus + { + UNKNOWN, + DELETED, //!< A mandatory sidestake that has been deleted by contract + MANDATORY, //!< An active mandatory sidetake by contract + OUT_OF_BOUND + }; + //! //! \brief Wrapped Enumeration of sidestake entry status, mainly for serialization/deserialization. //! - using Status = EnumByte; + using Status = EnumByte; CBitcoinAddressForStorage m_address; //!< The Gridcoin Address of the sidestake destination. @@ -86,7 +197,7 @@ class SideStake //! //! \brief Initialize an empty, invalid sidestake instance. //! - SideStake(); + MandatorySideStake(); //! //! \brief Initialize a sidestake instance with the provided address and allocation. This is used to construct a user @@ -96,7 +207,7 @@ class SideStake //! \param allocation //! \param description (optional) //! - SideStake(CBitcoinAddressForStorage address, double allocation, std::string description); + MandatorySideStake(CBitcoinAddressForStorage address, double allocation, std::string description); //! //! \brief Initialize a sidestake instance with the provided parameters. @@ -106,7 +217,7 @@ class SideStake //! \param description (optional) //! \param status //! - SideStake(CBitcoinAddressForStorage address, double allocation, std::string description, SideStakeStatus status); + MandatorySideStake(CBitcoinAddressForStorage address, double allocation, std::string description, MandatorySideStakeStatus status); //! //! \brief Initialize a sidestake instance with the provided parameters. This form is normally used to construct a @@ -119,8 +230,8 @@ class SideStake //! \param hash //! \param status //! - SideStake(CBitcoinAddressForStorage address, double allocation, std::string description, int64_t timestamp, - uint256 hash, SideStakeStatus status); + MandatorySideStake(CBitcoinAddressForStorage address, double allocation, std::string description, int64_t timestamp, + uint256 hash, MandatorySideStakeStatus status); //! //! \brief Determine whether a sidestake contains each of the required elements. @@ -157,7 +268,7 @@ class SideStake //! //! \return SideStake status string. //! - std::string StatusToString(const SideStakeStatus& status, const bool& translated = true) const; + std::string StatusToString(const MandatorySideStakeStatus& status, const bool& translated = true) const; //! //! \brief Comparison operator overload used in the unit test harness. @@ -166,7 +277,7 @@ class SideStake //! //! \return Equal or not. //! - bool operator==(SideStake b); + bool operator==(MandatorySideStake b); //! //! \brief Comparison operator overload used in the unit test harness. @@ -175,7 +286,7 @@ class SideStake //! //! \return Equal or not. //! - bool operator!=(SideStake b); + bool operator!=(MandatorySideStake b); ADD_SERIALIZE_METHODS; @@ -193,7 +304,39 @@ class SideStake }; //! -//! \brief The type that defines a shared pointer to a sidestake +//! \brief The type that defines a shared pointer to a mandatory sidestake +//! +typedef std::shared_ptr MandatorySideStake_ptr; + +//! +//! \brief This is a convenience class that combines the mandatory and local sidestake classes into one for use in the registry +//! and the GUI code. +//! +class SideStake +{ +public: + SideStake(); + + SideStake(LocalSideStake_ptr sidestake_ptr); + + SideStake(MandatorySideStake_ptr sidestake_ptr); + + bool IsMandatory() const; + + CBitcoinAddress GetAddress() const; + double GetAllocation() const; + std::string GetDescription() const; + std::string StatusToString() const; + + LocalSideStake_ptr m_local_sidestake_ptr; + MandatorySideStake_ptr m_mandatory_sidestake_ptr; + +private: + bool m_mandatory; +}; + +//! +//! \brief The type that defines a shared pointer to a mandatory sidestake //! typedef std::shared_ptr SideStake_ptr; @@ -221,7 +364,7 @@ class SideStakePayload : public IContractPayload //! uint32_t m_version = CURRENT_VERSION; - SideStake m_entry; //!< The sidestake entry in the payload. + MandatorySideStake m_entry; //!< The sidestake entry in the payload. //! //! \brief Initialize an empty, invalid sidestake entry payload. @@ -238,7 +381,7 @@ class SideStakePayload : public IContractPayload //! \param status. Status of the sidestake entry //! SideStakePayload(const uint32_t version, CBitcoinAddressForStorage address, double allocation, - std::string description, SideStakeStatus status); + std::string description, MandatorySideStake::MandatorySideStakeStatus status); //! //! \brief Initialize a sidestake entry payload from the given sidestake entry @@ -247,7 +390,7 @@ class SideStakePayload : public IContractPayload //! \param version Version of the serialized sidestake entry format. //! \param sidestake_entry The sidestake entry itself. //! - SideStakePayload(const uint32_t version, SideStake sidestake_entry); + SideStakePayload(const uint32_t version, MandatorySideStake sidestake_entry); //! //! \brief Initialize a sidestake entry payload from the given sidestake entry @@ -255,7 +398,7 @@ class SideStakePayload : public IContractPayload //! //! \param sidestake_entry The sidestake entry itself. //! - SideStakePayload(SideStake sidestake_entry); + SideStakePayload(MandatorySideStake sidestake_entry); //! //! \brief Get the type of contract that this payload contains data for. @@ -363,16 +506,23 @@ class SideStakeRegistry : public IContractHandler }; //! - //! \brief The type that keys sidestake entries by their addresses. Note that the entries + //! \brief The type that keys local sidestake entries by their addresses. 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 LocalSideStakeMap; + + //! + //! \brief The type that keys mandatory sidestake entries by their addresses. 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; + typedef std::map MandatorySideStakeMap; //! //! \brief PendingSideStakeMap. This is not actually used but defined to satisfy the template. //! - typedef SideStakeMap PendingSideStakeMap; + typedef MandatorySideStakeMap PendingSideStakeMap; //! //! \brief The type that keys historical sidestake entries by the contract hash (txid). @@ -380,7 +530,7 @@ class SideStakeRegistry : public IContractHandler //! the same actual object can be held by both this map and the (current) sidestake entry map //! without object duplication. //! - typedef std::map HistoricalSideStakeMap; + typedef std::map HistoricalSideStakeMap; //! //! \brief Get the collection of current sidestake entries. Note that this INCLUDES deleted @@ -459,22 +609,30 @@ class SideStakeRegistry : public IContractHandler bool BlockValidate(const ContractContext& ctx, int& DoS) const override; //! - //! \brief Allows local (voluntary) sidestakes to be added to the in-memory local map and not persisted to - //! the registry db. + //! \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 SideStake object to add - //! \param bool save_to_file if true causes SaveLocalSideStakesToConfig() to be called. + //! \param ctx //! - void NonContractAdd(const SideStake& sidestake, const bool& save_to_file = true); + void Add(const ContractContext& ctx) override; //! - //! \brief Add a sidestake entry to the registry from contract data. For the sidestake registry + //! \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 Add(const ContractContext& ctx) override; + void Delete(const ContractContext& ctx) override; + + //! + //! \brief Allows local (voluntary) sidestakes to be added to the in-memory local map and not persisted to + //! the registry db. + //! + //! \param SideStake object to add + //! \param bool save_to_file if true causes SaveLocalSideStakesToConfig() to be called. + //! + void NonContractAdd(const LocalSideStake& sidestake, const bool& save_to_file = true); //! //! \brief Provides for deletion of local (voluntary) sidestakes from the in-memory local map that are not persisted @@ -485,14 +643,6 @@ class SideStakeRegistry : public IContractHandler //! void NonContractDelete(const CBitcoinAddressForStorage& address, const bool& save_to_file = true); - //! - //! \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 used @@ -560,10 +710,10 @@ class SideStakeRegistry : public IContractHandler //! //! \brief Specializes the template RegistryDB for the SideStake class //! - typedef RegistryDB SideStakeDB; @@ -592,9 +742,9 @@ class SideStakeRegistry : public IContractHandler void SubscribeToCoreSignals(); void UnsubscribeFromCoreSignals(); - SideStakeMap m_local_sidestake_entries; //!< Contains the local (non-contract) sidestake entries. - SideStakeMap m_sidestake_entries; //!< Contains the mandatory sidestake entries, including DELETED. - PendingSideStakeMap m_pending_sidestake_entries {}; //!< Not used. Only to satisfy the template. + LocalSideStakeMap m_local_sidestake_entries; //!< Contains the local (non-contract) sidestake entries. + MandatorySideStakeMap m_mandatory_sidestake_entries; //!< Contains the mandatory sidestake entries, including DELETED. + PendingSideStakeMap m_pending_sidestake_entries {}; //!< Not used. Only to satisfy the template. SideStakeDB m_sidestake_db; diff --git a/src/miner.cpp b/src/miner.cpp index a79be4ab23..7150ad4e81 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -921,26 +921,28 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake (iterSideStake != vSideStakeAlloc.end()) && (nOutputsUsed <= nMaxSideStakeOutputs); ++iterSideStake) { - CBitcoinAddress& address = iterSideStake->get()->m_address; + CBitcoinAddress address = iterSideStake->get()->GetAddress(); + double allocation = iterSideStake->get()->GetAllocation(); + if (!address.IsValid()) { LogPrintf("WARN: SplitCoinStakeOutput: ignoring sidestake invalid address %s.", - iterSideStake->get()->m_address.ToString()); + address.ToString()); continue; } // Do not process a distribution that would result in an output less than 1 CENT. This will flow back into // the coinstake below. Prevents dust build-up. - if (nReward * iterSideStake->get()->m_allocation < CENT) + if (nReward * allocation < CENT) { LogPrintf("WARN: SplitCoinStakeOutput: distribution %f too small to address %s.", - CoinToDouble(nReward * iterSideStake->get()->m_allocation), - iterSideStake->get()->m_address.ToString() + CoinToDouble(nReward * allocation), + address.ToString() ); continue; } - if (dSumAllocation + iterSideStake->get()->m_allocation > 1.0) + if (dSumAllocation + allocation > 1.0) { LogPrintf("WARN: SplitCoinStakeOutput: allocation percentage over 100 percent, " "ending sidestake allocations."); @@ -963,11 +965,11 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake int64_t nSideStake = 0; // For allocations ending less than 100% assign using sidestake allocation. - if (dSumAllocation + iterSideStake->get()->m_allocation < 1.0) - nSideStake = nReward * iterSideStake->get()->m_allocation; + if (dSumAllocation + allocation < 1.0) + nSideStake = nReward * allocation; // We need to handle the final sidestake differently in the case it brings the total allocation up to 100%, // because testing showed in corner cases the output return to the staking address could be off by one Halford. - else if (dSumAllocation + iterSideStake->get()->m_allocation == 1.0) + else if (dSumAllocation + allocation == 1.0) // Simply assign the special case final nSideStake the remaining output value minus input value to ensure // a match on the output flowing down. nSideStake = nRemainingStakeOutputValue - nInputValue; @@ -976,10 +978,10 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake LogPrintf("SplitCoinStakeOutput: create sidestake UTXO %i value %f to address %s", nOutputsUsed, - CoinToDouble(nReward * iterSideStake->get()->m_allocation), - iterSideStake->get()->m_address.ToString() + CoinToDouble(nReward * allocation), + address.ToString() ); - dSumAllocation += iterSideStake->get()->m_allocation; + dSumAllocation += allocation; nRemainingStakeOutputValue -= nSideStake; nOutputsUsed++; } diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 98c898ac4b..26bd13b1d3 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -650,8 +650,7 @@ void OptionsDialog::sidestakeSelectionChanged() if (indexes.size() > 1) { ui->pushButtonEditSideStake->setEnabled(false); ui->pushButtonDeleteSideStake->setEnabled(false); - } else if (static_cast(indexes.at(0).internalPointer())->m_status - == GRC::SideStakeStatus::MANDATORY) { + } else if (static_cast(indexes.at(0).internalPointer())->IsMandatory()) { ui->pushButtonEditSideStake->setEnabled(false); ui->pushButtonDeleteSideStake->setEnabled(false); } else { diff --git a/src/qt/sidestaketablemodel.cpp b/src/qt/sidestaketablemodel.cpp index 85d7ce9b9c..14bc935f60 100644 --- a/src/qt/sidestaketablemodel.cpp +++ b/src/qt/sidestaketablemodel.cpp @@ -34,15 +34,35 @@ bool SideStakeLessThan::operator()(const GRC::SideStake& left, const GRC::SideSt std::swap(pLeft, pRight); } + int left_status, right_status; + + if (pLeft->IsMandatory()) { + left_status = static_cast(pLeft->m_mandatory_sidestake_ptr->m_status.Value()); + } else { + // For purposes of comparison, the enum value for local sidestake is shifted by the max entry of the mandatory + // status enum. + left_status = static_cast(pLeft->m_local_sidestake_ptr->m_status.Value()) + + static_cast(GRC::MandatorySideStake::MandatorySideStakeStatus::OUT_OF_BOUND); + } + + if (pRight->IsMandatory()) { + right_status = static_cast(pRight->m_mandatory_sidestake_ptr->m_status.Value()); + } else { + // For purposes of comparison, the enum value for local sidestake is shifted by the max entry of the mandatory + // status enum. + right_status = static_cast(pRight->m_local_sidestake_ptr->m_status.Value()) + + static_cast(GRC::MandatorySideStake::MandatorySideStakeStatus::OUT_OF_BOUND); + } + switch (static_cast(m_column)) { case SideStakeTableModel::Address: - return pLeft->m_address < pRight->m_address; + return pLeft->GetAddress() < pRight->GetAddress(); case SideStakeTableModel::Allocation: - return pLeft->m_allocation < pRight->m_allocation; + return pLeft->GetAllocation() < pRight->GetAllocation(); case SideStakeTableModel::Description: - return pLeft->m_description.compare(pRight->m_description) < 0; + return pLeft->GetDescription().compare(pRight->GetDescription()) < 0; case SideStakeTableModel::Status: - return pLeft->m_status < pRight->m_status; + return left_status < right_status; } // no default case, so the compiler can warn about missing cases assert(false); } @@ -84,7 +104,6 @@ class SideStakeTablePriv return nullptr; } - }; SideStakeTableModel::SideStakeTableModel(OptionsModel* parent) @@ -131,11 +150,11 @@ QVariant SideStakeTableModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole || role == Qt::EditRole) { switch (column) { case Address: - return QString::fromStdString(rec->m_address.ToString()); + return QString::fromStdString(rec->GetAddress().ToString()); case Allocation: - return rec->m_allocation * 100.0; + return rec->GetAllocation() * 100.0; case Description: - return QString::fromStdString(rec->m_description); + return QString::fromStdString(rec->GetDescription()); case Status: return QString::fromStdString(rec->StatusToString()); } // no default case, so the compiler can warn about missing cases @@ -181,8 +200,7 @@ bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &valu CBitcoinAddress address; address.SetString(value.toString().toStdString()); - - if (rec->m_address == address) { + if (rec->GetAddress() == address) { m_edit_status = NO_CHANGES; return false; } else if (!address.IsValid()) { @@ -209,15 +227,29 @@ bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &valu // Save the original local sidestake (also in the core). GRC::SideStake orig_sidestake = *rec; + CBitcoinAddress orig_address = rec->GetAddress(); + double orig_allocation = rec->GetAllocation(); + std::string orig_description = rec->GetDescription(); + + // We really only need the local sidestake status enum here + GRC::LocalSideStake::Status orig_status; + + if (!rec->IsMandatory()) { + orig_status = rec->m_local_sidestake_ptr->m_status; + } + for (const auto& entry : registry.ActiveSideStakeEntries(false, true)) { - if (entry->m_address == orig_sidestake.m_address) { + CBitcoinAddress address = entry->GetAddress(); + double allocation = entry->GetAllocation(); + + if (address == orig_address) { continue; } - prior_total_allocation += entry->m_allocation * 100.0; + prior_total_allocation += allocation * 100.0; } - if (rec->m_allocation * 100.0 == value.toDouble()) { + if (orig_allocation * 100.0 == value.toDouble()) { m_edit_status = NO_CHANGES; return false; } @@ -233,15 +265,13 @@ bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &valu } // Delete the original sidestake - registry.NonContractDelete(orig_sidestake.m_address, false); + registry.NonContractDelete(orig_address, false); // Add back the sidestake with the modified allocation - registry.NonContractAdd(GRC::SideStake(orig_sidestake.m_address, + registry.NonContractAdd(GRC::LocalSideStake(orig_address, value.toDouble() / 100.0, - orig_sidestake.m_description, - int64_t {0}, - uint256 {}, - orig_sidestake.m_status.Value()), true); + orig_description, + orig_status.Value()), true); break; } @@ -250,7 +280,7 @@ bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &valu std::string orig_value = value.toString().toStdString(); std::string san_value = SanitizeString(orig_value, SAFE_CHARS_CSV); - if (rec->m_description == orig_value) { + if (rec->GetDescription() == orig_value) { m_edit_status = NO_CHANGES; return false; } @@ -264,15 +294,13 @@ bool SideStakeTableModel::setData(const QModelIndex &index, const QVariant &valu GRC::SideStake orig_sidestake = *rec; // Delete the original sidestake - registry.NonContractDelete(orig_sidestake.m_address, false); + registry.NonContractDelete(orig_sidestake.GetAddress(), false); // Add back the sidestake with the modified allocation - registry.NonContractAdd(GRC::SideStake(orig_sidestake.m_address, - orig_sidestake.m_allocation, - san_value, - int64_t {0}, - uint256 {}, - orig_sidestake.m_status.Value()), true); + registry.NonContractAdd(GRC::LocalSideStake(orig_sidestake.GetAddress(), + orig_sidestake.GetAllocation(), + san_value, + orig_sidestake.m_local_sidestake_ptr->m_status.Value()), true); break; } @@ -308,7 +336,8 @@ Qt::ItemFlags SideStakeTableModel::flags(const QModelIndex &index) const Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled; - if (rec->m_status == GRC::SideStakeStatus::ACTIVE + if (!rec->IsMandatory() + && rec->m_local_sidestake_ptr->m_status == GRC::LocalSideStake::LocalSideStakeStatus::ACTIVE && (index.column() == Allocation || index.column() == Description)) { retval |= Qt::ItemIsEditable; } @@ -350,7 +379,7 @@ QString SideStakeTableModel::addRow(const QString &address, const QString &alloc // Get total allocation of all active/mandatory sidestake entries for (const auto& entry : registry.ActiveSideStakeEntries(false, true)) { - prior_total_allocation += entry->m_allocation * 100.0; + prior_total_allocation += entry->GetAllocation() * 100.0; } if (!core_local_sidestake.empty()) { @@ -376,12 +405,10 @@ QString SideStakeTableModel::addRow(const QString &address, const QString &alloc return QString(); } - registry.NonContractAdd(GRC::SideStake(sidestake_address, - sidestake_allocation, - sanitized_description, - int64_t {0}, - uint256 {}, - GRC::SideStakeStatus::ACTIVE)); + registry.NonContractAdd(GRC::LocalSideStake(sidestake_address, + sidestake_allocation, + sanitized_description, + GRC::LocalSideStake::LocalSideStakeStatus::ACTIVE)); updateSideStakeTableModel(); @@ -393,14 +420,14 @@ bool SideStakeTableModel::removeRows(int row, int count, const QModelIndex &pare Q_UNUSED(parent); GRC::SideStake* rec = m_priv->index(row); - if(count != 1 || !rec || rec->m_status == GRC::SideStakeStatus::MANDATORY) + if (count != 1 || !rec || rec->IsMandatory()) { // Can only remove one row at a time, and cannot remove rows not in model. // Also refuse to remove mandatory sidestakes. return false; } - GRC::GetSideStakeRegistry().NonContractDelete(rec->m_address); + GRC::GetSideStakeRegistry().NonContractDelete(rec->GetAddress()); updateSideStakeTableModel(); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c06cbec093..bc5b0a4c6c 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2496,14 +2496,14 @@ UniValue addkey(const UniValue& params, bool fHelp) } contract = GRC::MakeContract( - contract_version, // Contract version number (3+) - action, // Contract action - uint32_t {1}, // Contract payload version number - sidestake_address, // Sidestake address - allocation, // Sidestake allocation - description, // Sidestake description - GRC::SideStakeStatus::MANDATORY // sidestake status - ); + contract_version, // Contract version number (3+) + action, // Contract action + uint32_t {1}, // Contract payload version number + sidestake_address, // Sidestake address + allocation, // Sidestake allocation + description, // Sidestake description + GRC::MandatorySideStake::MandatorySideStakeStatus::MANDATORY // sidestake status + ); } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Sidestake contracts are not valid for block version less than v13."); } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 55cd761167..de16010f16 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -117,8 +117,8 @@ UniValue getstakinginfo(const UniValue& params, bool fHelp) // sidestakes are always included. for (const auto& alloc : vSideStakeAlloc) { - sidestakingalloc.pushKV("address", alloc->m_address.ToString()); - sidestakingalloc.pushKV("allocation_pct", alloc->m_allocation * 100); + sidestakingalloc.pushKV("address", alloc->GetAddress().ToString()); + sidestakingalloc.pushKV("allocation_pct", alloc->GetAllocation() * 100); sidestakingalloc.pushKV("status", alloc->StatusToString()); vsidestakingalloc.push_back(sidestakingalloc);