diff --git a/src/gridcoin/project.cpp b/src/gridcoin/project.cpp index cf5c805e18..fb90479818 100644 --- a/src/gridcoin/project.cpp +++ b/src/gridcoin/project.cpp @@ -97,6 +97,8 @@ std::string ProjectEntry::StatusToString(const ProjectEntryStatus& status, const switch(status) { case ProjectEntryStatus::UNKNOWN: return _("Unknown"); case ProjectEntryStatus::DELETED: return _("Deleted"); + case ProjectEntryStatus::MAN_GREYLISTED: return _("Manually Greylisted"); + case ProjectEntryStatus::AUTO_GREYLISTED: return _("Automatically Greylisted"); case ProjectEntryStatus::ACTIVE: return _("Active"); case ProjectEntryStatus::OUT_OF_BOUND: break; } @@ -107,6 +109,8 @@ std::string ProjectEntry::StatusToString(const ProjectEntryStatus& status, const switch(status) { case ProjectEntryStatus::UNKNOWN: return "Unknown"; case ProjectEntryStatus::DELETED: return "Deleted"; + case ProjectEntryStatus::MAN_GREYLISTED: return "Manually Greylisted"; + case ProjectEntryStatus::AUTO_GREYLISTED: return "Automatically Greylisted"; case ProjectEntryStatus::ACTIVE: return "Active"; case ProjectEntryStatus::OUT_OF_BOUND: break; } @@ -196,6 +200,14 @@ Project::Project(uint32_t version, std::string name, std::string url, bool gdpr_ { } +Project::Project(uint32_t version, std::string name, std::string url, bool gdpr_controls, bool manual_greylist) + : ProjectEntry(version, name, url, gdpr_controls, ProjectEntryStatus::UNKNOWN, int64_t {0}) +{ + if (manual_greylist) { + m_status = ProjectEntryStatus::MAN_GREYLISTED; + } +} + Project::Project(uint32_t version, std::string name, std::string url, bool gdpr_controls, int64_t timestamp) : ProjectEntry(version, name, url, gdpr_controls, ProjectEntryStatus::UNKNOWN, timestamp) { @@ -215,8 +227,9 @@ Project::Project(ProjectEntry entry) // Class: WhitelistSnapshot // ----------------------------------------------------------------------------- -WhitelistSnapshot::WhitelistSnapshot(ProjectListPtr projects) +WhitelistSnapshot::WhitelistSnapshot(ProjectListPtr projects, const ProjectEntry::ProjectFilterFlag& filter_used) : m_projects(std::move(projects)) + , m_filter(filter_used) { } @@ -258,6 +271,11 @@ bool WhitelistSnapshot::Contains(const std::string& name) const return false; } +ProjectEntry::ProjectFilterFlag WhitelistSnapshot::FilterUsed() const +{ + return m_filter; +} + WhitelistSnapshot WhitelistSnapshot::Sorted() const { ProjectList sorted(m_projects->begin(), m_projects->end()); @@ -275,26 +293,70 @@ WhitelistSnapshot WhitelistSnapshot::Sorted() const std::sort(sorted.begin(), sorted.end(), ascending_by_name); - return WhitelistSnapshot(std::make_shared(sorted)); + return WhitelistSnapshot(std::make_shared(sorted), m_filter); } // ----------------------------------------------------------------------------- // Class: Whitelist (Registry) // ----------------------------------------------------------------------------- -WhitelistSnapshot Whitelist::Snapshot() const +WhitelistSnapshot Whitelist::Snapshot(const ProjectEntry::ProjectFilterFlag& filter) const { LOCK(cs_lock); ProjectList projects; for (const auto& iter : m_project_entries) { - if (iter.second->m_status == ProjectEntryStatus::ACTIVE) { + switch (filter) { + case ProjectEntry::ProjectFilterFlag::ACTIVE: + if (iter.second->m_status == ProjectEntryStatus::ACTIVE) { + projects.push_back(*iter.second); + } + break; + case ProjectEntry::ProjectFilterFlag::MAN_GREYLISTED: + if (iter.second->m_status == ProjectEntryStatus::MAN_GREYLISTED) { + projects.push_back(*iter.second); + } + break; + case ProjectEntry::ProjectFilterFlag::AUTO_GREYLISTED: + if (iter.second->m_status == ProjectEntryStatus::AUTO_GREYLISTED) { + projects.push_back(*iter.second); + } + break; + case ProjectEntry::ProjectFilterFlag::GREYLISTED: + if (iter.second->m_status == ProjectEntryStatus::MAN_GREYLISTED + || iter.second->m_status == ProjectEntryStatus::AUTO_GREYLISTED) { + projects.push_back(*iter.second); + } + break; + case ProjectEntry::ProjectFilterFlag::DELETED: + if (iter.second->m_status == ProjectEntryStatus::DELETED) { + projects.push_back(*iter.second); + } + break; + case ProjectEntry::ProjectFilterFlag::NOT_ACTIVE: + if (iter.second->m_status == ProjectEntryStatus::MAN_GREYLISTED + || iter.second->m_status == ProjectEntryStatus::AUTO_GREYLISTED + || iter.second->m_status == ProjectEntryStatus::DELETED) { + projects.push_back(*iter.second); + } + break; + case ProjectEntry::ProjectFilterFlag::ALL_BUT_DELETED: + if (iter.second->m_status == ProjectEntryStatus::ACTIVE + || iter.second->m_status == ProjectEntryStatus::MAN_GREYLISTED + || iter.second->m_status == ProjectEntryStatus::AUTO_GREYLISTED) { + projects.push_back(*iter.second); + } + break; + case ProjectEntry::ProjectFilterFlag::ALL: projects.push_back(*iter.second); + break; + case ProjectEntry::ProjectFilterFlag::NONE: + break; } } - return WhitelistSnapshot(std::make_shared(projects)); + return WhitelistSnapshot(std::make_shared(projects), filter); } void Whitelist::Reset() @@ -325,7 +387,12 @@ void Whitelist::AddDelete(const ContractContext& ctx) // If the contract status is ADD, then ProjectEntryStatus will be ACTIVE. If contract status // is REMOVE then ProjectEntryStatus will be DELETED. if (ctx->m_action == ContractAction::ADD) { + // Normal add/delete contracts are constructed with "unknown" status. The status is recorded on the local client + // based on the add/delete action. The manual greylist status is specified in the contract as constructed, and will carry + // through here for the contract add with the greylist flag set. + if (payload.m_status == ProjectEntryStatus::UNKNOWN) { payload.m_status = ProjectEntryStatus::ACTIVE; + } } else if (ctx->m_action == ContractAction::REMOVE) { payload.m_status = ProjectEntryStatus::DELETED; } @@ -545,4 +612,3 @@ template<> const std::string Whitelist::ProjectEntryDB::KeyType() { return std::string("project"); } - diff --git a/src/gridcoin/project.h b/src/gridcoin/project.h index 9e4c55ae87..0367e93a2e 100644 --- a/src/gridcoin/project.h +++ b/src/gridcoin/project.h @@ -31,6 +31,8 @@ namespace GRC //! //! ACTIVE corresponds to an active entry. //! +//! GREYLISTED means that the project temporarily does not meet the whitelist qualification criteria. +//! //! OUT_OF_BOUND must go at the end and be retained for the EnumBytes wrapper. //! enum class ProjectEntryStatus @@ -38,6 +40,8 @@ enum class ProjectEntryStatus UNKNOWN, DELETED, ACTIVE, + MAN_GREYLISTED, + AUTO_GREYLISTED, OUT_OF_BOUND }; @@ -49,6 +53,23 @@ class ProjectEntry //! using Status = EnumByte; + //! + //! \brief Project filter flag enumeration. + //! + //! This controls what project entries by status are in the project whitelist snapshot. + //! + enum ProjectFilterFlag : uint8_t { + NONE = 0b0000, + DELETED = 0b0001, + MAN_GREYLISTED = 0b0010, + AUTO_GREYLISTED = 0b0100, + GREYLISTED = MAN_GREYLISTED | AUTO_GREYLISTED, + ACTIVE = 0b1000, + NOT_ACTIVE = 0b0111, + ALL_BUT_DELETED = 0b1110, + ALL = 0b1111 + }; + //! //! \brief Version number of the current format for a serialized project. //! @@ -284,6 +305,17 @@ class Project : public IContractPayload, public ProjectEntry //! Project(uint32_t version, std::string name, std::string url, bool gdpr_controls); + //! + //! \brief Initialize a \c Project using data from the contract. + //! + //! \param version Project payload version. + //! \param name Project name from contract message key. + //! \param url Project URL from contract message value. + //! \param gdpr_controls Boolean to indicate gdpr stats export controls enforced + //! \param manual_greylist Boolean to force manual greylisting of project + //! + Project(uint32_t version, std::string name, std::string url, bool gdpr_controls, bool manual_greylist); + //! //! \brief Initialize a \c Project using data from the contract. //! @@ -307,7 +339,7 @@ class Project : public IContractPayload, public ProjectEntry Project(std::string name, std::string url, int64_t timestamp, uint32_t version, bool gdpr_controls); //! - //! \brief Initialize a \c Project payloard from a provided project entry + //! \brief Initialize a \c Project payload from a provided project entry //! \param version. Project Payload version. //! \param entry. Project entry to place in the payload. //! @@ -426,8 +458,10 @@ class WhitelistSnapshot //! \brief Initialize a snapshot for the provided project list from the project entry map. //! //! \param projects A copy of the smart pointer to the project list. + //! \param filter_used The project status filter used for the project list. //! - WhitelistSnapshot(ProjectListPtr projects); + WhitelistSnapshot(ProjectListPtr projects, + const ProjectEntry::ProjectFilterFlag& filter_used = ProjectEntry::ProjectFilterFlag::ACTIVE); //! //! \brief Returns an iterator to the beginning. @@ -463,6 +497,13 @@ class WhitelistSnapshot //! bool Contains(const std::string& name) const; + //! + //! \brief Returns the project status filter flag used to populate the snapshot. + //! + //! \return ProjectFilterFlag used for whitelist snapshot. + //! + ProjectEntry::ProjectFilterFlag FilterUsed() const; + //! //! \brief Create a snapshot copy sorted alphabetically by project name. //! @@ -472,6 +513,7 @@ class WhitelistSnapshot private: const ProjectListPtr m_projects; //!< The vector of whitelisted projects. + const ProjectEntry::ProjectFilterFlag m_filter; //!< The filter used to populate the readonly list. }; //! @@ -513,9 +555,10 @@ class Whitelist : public IContractHandler typedef std::map HistoricalProjectEntryMap; //! - //! \brief Get a read-only view of the projects in the whitelist. + //! \brief Get a read-only view of the projects in the whitelist. The default filter is ACTIVE, which + //! provides the original ACTIVE project only view. //! - WhitelistSnapshot Snapshot() const; + WhitelistSnapshot Snapshot(const ProjectEntry::ProjectFilterFlag& filter = ProjectEntry::ProjectFilterFlag::ACTIVE) const; //! //! \brief Destroy the contract handler state to prepare for historical diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 32ee83380c..687327ef1f 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -2194,23 +2194,35 @@ UniValue superblocks(const UniValue& params, bool fHelp) //! //! To whitelist a project: //! -//! addkey add project projectname url +//! addkey add project projectname url (< v2) //! -//! To de-whitelist a project: +//! addkey add project projectname url gdpr_controls (v2) +//! +//! addkey add project projectname url gdpr_controls manual_greylist (v3) //! -//! addkey delete project projectname +//! To de-whitelist a project: //! -//! or +//! addkey delete project projectname 1 (<= v2) //! -//! addkey delete project projectname 1 +//! addkey delete project projectname (>= v3) //! //! Key examples: //! +//! To add: +//! //! v1: addkey add project milkyway@home http://milkyway.cs.rpi.edu/milkyway/@ //! v2: addkey add project milkyway@home http://milkyway.cs.rpi.edu/milkyway/@ true +//! +//! To manually greylist: +//! +//! v3: addkey add project milkyway@home http://milkyway.cs.rpi.edu/milkyway/@ true true +//! +//! To delete: +//! //! v1 or v2: addkey delete project milkyway@home //! v1 or v2: addkey delete project milkyway@home 1 (last parameter is a dummy) //! v2: addkey delete project milkyway@home 1 false (last two parameters are dummies) +//! v3: addkey delete project milkyway@home (the dummy parameters are no longer needed for v3 deletes) //! //! GRC will only memorize the *last* value it finds for a key in the highest //! block. @@ -2254,7 +2266,8 @@ UniValue addkey(const UniValue& params, bool fHelp) && action == GRC::ContractAction::ADD && type == GRC::ContractType::PROJECT) { required_param_count = 5; - param_count_max = 5; + + block_v13_enabled ? param_count_max = 6 : param_count_max = 5; } if ((type == GRC::ContractType::PROJECT || type == GRC::ContractType::SCRAPER) @@ -2332,6 +2345,7 @@ UniValue addkey(const UniValue& params, bool fHelp) { if (action == GRC::ContractAction::ADD) { bool gdpr_export_control = false; + bool manually_greylist = false; if (block_v13_enabled) { // We must do our own conversion to boolean here, because the 5th parameter can either be @@ -2344,13 +2358,20 @@ UniValue addkey(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_INVALID_PARAMETER, "GDPR export parameter invalid. Must be true or false."); } + if (params.size() == 6) { + if (ToLower(params[4].get_str()) == "true") { + manually_greylist = true; + } + } + contract = GRC::MakeContract( contract_version, action, uint32_t{3}, // Contract payload version number, 3 params[2].get_str(), // Name params[3].get_str(), // URL - gdpr_export_control); // GDPR stats export protection enforced boolean + gdpr_export_control, // GDPR stats export protection enforced boolean + manually_greylist); // manual greylist flag } else if (project_v2_enabled) { // We must do our own conversion to boolean here, because the 5th parameter can either be @@ -2386,7 +2407,8 @@ UniValue addkey(const UniValue& params, bool fHelp) uint32_t{3}, // Contract payload version number, 3 params[2].get_str(), // Name std::string{}, // URL ignored - false); // GDPR status irrelevant + false, // GDPR status irrelevant + false); // manual greylisting irrelevant } else if (project_v2_enabled) { contract = GRC::MakeContract(