diff --git a/src/gridcoin/contract/contract.cpp b/src/gridcoin/contract/contract.cpp index bb6b2a4b36..8630b5f532 100644 --- a/src/gridcoin/contract/contract.cpp +++ b/src/gridcoin/contract/contract.cpp @@ -16,6 +16,7 @@ #include "gridcoin/project.h" #include "gridcoin/researcher.h" #include "gridcoin/scraper/scraper_registry.h" +#include "gridcoin/sidestake.h" #include "gridcoin/support/block_finder.h" #include "gridcoin/support/xml.h" #include "gridcoin/tx_message.h" @@ -276,6 +277,7 @@ class Dispatcher case ContractType::SCRAPER: return GetScraperRegistry(); case ContractType::VOTE: return GetPollRegistry(); case ContractType::MRC: return m_mrc_contract_handler; + case ContractType::SIDESTAKE: return GetSideStakeRegistry(); default: return m_unknown_handler; } } @@ -677,12 +679,13 @@ bool Contract::RequiresMasterKey() const // beacons by signing them with the original private key: return m_version == 1 && m_action == ContractAction::REMOVE; - case ContractType::POLL: return m_action == ContractAction::REMOVE; - case ContractType::PROJECT: return true; - case ContractType::PROTOCOL: return true; - case ContractType::SCRAPER: return true; - case ContractType::VOTE: return m_action == ContractAction::REMOVE; - default: return false; + case ContractType::POLL: return m_action == ContractAction::REMOVE; + case ContractType::PROJECT: return true; + case ContractType::PROTOCOL: return true; + case ContractType::SCRAPER: return true; + case ContractType::VOTE: return m_action == ContractAction::REMOVE; + case ContractType::SIDESTAKE: return true; + default: return false; } } @@ -693,10 +696,23 @@ CAmount Contract::RequiredBurnAmount() const bool Contract::WellFormed() const { - return m_version > 0 && m_version <= Contract::CURRENT_VERSION - && m_type != ContractType::UNKNOWN - && m_action != ContractAction::UNKNOWN - && m_body.WellFormed(m_action.Value()); + bool result = m_version > 0 && m_version <= Contract::CURRENT_VERSION + && m_type != ContractType::UNKNOWN + && m_action != ContractAction::UNKNOWN + && m_body.WellFormed(m_action.Value()); + + if (!result) { + LogPrint(BCLog::LogFlags::CONTRACT, "WARN: %s: Contract was not well formed. m_version = %u, m_type = %s, " + "m_action = %s, m_body.Wellformed(m_action.Value()) = %u", + __func__, + m_version, + m_type.ToString(), + m_action.ToString(), + m_body.WellFormed(m_action.Value()) + ); + } + + return result; } ContractPayload Contract::SharePayload() const @@ -761,6 +777,7 @@ Contract::Type Contract::Type::Parse(std::string input) if (input == "scraper") return ContractType::SCRAPER; if (input == "protocol") return ContractType::PROTOCOL; if (input == "message") return ContractType::MESSAGE; + if (input == "sidestake") return ContractType::SIDESTAKE; return ContractType::UNKNOWN; } @@ -777,6 +794,7 @@ std::string Contract::Type::ToString() const case ContractType::PROTOCOL: return "protocol"; case ContractType::SCRAPER: return "scraper"; case ContractType::VOTE: return "vote"; + case ContractType::SIDESTAKE: return "sidestake"; default: return ""; } } @@ -793,6 +811,7 @@ std::string Contract::Type::ToString(ContractType contract_type) case ContractType::PROTOCOL: return "protocol"; case ContractType::SCRAPER: return "scraper"; case ContractType::VOTE: return "vote"; + case ContractType::SIDESTAKE: return "sidestake"; default: return ""; } } @@ -809,6 +828,7 @@ std::string Contract::Type::ToTranslatedString(ContractType contract_type) case ContractType::PROTOCOL: return _("protocol"); case ContractType::SCRAPER: return _("scraper"); case ContractType::VOTE: return _("vote"); + case ContractType::SIDESTAKE: return _("sidestake"); default: return ""; } } @@ -905,6 +925,9 @@ ContractPayload Contract::Body::ConvertFromLegacy(const ContractType type, uint3 case ContractType::VOTE: return ContractPayload::Make( LegacyVote::Parse(legacy.m_key, legacy.m_value)); + case ContractType::SIDESTAKE: + // Sidestakes have no legacy representation as a contract. + assert(false && "Attempted to convert non-existent legacy sidestake contract."); case ContractType::OUT_OF_BOUND: assert(false); } @@ -961,6 +984,9 @@ void Contract::Body::ResetType(const ContractType type) case ContractType::VOTE: m_payload.Reset(new Vote()); break; + case ContractType::SIDESTAKE: + m_payload.Reset(new SideStakePayload()); + break; case ContractType::OUT_OF_BOUND: assert(false); } diff --git a/src/gridcoin/contract/registry.cpp b/src/gridcoin/contract/registry.cpp index cb7e85e5b5..aa618ef50e 100644 --- a/src/gridcoin/contract/registry.cpp +++ b/src/gridcoin/contract/registry.cpp @@ -11,6 +11,7 @@ const std::vector RegistryBookmarks::CONTRACT_TYPES_WITH_REG_ ContractType::PROJECT, ContractType::PROTOCOL, ContractType::SCRAPER, + ContractType::SIDESTAKE }; const std::vector RegistryBookmarks::CONTRACT_TYPES_SUPPORTING_REVERT = { @@ -20,6 +21,7 @@ const std::vector RegistryBookmarks::CONTRACT_TYPES_SUPPORTIN ContractType::PROTOCOL, ContractType::SCRAPER, ContractType::VOTE, + ContractType::SIDESTAKE }; } // namespace GRC diff --git a/src/gridcoin/contract/registry.h b/src/gridcoin/contract/registry.h index b92b840a5f..4132f465eb 100644 --- a/src/gridcoin/contract/registry.h +++ b/src/gridcoin/contract/registry.h @@ -9,6 +9,7 @@ #include "gridcoin/beacon.h" #include "gridcoin/project.h" #include "gridcoin/protocol.h" +#include "gridcoin/sidestake.h" #include "gridcoin/scraper/scraper_registry.h" #include "gridcoin/voting/registry.h" @@ -50,6 +51,7 @@ class RegistryBookmarks case ContractType::PROJECT: return GetWhitelist(); case ContractType::PROTOCOL: return GetProtocolRegistry(); case ContractType::SCRAPER: return GetScraperRegistry(); + case ContractType::SIDESTAKE: return GetSideStakeRegistry(); case ContractType::UNKNOWN: [[fallthrough]]; case ContractType::CLAIM: @@ -78,6 +80,7 @@ class RegistryBookmarks case ContractType::PROTOCOL: return GetProtocolRegistry(); case ContractType::SCRAPER: return GetScraperRegistry(); case ContractType::VOTE: return GetPollRegistry(); + case ContractType::SIDESTAKE: return GetSideStakeRegistry(); [[fallthrough]]; case ContractType::UNKNOWN: [[fallthrough]]; diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp index c06ad5e234..b9beec8389 100644 --- a/src/gridcoin/sidestake.cpp +++ b/src/gridcoin/sidestake.cpp @@ -182,16 +182,30 @@ const SideStakeRegistry::SideStakeMap& SideStakeRegistry::SideStakeEntries() con const std::vector SideStakeRegistry::ActiveSideStakeEntries() { std::vector sidestakes; + double allocation_sum = 0.0; // For right now refresh sidestakes from config file. This is about the same overhead as the original // function in the miner. Perhaps replace with a signal to only refresh when r-w config file is // actually changed. LoadLocalSideStakesFromConfig(); + // The loops below prevent sidestakes from being added that cause a total allocation above 1.0 (100%). + + // Do mandatory sidestakes first. for (const auto& entry : m_sidestake_entries) { - if (entry.second->m_status == SideStakeStatus::ACTIVE || entry.second->m_status == SideStakeStatus::MANDATORY) { + if (entry.second->m_status == SideStakeStatus::MANDATORY && allocation_sum + entry.second->m_allocation <= 1.0) { sidestakes.push_back(entry.second); + allocation_sum += entry.second->m_allocation; + } + } + + // Followed by local active sidestakes + for (const auto& entry : m_sidestake_entries) + { + if (entry.second->m_status == SideStakeStatus::ACTIVE && allocation_sum + entry.second->m_allocation <= 1.0) { + sidestakes.push_back(entry.second); + allocation_sum += entry.second->m_allocation; } } @@ -519,9 +533,17 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig() } } + // First, determine allocation already taken by mandatory sidestakes, because they must be allocated first. + for (const auto& entry : SideStakeEntries()) { + if (entry.second->m_status == SideStakeStatus::MANDATORY) { + dSumAllocation += entry.second->m_allocation; + } + } + for (const auto& entry : raw_vSideStakeAlloc) { std::string sAddress; + double dAllocation = 0.0; sAddress = entry.first; diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h index f0fc8b4687..5018a438e6 100644 --- a/src/gridcoin/sidestake.h +++ b/src/gridcoin/sidestake.h @@ -11,6 +11,7 @@ #include "gridcoin/contract/registry_db.h" #include "gridcoin/support/enumbytes.h" #include "serialize.h" +#include "logging.h" namespace GRC { @@ -29,6 +30,7 @@ class CBitcoinAddressForStorage : public CBitcoinAddress // 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(nVersion); READWRITE(vchData); } }; @@ -274,11 +276,35 @@ class SideStakePayload : public IContractPayload //! bool WellFormed(const ContractAction action) const override { - if (m_version <= 0 || m_version > CURRENT_VERSION) { + bool valid = !(m_version <= 0 || m_version > CURRENT_VERSION); + + if (!valid) { + LogPrint(BCLog::LogFlags::CONTRACT, "WARN: %s: Payload is not well formed. " + "m_version = %u, CURRENT_VERSION = %u", + __func__, + m_version, + CURRENT_VERSION); + + return false; + } + + valid = m_entry.WellFormed(); + + if (!valid) { + LogPrint(BCLog::LogFlags::CONTRACT, "WARN: %s: Sidestake entry is not well-formed. " + "m_entry.WellFormed = %u, m_entry.m_key = %s, m_entry.m_allocation = %f, " + "m_entry.StatusToString() = %s", + __func__, + valid, + m_entry.m_key.ToString(), + m_entry.m_allocation, + m_entry.StatusToString() + ); + return false; } - return m_entry.WellFormed(); + return valid; } //! @@ -372,6 +398,8 @@ class SideStakeRegistry : public IContractHandler //! \brief Get the collection of active sidestake entries. This is presented as a vector of //! smart pointers to the relevant sidestake entries in the database. The entries included have //! the status of active (for local sidestakes) and/or mandatory (for contract sidestakes). + //! Mandatory sidestakes come before local ones, and the method ensures that the sidestakes + //! returned do not total an allocation greater than 1.0. //! //! \return A vector of smart pointers to sidestake entries. //! diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 538ce8c954..025d6584f3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -7,6 +7,7 @@ #include "blockchain.h" #include "gridcoin/protocol.h" #include "gridcoin/scraper/scraper_registry.h" +#include "gridcoin/sidestake.h" #include "node/blockstorage.h" #include #include "gridcoin/mrc.h" @@ -2304,7 +2305,8 @@ UniValue addkey(const UniValue& params, bool fHelp) if (!(type == GRC::ContractType::PROJECT || type == GRC::ContractType::SCRAPER - || type == GRC::ContractType::PROTOCOL)) { + || type == GRC::ContractType::PROTOCOL + || type == GRC::ContractType::SIDESTAKE)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid contract type for addkey."); } @@ -2433,6 +2435,34 @@ UniValue addkey(const UniValue& params, bool fHelp) params[2].get_str(), // key params[3].get_str()); // value break; + case GRC::ContractType::SIDESTAKE: + { + GRC::CBitcoinAddressForStorage sidestake_address; + if (!sidestake_address.SetString(params[2].get_str())) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Address specified for the sidestake is invalid."); + } + + double allocation = 0.0; + if (!ParseDouble(params[3].get_str(), &allocation)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid allocation specified."); + } + + allocation /= 100.0; + + if (allocation > 1.0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Allocation specified is greater than 100.0%."); + } + + contract = GRC::MakeContract( + contract_version, // Contract version number + action, // Contract action + uint32_t {1}, // Contract payload version number + sidestake_address, // Sidestake address + allocation, // Sidestake allocation + GRC::SideStakeStatus::MANDATORY // sidestake status + ); + break; + } case GRC::ContractType::BEACON: [[fallthrough]]; case GRC::ContractType::CLAIM: diff --git a/src/validation.cpp b/src/validation.cpp index ee78d93bf5..5500005648 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1934,6 +1934,7 @@ bool AcceptBlock(CBlock& block, bool generated_by_me) EXCLUSIVE_LOCKS_REQUIRED(c || (IsV10Enabled(nHeight) && block.nVersion < 10) || (IsV11Enabled(nHeight) && block.nVersion < 11) || (IsV12Enabled(nHeight) && block.nVersion < 12) + || (IsV13Enabled(nHeight) && block.nVersion < 13) ) { return block.DoS(20, error("%s: reject too old nVersion = %d", __func__, block.nVersion)); } else if ((!IsProtocolV2(nHeight) && block.nVersion >= 7) @@ -1942,6 +1943,7 @@ bool AcceptBlock(CBlock& block, bool generated_by_me) EXCLUSIVE_LOCKS_REQUIRED(c || (!IsV10Enabled(nHeight) && block.nVersion >= 10) || (!IsV11Enabled(nHeight) && block.nVersion >= 11) || (!IsV12Enabled(nHeight) && block.nVersion >= 12) + || (!IsV13Enabled(nHeight) && block.nVersion >= 13) ) { return block.DoS(100, error("%s: reject too new nVersion = %d", __func__, block.nVersion)); }