2018-09-28 09:56:17 +02:00
|
|
|
// Copyright (c) 2014-2018 The Dash Core developers
|
2017-06-26 15:56:29 +02:00
|
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
#include "governance-validators.h"
|
|
|
|
|
|
|
|
#include "base58.h"
|
2018-03-21 12:09:13 +01:00
|
|
|
#include "timedata.h"
|
2018-02-27 14:39:48 +01:00
|
|
|
#include "tinyformat.h"
|
2017-06-26 15:56:29 +02:00
|
|
|
#include "utilstrencodings.h"
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
const size_t MAX_DATA_SIZE = 512;
|
|
|
|
const size_t MAX_NAME_SIZE = 40;
|
2017-06-26 15:56:29 +02:00
|
|
|
|
2018-02-27 14:39:48 +01:00
|
|
|
CProposalValidator::CProposalValidator(const std::string& strHexData) :
|
|
|
|
objJSON(UniValue::VOBJ),
|
|
|
|
fJSONValid(false),
|
|
|
|
strErrorMessages()
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!strHexData.empty()) {
|
2018-02-27 14:39:48 +01:00
|
|
|
ParseStrHexData(strHexData);
|
|
|
|
}
|
2017-06-26 15:56:29 +02:00
|
|
|
}
|
|
|
|
|
2018-02-27 14:39:48 +01:00
|
|
|
void CProposalValidator::ParseStrHexData(const std::string& strHexData)
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
2018-02-27 14:39:48 +01:00
|
|
|
std::vector<unsigned char> v = ParseHex(strHexData);
|
2018-04-05 14:34:26 +02:00
|
|
|
if (v.size() > MAX_DATA_SIZE) {
|
|
|
|
strErrorMessages = strprintf("data exceeds %lu characters;", MAX_DATA_SIZE);
|
|
|
|
return;
|
|
|
|
}
|
2018-02-27 14:39:48 +01:00
|
|
|
ParseJSONData(std::string(v.begin(), v.end()));
|
2017-06-26 15:56:29 +02:00
|
|
|
}
|
|
|
|
|
2018-03-21 12:09:13 +01:00
|
|
|
bool CProposalValidator::Validate(bool fCheckExpiration)
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!fJSONValid) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "JSON parsing error;";
|
|
|
|
return false;
|
|
|
|
}
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!ValidateName()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Invalid name;";
|
|
|
|
return false;
|
|
|
|
}
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!ValidateStartEndEpoch(fCheckExpiration)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Invalid start:end range;";
|
|
|
|
return false;
|
|
|
|
}
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!ValidatePaymentAmount()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Invalid payment amount;";
|
|
|
|
return false;
|
|
|
|
}
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!ValidatePaymentAddress()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Invalid payment address;";
|
|
|
|
return false;
|
|
|
|
}
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!ValidateURL()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Invalid URL;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CProposalValidator::ValidateName()
|
|
|
|
{
|
|
|
|
std::string strName;
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!GetDataValue("name", strName)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "name field not found;";
|
2017-10-25 16:57:39 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (strName.size() > MAX_NAME_SIZE) {
|
2018-02-27 14:39:48 +01:00
|
|
|
strErrorMessages += strprintf("name exceeds %lu characters;", MAX_NAME_SIZE);
|
2017-06-26 15:56:29 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-07-21 11:36:34 +02:00
|
|
|
static const std::string strAllowedChars = "-_abcdefghijklmnopqrstuvwxyz0123456789";
|
2017-06-26 15:56:29 +02:00
|
|
|
|
|
|
|
std::transform(strName.begin(), strName.end(), strName.begin(), ::tolower);
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (strName.find_first_not_of(strAllowedChars) != std::string::npos) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "name contains invalid characters;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-21 12:09:13 +01:00
|
|
|
bool CProposalValidator::ValidateStartEndEpoch(bool fCheckExpiration)
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
|
|
|
int64_t nStartEpoch = 0;
|
|
|
|
int64_t nEndEpoch = 0;
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!GetDataValue("start_epoch", nStartEpoch)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "start_epoch field not found;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!GetDataValue("end_epoch", nEndEpoch)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "end_epoch field not found;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (nEndEpoch <= nStartEpoch) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "end_epoch <= start_epoch;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (fCheckExpiration && nEndEpoch <= GetAdjustedTime()) {
|
2018-03-21 12:09:13 +01:00
|
|
|
strErrorMessages += "expired;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-06-26 15:56:29 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CProposalValidator::ValidatePaymentAmount()
|
|
|
|
{
|
|
|
|
double dValue = 0.0;
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!GetDataValue("payment_amount", dValue)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "payment_amount field not found;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (dValue <= 0.0) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "payment_amount is negative;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Should check for an amount which exceeds the budget but this is
|
|
|
|
// currently difficult because start and end epochs are defined in terms of
|
|
|
|
// clock time instead of block height.
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CProposalValidator::ValidatePaymentAddress()
|
|
|
|
{
|
|
|
|
std::string strPaymentAddress;
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!GetDataValue("payment_address", strPaymentAddress)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "payment_address field not found;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (std::find_if(strPaymentAddress.begin(), strPaymentAddress.end(), ::isspace) != strPaymentAddress.end()) {
|
2018-02-27 14:39:48 +01:00
|
|
|
strErrorMessages += "payment_address can't have whitespaces;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-06-26 15:56:29 +02:00
|
|
|
CBitcoinAddress address(strPaymentAddress);
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!address.IsValid()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "payment_address is invalid;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (address.IsScript()) {
|
2018-02-12 13:46:27 +01:00
|
|
|
strErrorMessages += "script addresses are not supported;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-06-26 15:56:29 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CProposalValidator::ValidateURL()
|
|
|
|
{
|
|
|
|
std::string strURL;
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!GetDataValue("url", strURL)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "url field not found;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (std::find_if(strURL.begin(), strURL.end(), ::isspace) != strURL.end()) {
|
2018-02-27 14:39:48 +01:00
|
|
|
strErrorMessages += "url can't have whitespaces;";
|
|
|
|
return false;
|
|
|
|
}
|
2017-06-26 15:56:29 +02:00
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (strURL.size() < 4U) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "url too short;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (!CheckURL(strURL)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "url invalid;";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-02-27 14:39:48 +01:00
|
|
|
void CProposalValidator::ParseJSONData(const std::string& strJSONData)
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
|
|
|
fJSONValid = false;
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (strJSONData.empty()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
UniValue obj(UniValue::VOBJ);
|
2018-02-28 00:20:16 +01:00
|
|
|
|
2018-02-27 14:39:48 +01:00
|
|
|
obj.read(strJSONData);
|
2018-02-28 00:20:16 +01:00
|
|
|
|
|
|
|
if (obj.isObject()) {
|
|
|
|
objJSON = obj;
|
|
|
|
} else {
|
|
|
|
std::vector<UniValue> arr1 = obj.getValues();
|
|
|
|
std::vector<UniValue> arr2 = arr1.at(0).getValues();
|
|
|
|
objJSON = arr2.at(1);
|
|
|
|
}
|
|
|
|
|
2017-06-26 15:56:29 +02:00
|
|
|
fJSONValid = true;
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (std::exception& e) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += std::string(e.what()) + std::string(";");
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (...) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Unknown exception;";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-27 14:39:48 +01:00
|
|
|
bool CProposalValidator::GetDataValue(const std::string& strKey, std::string& strValueRet)
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
|
|
|
bool fOK = false;
|
2018-09-28 09:56:17 +02:00
|
|
|
try {
|
2018-02-27 14:39:48 +01:00
|
|
|
strValueRet = objJSON[strKey].get_str();
|
2017-06-26 15:56:29 +02:00
|
|
|
fOK = true;
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (std::exception& e) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += std::string(e.what()) + std::string(";");
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (...) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Unknown exception;";
|
|
|
|
}
|
|
|
|
return fOK;
|
|
|
|
}
|
|
|
|
|
2018-02-27 14:39:48 +01:00
|
|
|
bool CProposalValidator::GetDataValue(const std::string& strKey, int64_t& nValueRet)
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
|
|
|
bool fOK = false;
|
2018-09-28 09:56:17 +02:00
|
|
|
try {
|
2017-06-26 15:56:29 +02:00
|
|
|
const UniValue uValue = objJSON[strKey];
|
2018-09-28 09:56:17 +02:00
|
|
|
switch (uValue.getType()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
case UniValue::VNUM:
|
2018-02-27 14:39:48 +01:00
|
|
|
nValueRet = uValue.get_int64();
|
2017-06-26 15:56:29 +02:00
|
|
|
fOK = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (std::exception& e) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += std::string(e.what()) + std::string(";");
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (...) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Unknown exception;";
|
|
|
|
}
|
|
|
|
return fOK;
|
|
|
|
}
|
|
|
|
|
2018-02-27 14:39:48 +01:00
|
|
|
bool CProposalValidator::GetDataValue(const std::string& strKey, double& dValueRet)
|
2017-06-26 15:56:29 +02:00
|
|
|
{
|
|
|
|
bool fOK = false;
|
2018-09-28 09:56:17 +02:00
|
|
|
try {
|
2017-06-26 15:56:29 +02:00
|
|
|
const UniValue uValue = objJSON[strKey];
|
2018-09-28 09:56:17 +02:00
|
|
|
switch (uValue.getType()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
case UniValue::VNUM:
|
2018-02-27 14:39:48 +01:00
|
|
|
dValueRet = uValue.get_real();
|
2017-06-26 15:56:29 +02:00
|
|
|
fOK = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (std::exception& e) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += std::string(e.what()) + std::string(";");
|
2018-09-28 09:56:17 +02:00
|
|
|
} catch (...) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strErrorMessages += "Unknown exception;";
|
|
|
|
}
|
|
|
|
return fOK;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
The purpose of this function is to replicate the behavior of the
|
|
|
|
Python urlparse function used by sentinel (urlparse.py). This function
|
|
|
|
should return false whenever urlparse raises an exception and true
|
|
|
|
otherwise.
|
|
|
|
*/
|
|
|
|
bool CProposalValidator::CheckURL(const std::string& strURLIn)
|
|
|
|
{
|
|
|
|
std::string strRest(strURLIn);
|
|
|
|
std::string::size_type nPos = strRest.find(':');
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (nPos != std::string::npos) {
|
2017-06-26 15:56:29 +02:00
|
|
|
//std::string strSchema = strRest.substr(0,nPos);
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if (nPos < strRest.size()) {
|
2017-06-26 15:56:29 +02:00
|
|
|
strRest = strRest.substr(nPos + 1);
|
2018-09-28 09:56:17 +02:00
|
|
|
} else {
|
2017-06-26 15:56:29 +02:00
|
|
|
strRest = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process netloc
|
2018-09-28 09:56:17 +02:00
|
|
|
if ((strRest.size() > 2) && (strRest.substr(0, 2) == "//")) {
|
2017-06-26 15:56:29 +02:00
|
|
|
static const std::string strNetlocDelimiters = "/?#";
|
|
|
|
|
|
|
|
strRest = strRest.substr(2);
|
|
|
|
|
|
|
|
std::string::size_type nPos2 = strRest.find_first_of(strNetlocDelimiters);
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
std::string strNetloc = strRest.substr(0, nPos2);
|
2017-06-26 15:56:29 +02:00
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if ((strNetloc.find('[') != std::string::npos) && (strNetloc.find(']') == std::string::npos)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-09-28 09:56:17 +02:00
|
|
|
if ((strNetloc.find(']') != std::string::npos) && (strNetloc.find('[') == std::string::npos)) {
|
2017-06-26 15:56:29 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|