Skip to content

Commit e6b705b

Browse files
committed
refactor: move validators to governance/object.h; remove lonely empty header and cpp
1 parent 962eb17 commit e6b705b

11 files changed

Lines changed: 342 additions & 364 deletions

File tree

src/Makefile.am

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,6 @@ BITCOIN_CORE_H = \
236236
governance/object.h \
237237
governance/signing.h \
238238
governance/superblock.h \
239-
governance/validators.h \
240239
governance/vote.h \
241240
governance/votedb.h \
242241
gsl/assert.h \
@@ -525,7 +524,6 @@ libbitcoin_node_a_SOURCES = \
525524
governance/object.cpp \
526525
governance/signing.cpp \
527526
governance/superblock.cpp \
528-
governance/validators.cpp \
529527
governance/vote.cpp \
530528
governance/votedb.cpp \
531529
gsl/assert.cpp \

src/governance/governance.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
#include <evo/deterministicmns.h>
88
#include <flat-database.h>
99
#include <governance/common.h>
10+
#include <governance/object.h>
1011
#include <governance/superblock.h>
11-
#include <governance/validators.h>
1212
#include <masternode/meta.h>
1313
#include <masternode/sync.h>
1414

@@ -420,7 +420,7 @@ void CGovernanceManager::CheckAndRemove()
420420
} else {
421421
if (pObj->GetObjectType() == GovernanceObject::PROPOSAL) {
422422
std::string strValidationError;
423-
if (!ValidateProposal(pObj->GetDataAsHexString(), strValidationError)) {
423+
if (!governance::ValidateProposal(pObj->GetDataAsHexString(), strValidationError)) {
424424
LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- set for deletion expired obj %s\n", strHash);
425425
pObj->PrepareDeletion(nNow.count());
426426
}

src/governance/object.cpp

Lines changed: 315 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,300 @@
66

77
#include <bls/bls.h>
88
#include <evo/deterministicmns.h>
9-
#include <governance/validators.h>
109
#include <masternode/meta.h>
1110
#include <masternode/sync.h>
1211

1312
#include <chainparams.h>
1413
#include <index/txindex.h>
14+
#include <key_io.h>
1515
#include <logging.h>
1616
#include <node/interface_ui.h>
1717
#include <timedata.h>
1818
#include <tinyformat.h>
19+
#include <util/std23.h>
1920
#include <util/strencodings.h>
2021
#include <util/time.h>
2122
#include <validation.h>
2223
#include <validationinterface.h>
2324

25+
#include <univalue.h>
26+
27+
#include <algorithm>
2428
#include <iostream>
2529
#include <string>
2630

31+
namespace {
32+
33+
constexpr size_t MAX_DATA_SIZE = 512;
34+
constexpr size_t MAX_NAME_SIZE = 40;
35+
36+
bool GetDataValue(const UniValue& objJSON, const std::string& strKey, std::string& strValueRet, std::string& strErrorMessages)
37+
{
38+
try {
39+
strValueRet = objJSON[strKey].get_str();
40+
return true;
41+
} catch (std::exception& e) {
42+
strErrorMessages += std::string(e.what()) + std::string(";");
43+
} catch (...) {
44+
strErrorMessages += "Unknown exception;";
45+
}
46+
return false;
47+
}
48+
49+
bool GetDataValue(const UniValue& objJSON, const std::string& strKey, int64_t& nValueRet, std::string& strErrorMessages)
50+
{
51+
try {
52+
const UniValue uValue = objJSON[strKey];
53+
if (uValue.getType() == UniValue::VNUM) {
54+
nValueRet = uValue.getInt<int64_t>();
55+
return true;
56+
}
57+
} catch (std::exception& e) {
58+
strErrorMessages += std::string(e.what()) + std::string(";");
59+
} catch (...) {
60+
strErrorMessages += "Unknown exception;";
61+
}
62+
return false;
63+
}
64+
65+
bool GetDataValue(const UniValue& objJSON, const std::string& strKey, double& dValueRet, std::string& strErrorMessages)
66+
{
67+
try {
68+
const UniValue uValue = objJSON[strKey];
69+
if (uValue.getType() == UniValue::VNUM) {
70+
dValueRet = uValue.get_real();
71+
return true;
72+
}
73+
} catch (std::exception& e) {
74+
strErrorMessages += std::string(e.what()) + std::string(";");
75+
} catch (...) {
76+
strErrorMessages += "Unknown exception;";
77+
}
78+
return false;
79+
}
80+
81+
bool ValidateType(const UniValue& objJSON, std::string& strErrorMessages)
82+
{
83+
int64_t nType;
84+
if (!GetDataValue(objJSON, "type", nType, strErrorMessages)) {
85+
strErrorMessages += "type field not found;";
86+
return false;
87+
}
88+
89+
if (nType != std23::to_underlying(GovernanceObject::PROPOSAL)) {
90+
strErrorMessages += strprintf("type is not %d;", std23::to_underlying(GovernanceObject::PROPOSAL));
91+
return false;
92+
}
93+
94+
return true;
95+
}
96+
97+
bool ValidateName(const UniValue& objJSON, std::string& strErrorMessages)
98+
{
99+
std::string strName;
100+
if (!GetDataValue(objJSON, "name", strName, strErrorMessages)) {
101+
strErrorMessages += "name field not found;";
102+
return false;
103+
}
104+
105+
if (strName.size() > MAX_NAME_SIZE) {
106+
strErrorMessages += strprintf("name exceeds %lu characters;", MAX_NAME_SIZE);
107+
return false;
108+
}
109+
110+
if (strName.empty()) {
111+
strErrorMessages += "name cannot be empty;";
112+
return false;
113+
}
114+
115+
static constexpr std::string_view strAllowedChars{"-_abcdefghijklmnopqrstuvwxyz0123456789"};
116+
117+
strName = ToLower(strName);
118+
119+
if (strName.find_first_not_of(strAllowedChars) != std::string::npos) {
120+
strErrorMessages += "name contains invalid characters;";
121+
return false;
122+
}
123+
124+
return true;
125+
}
126+
127+
bool ValidateStartEndEpoch(const UniValue& objJSON, bool fCheckExpiration, std::string& strErrorMessages)
128+
{
129+
int64_t nStartEpoch = 0;
130+
int64_t nEndEpoch = 0;
131+
132+
if (!GetDataValue(objJSON, "start_epoch", nStartEpoch, strErrorMessages)) {
133+
strErrorMessages += "start_epoch field not found;";
134+
return false;
135+
}
136+
137+
if (!GetDataValue(objJSON, "end_epoch", nEndEpoch, strErrorMessages)) {
138+
strErrorMessages += "end_epoch field not found;";
139+
return false;
140+
}
141+
142+
if (nEndEpoch <= nStartEpoch) {
143+
strErrorMessages += "end_epoch <= start_epoch;";
144+
return false;
145+
}
146+
147+
if (fCheckExpiration && nEndEpoch <= GetAdjustedTime()) {
148+
strErrorMessages += "expired;";
149+
return false;
150+
}
151+
152+
return true;
153+
}
154+
155+
bool ValidatePaymentAmount(const UniValue& objJSON, std::string& strErrorMessages)
156+
{
157+
double dValue = 0.0;
158+
159+
if (!GetDataValue(objJSON, "payment_amount", dValue, strErrorMessages)) {
160+
strErrorMessages += "payment_amount field not found;";
161+
return false;
162+
}
163+
164+
if (dValue <= 0.0) {
165+
strErrorMessages += "payment_amount is negative;";
166+
return false;
167+
}
168+
169+
// TODO: Should check for an amount which exceeds the budget but this is
170+
// currently difficult because start and end epochs are defined in terms of
171+
// clock time instead of block height.
172+
173+
return true;
174+
}
175+
176+
bool ValidatePaymentAddress(const UniValue& objJSON, bool fAllowScript, std::string& strErrorMessages)
177+
{
178+
std::string strPaymentAddress;
179+
180+
if (!GetDataValue(objJSON, "payment_address", strPaymentAddress, strErrorMessages)) {
181+
strErrorMessages += "payment_address field not found;";
182+
return false;
183+
}
184+
185+
if (std::find_if(strPaymentAddress.begin(), strPaymentAddress.end(), IsSpace) != strPaymentAddress.end()) {
186+
strErrorMessages += "payment_address can't have whitespaces;";
187+
return false;
188+
}
189+
190+
CTxDestination dest = DecodeDestination(strPaymentAddress);
191+
if (!IsValidDestination(dest)) {
192+
strErrorMessages += "payment_address is invalid;";
193+
return false;
194+
}
195+
196+
const ScriptHash *scriptID = std::get_if<ScriptHash>(&dest);
197+
if (!fAllowScript && scriptID) {
198+
strErrorMessages += "script addresses are not supported;";
199+
return false;
200+
}
201+
202+
return true;
203+
}
204+
205+
/*
206+
The purpose of this function is to replicate the behavior of the
207+
Python urlparse function used by sentinel (urlparse.py). This function
208+
should return false whenever urlparse raises an exception and true
209+
otherwise.
210+
*/
211+
bool CheckURL(const std::string& strURLIn)
212+
{
213+
std::string strRest(strURLIn);
214+
std::string::size_type nPos = strRest.find(':');
215+
216+
if (nPos != std::string::npos) {
217+
if (nPos < strRest.size()) {
218+
strRest = strRest.substr(nPos + 1);
219+
} else {
220+
strRest = "";
221+
}
222+
}
223+
224+
// Process netloc
225+
if ((strRest.size() > 2) && (strRest.substr(0, 2) == "//")) {
226+
static constexpr std::string_view strNetlocDelimiters{"/?#"};
227+
228+
strRest = strRest.substr(2);
229+
230+
std::string::size_type nPos2 = strRest.find_first_of(strNetlocDelimiters);
231+
232+
std::string strNetloc = strRest.substr(0, nPos2);
233+
234+
if ((strNetloc.find('[') != std::string::npos) && (strNetloc.find(']') == std::string::npos)) {
235+
return false;
236+
}
237+
238+
if ((strNetloc.find(']') != std::string::npos) && (strNetloc.find('[') == std::string::npos)) {
239+
return false;
240+
}
241+
}
242+
243+
return true;
244+
}
245+
246+
bool ValidateURL(const UniValue& objJSON, std::string& strErrorMessages)
247+
{
248+
std::string strURL;
249+
if (!GetDataValue(objJSON, "url", strURL, strErrorMessages)) {
250+
strErrorMessages += "url field not found;";
251+
return false;
252+
}
253+
254+
if (std::find_if(strURL.begin(), strURL.end(), IsSpace) != strURL.end()) {
255+
strErrorMessages += "url can't have whitespaces;";
256+
return false;
257+
}
258+
259+
if (strURL.size() < 4U) {
260+
strErrorMessages += "url too short;";
261+
return false;
262+
}
263+
264+
if (!CheckURL(strURL)) {
265+
strErrorMessages += "url invalid;";
266+
return false;
267+
}
268+
269+
return true;
270+
}
271+
272+
bool ParseProposalJSON(const std::string& strHexData, UniValue& objJSONOut, std::string& strErrorMessages)
273+
{
274+
if (strHexData.empty()) return false;
275+
276+
std::vector<unsigned char> v = ParseHex(strHexData);
277+
if (v.size() > MAX_DATA_SIZE) {
278+
strErrorMessages = strprintf("data exceeds %lu characters;", MAX_DATA_SIZE);
279+
return false;
280+
}
281+
282+
const std::string strJSONData(v.begin(), v.end());
283+
if (strJSONData.empty()) return false;
284+
285+
try {
286+
UniValue obj(UniValue::VOBJ);
287+
obj.read(strJSONData);
288+
if (!obj.isObject()) {
289+
throw std::runtime_error("Proposal must be a JSON object");
290+
}
291+
objJSONOut = obj;
292+
return true;
293+
} catch (std::exception& e) {
294+
strErrorMessages += std::string(e.what()) + std::string(";");
295+
} catch (...) {
296+
strErrorMessages += "Unknown exception;";
297+
}
298+
return false;
299+
}
300+
301+
} // namespace
302+
27303
std::ostream& operator<<(std::ostream& os, governance_exception_type_enum_t eType)
28304
{
29305
switch (eType) {
@@ -429,7 +705,7 @@ bool CGovernanceObject::IsValidLocally(const CDeterministicMNList& tip_mn_list,
429705
// they are going to be cleared by CGovernanceManager::CheckAndRemove()
430706
// TODO: should they be tagged as "expired" to skip vote downloading?
431707
std::string strValidationError;
432-
if (!ValidateProposal(GetDataAsHexString(), strValidationError, /*fCheckExpiration=*/false)) {
708+
if (!governance::ValidateProposal(GetDataAsHexString(), strValidationError, /*fCheckExpiration=*/false)) {
433709
strError = strprintf("Invalid proposal data, error messages: %s", strValidationError);
434710
return false;
435711
}
@@ -693,3 +969,40 @@ void CGovernanceObject::UpdateSentinelVariables(const CDeterministicMNList& tip_
693969

694970
if (GetAbsoluteNoCount(tip_mn_list, VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false;
695971
}
972+
973+
namespace governance {
974+
bool ValidateProposal(const std::string& strDataHex, std::string& strErrorOut,
975+
bool fCheckExpiration, bool fAllowScript)
976+
{
977+
UniValue objJSON(UniValue::VOBJ);
978+
if (!ParseProposalJSON(strDataHex, objJSON, strErrorOut)) {
979+
strErrorOut += "JSON parsing error;";
980+
return false;
981+
}
982+
if (!ValidateType(objJSON, strErrorOut)) {
983+
strErrorOut += "Invalid type;";
984+
return false;
985+
}
986+
if (!ValidateName(objJSON, strErrorOut)) {
987+
strErrorOut += "Invalid name;";
988+
return false;
989+
}
990+
if (!ValidateStartEndEpoch(objJSON, fCheckExpiration, strErrorOut)) {
991+
strErrorOut += "Invalid start:end range;";
992+
return false;
993+
}
994+
if (!ValidatePaymentAmount(objJSON, strErrorOut)) {
995+
strErrorOut += "Invalid payment amount;";
996+
return false;
997+
}
998+
if (!ValidatePaymentAddress(objJSON, fAllowScript, strErrorOut)) {
999+
strErrorOut += "Invalid payment address;";
1000+
return false;
1001+
}
1002+
if (!ValidateURL(objJSON, strErrorOut)) {
1003+
strErrorOut += "Invalid URL;";
1004+
return false;
1005+
}
1006+
return true;
1007+
}
1008+
} // namespace governance

0 commit comments

Comments
 (0)