Skip to content

Commit

Permalink
Enhance rpc addpoll to properly handle (required) additional fields
Browse files Browse the repository at this point in the history
This also makes some modifications to PollBuilder.
  • Loading branch information
jamescowens committed Jun 13, 2022
1 parent ed6ad37 commit 406638e
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 16 deletions.
17 changes: 17 additions & 0 deletions src/gridcoin/voting/builders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1102,6 +1102,13 @@ PollBuilder PollBuilder::SetAdditionalFields(std::vector<Poll::AdditionalField>
return AddAdditionalFields(std::move(fields));
}

PollBuilder PollBuilder::SetAdditionalFields(Poll::AdditionalFieldList fields)
{
m_poll->m_additional_fields = Poll::AdditionalFieldList();

return AddAdditionalFields(std::move(fields));
}

PollBuilder PollBuilder::AddAdditionalFields(std::vector<Poll::AdditionalField> fields)
{
for (auto& field : fields) {
Expand All @@ -1111,11 +1118,21 @@ PollBuilder PollBuilder::AddAdditionalFields(std::vector<Poll::AdditionalField>
return std::move(*this);
}

PollBuilder PollBuilder::AddAdditionalFields(Poll::AdditionalFieldList fields)
{
for (auto& field : fields) {
*this = AddAdditionalField(std::move(field));
}

return std::move(*this);
}

PollBuilder PollBuilder::AddAdditionalField(Poll::AdditionalField field)
{
// Make sure there are no leading and trailing spaces.
field.m_name = TrimString(field.m_name);
field.m_value = TrimString(field.m_value);
field.m_required = field.m_required;

if (!field.WellFormed()) {
throw VotingError(_("The field is not well-formed."));
Expand Down
26 changes: 23 additions & 3 deletions src/gridcoin/voting/builders.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,27 +169,47 @@ class PollBuilder
//!
//! \brief Set the set of additional fields for the poll.
//!
//! \param labels A set of AdditionalFields to set.
//! \param field A set of AdditionalFields to set.
//!
//! \throws VotingError If any of the fields are malformed, or if the set of fields
//! contains a duplicate label.
//!
PollBuilder SetAdditionalFields(std::vector<Poll::AdditionalField> fields);

//!
//! \brief Set the set of additional fields for the poll.
//!
//! \param fields A set of AdditionalFields to set.
//!
//! \throws VotingError If any of the fields are malformed, or if the set of fields
//! contains a duplicate label.
//!
PollBuilder SetAdditionalFields(Poll::AdditionalFieldList fields);

//!
//! \brief Add a set of additional fields for the poll.
//!
//! \param labels A set of AdditionalFields to add.
//! \param fields A set of AdditionalFields to add.
//!
//! \throws VotingError If any of the fields are malformed, or if the set of fields
//! contains a duplicate label.
//!
PollBuilder AddAdditionalFields(std::vector<Poll::AdditionalField> fields);

//!
//! \brief Add a set of additional fields for the poll.
//!
//! \param fields A set of AdditionalFields to add.
//!
//! \throws VotingError If any of the fields are malformed, or if the set of fields
//! contains a duplicate label.
//!
PollBuilder AddAdditionalFields(Poll::AdditionalFieldList fields);

//!
//! \brief Add an additional field for the poll.
//!
//! \param AdditionalField The additional field name-value to add.
//! \param field The additional field name-value to add.
//!
//! \throws VotingError If the field is malformed, or if the set of fields
//! contains a duplicate label.
Expand Down
129 changes: 116 additions & 13 deletions src/rpc/voting.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ UniValue PollChoicesToJson(const Poll::ChoiceList& choices)
return json;
}

UniValue PollAdditionalFieldsToJson(const Poll::AdditionalFieldList fields)
{
UniValue json(UniValue::VARR);

for (size_t i = 0; i < fields.size(); ++i) {
UniValue field(UniValue::VOBJ);
UniValue field_value(UniValue::VOBJ);

field_value.pushKV("value", fields.At(i)->m_value);
field_value.pushKV("required", fields.At(i)->m_required);

field.pushKV(fields.At(i)->m_name, field_value);

json.push_back(field);
}

return json;
}

UniValue PollToJson(const Poll& poll, const uint256 txid)
{
UniValue json(UniValue::VOBJ);
Expand All @@ -59,6 +78,7 @@ UniValue PollToJson(const Poll& poll, const uint256 txid)
json.pushKV("id", txid.ToString());
json.pushKV("question", poll.m_question);
json.pushKV("url", poll.m_url);
json.pushKV("additional_fields", PollAdditionalFieldsToJson(poll.AdditionalFields()));
json.pushKV("poll_type", poll.PollTypeToString());
json.pushKV("poll_type_id", (int)poll.m_type.Raw());
json.pushKV("weight_type", poll.WeightTypeToString());
Expand Down Expand Up @@ -318,29 +338,31 @@ UniValue addpoll(const UniValue& params, bool fHelp)
types_ss << ToLower(Poll::PollTypeToString(type, false));
}

if (fHelp || params.size() != 8) {
if (params.size() == 0) {
std::string e = strprintf(
"addpoll <type> <title> <days> <question> <answer1;answer2...> <weighttype> <responsetype> <url>\n"
"addpoll <type> <title> <days> <question> <answer1;answer2...> <weighttype> <responsetype> <url> "
"<required_field_name1=value1;required_field_name2=value2...>\n"
"\n"
"<type> ---------> Type of poll. Valid types are: %s.\n"
"<title> --------> Title for the poll\n"
"<days> ---------> Number of days that the poll will run\n"
"<question> -----> Prompt that voters shall answer\n"
"<answers> ------> Answers for voters to choose from. Separate answers with semicolons (;)\n"
"<weighttype> ---> Weighing method for the poll: 1 = Balance, 2 = Magnitude + Balance\n"
"<responsetype> -> 1 = yes/no/abstain, 2 = single-choice, 3 = multiple-choice\n"
"<url> ----------> Discussion web page URL for the poll\n"
"<type> -----------> Type of poll. Valid types are: %s.\n"
"<title> ----------> Title for the poll\n"
"<days> -----------> Number of days that the poll will run\n"
"<question> -------> Prompt that voters shall answer\n"
"<answers> --------> Answers for voters to choose from. Separate answers with semicolons (;)\n"
"<weighttype> -----> Weighing method for the poll: 1 = Balance, 2 = Magnitude + Balance\n"
"<responsetype> ---> 1 = yes/no/abstain, 2 = single-choice, 3 = multiple-choice\n"
"<url> ------------> Discussion web page URL for the poll\n"
"<required fields>-> Required additional field(s) if any (see below)\n"
"\n"
"Add a poll to the network.\n"
"Requires 100K GRC balance. Costs 50 GRC.\n"
"Provide an empty string for <answers> when choosing \"yes/no/abstain\" for <responsetype>.\n",
"Provide an empty string for <answers> when choosing \"yes/no/abstain\" for <responsetype>.\n"
"Certain poll types may require additional fields. You can see these with addpoll <type> \n"
"with no other parameters.",
types_ss.str());

throw std::runtime_error(e);
}

EnsureWalletIsUnlocked();

std::string type_string = ToLower(params[0].get_str());

PollType poll_type;
Expand All @@ -361,6 +383,55 @@ UniValue addpoll(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_INVALID_PARAMETER, e);
}

const std::vector<std::string>& required_fields = Poll::POLL_TYPE_RULES[(int) poll_type].m_required_fields;
std::stringstream required_fields_ss;

for (const auto& required_field : required_fields) {
if (required_fields_ss.str() != std::string{}) {
required_fields_ss << ", ";
}

required_fields_ss << required_field;
}

if (params.size() == 1) {
std::string e = strprintf(
"For addpoll %s, the required fields are the following: %s.\n",
ToLower(params[0].get_str()),
required_fields.empty() ? "none" : required_fields_ss.str());

throw std::runtime_error(e);
}

size_t required_number_of_params = required_fields.empty() ? 8 : 9;

if (fHelp || params.size() < required_number_of_params) {
std::string e = strprintf(
"addpoll <type> <title> <days> <question> <answer1;answer2...> <weighttype> <responsetype> <url> "
"<required_field_name1=value1;required_field_name2=value2...>\n"
"\n"
"<type> -----------> Type of poll. Valid types are: %s.\n"
"<title> ----------> Title for the poll\n"
"<days> -----------> Number of days that the poll will run\n"
"<question> -------> Prompt that voters shall answer\n"
"<answers> --------> Answers for voters to choose from. Separate answers with semicolons (;)\n"
"<weighttype> -----> Weighing method for the poll: 1 = Balance, 2 = Magnitude + Balance\n"
"<responsetype> ---> 1 = yes/no/abstain, 2 = single-choice, 3 = multiple-choice\n"
"<url> ------------> Discussion web page URL for the poll\n"
"<required fields>-> Required additional field(s) if any (see below)\n"
"\n"
"Add a poll to the network.\n"
"Requires 100K GRC balance. Costs 50 GRC.\n"
"Provide an empty string for <answers> when choosing \"yes/no/abstain\" for <responsetype>.\n"
"Certain poll types may require additional fields. You can see these with addpoll <type> \n"
"with no other parameters.",
types_ss.str());

throw std::runtime_error(e);
}

EnsureWalletIsUnlocked();

PollBuilder builder = PollBuilder()
.SetPayloadVersion(payload_version)
.SetType(poll_type)
Expand All @@ -375,6 +446,38 @@ UniValue addpoll(const UniValue& params, bool fHelp)
builder = builder.SetChoices(split(params[4].get_str(), ";"));
}

if (params.size() == 9 && !params[8].isNull() && !params[8].get_str().empty()) {
std::vector<std::string> name_value_pairs = split(params[8].get_str(), ";");
Poll::AdditionalFieldList fields;

for (const auto& name_value_pair : name_value_pairs) {
std::vector v_field = split(name_value_pair, "=");
std::string field_name = TrimString(v_field[0]);
std::string field_value = TrimString(v_field[1]);
bool required = true;

if (v_field.size() != 2) {
throw std::runtime_error("Required fields parameter for poll is malformed.");
}

if (std::find(required_fields.begin(), required_fields.end(), field_name) == required_fields.end()) {
required = false;
}

Poll::AdditionalField field(field_name, field_value, required);

fields.Add(field);
}

// TODO: Extend Wellformed to do a duplicate check on the field name? This is done in the builder anyway. This
// makes sure that at least the required fields have been provided and that they are well formed.
if (!fields.WellFormed(poll_type)) {
throw std::runtime_error("Required field list is malformed.");
}

builder = builder.AddAdditionalFields(fields);
}

std::pair<CWalletTx, std::string> result_pair;

{
Expand Down

0 comments on commit 406638e

Please sign in to comment.