-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add logging and improve counting of amendment votes from UNL #5173
base: develop
Are you sure you want to change the base?
Changes from 10 commits
3bcb787
f1348b8
44996f9
c3a3a90
a2994dd
620a2a2
2979d86
3334704
72e0dbb
a3d06e1
58b6f35
d019338
1cba48e
b934598
4af840e
e6d83f6
8eae829
d7262e0
da15e6b
bbc6140
d701b97
f7c01e7
bafe5bc
b5a5bca
0ff3dc0
b3dc834
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,10 @@ | |
#include <xrpl/protocol/STValidation.h> | ||
#include <xrpl/protocol/TxFlags.h> | ||
#include <xrpl/protocol/jss.h> | ||
|
||
#include <boost/algorithm/string.hpp> | ||
#include <boost/format.hpp> | ||
#include <boost/range/adaptor/transformed.hpp> | ||
#include <boost/regex.hpp> | ||
#include <algorithm> | ||
#include <mutex> | ||
|
@@ -88,15 +91,17 @@ parseSection(Section const& section) | |
class TrustedVotes | ||
{ | ||
private: | ||
static constexpr NetClock::time_point maxTimeout = | ||
NetClock::time_point::max(); | ||
|
||
// Associates each trusted validator with the last votes we saw from them | ||
// and an expiration for that record. | ||
struct UpvotesAndTimeout | ||
{ | ||
std::vector<uint256> upVotes; | ||
NetClock::time_point timeout = maxTimeout; | ||
/** An unseated timeout indicates that either | ||
1. No validations have ever been received | ||
2. The validator has not been heard from in long enough that the | ||
timeout passed, and votes expired. | ||
*/ | ||
std::optional<NetClock::time_point> timeout; | ||
}; | ||
hash_map<PublicKey, UpvotesAndTimeout> recordedVotes_; | ||
|
||
|
@@ -130,7 +135,7 @@ class TrustedVotes | |
else | ||
{ | ||
// New validators have a starting position of no on everything. | ||
// Add the entry with an empty vector and maxTimeout. | ||
// Add the entry with an empty vector and unseated timeout. | ||
newRecordedVotes[trusted]; | ||
} | ||
} | ||
|
@@ -147,6 +152,7 @@ class TrustedVotes | |
Rules const& rules, | ||
std::vector<std::shared_ptr<STValidation>> const& valSet, | ||
NetClock::time_point const closeTime, | ||
beast::Journal j, | ||
std::lock_guard<std::mutex> const& lock) | ||
{ | ||
// When we get an STValidation we save the upVotes it contains, but | ||
|
@@ -163,38 +169,86 @@ class TrustedVotes | |
using namespace std::chrono_literals; | ||
static constexpr NetClock::duration expiresAfter = 24h; | ||
|
||
auto const newTimeout = closeTime + expiresAfter; | ||
|
||
// Walk all validations and replace previous votes from trusted | ||
// validators with these newest votes. | ||
for (auto const& val : valSet) | ||
{ | ||
auto const pkHuman = | ||
toBase58(TokenType::NodePublic, val->getSignerPublic()); | ||
// If this validation comes from one of our trusted validators... | ||
if (auto const iter = recordedVotes_.find(val->getSignerPublic()); | ||
iter != recordedVotes_.end()) | ||
{ | ||
iter->second.timeout = closeTime + expiresAfter; | ||
iter->second.timeout = newTimeout; | ||
if (val->isFieldPresent(sfAmendments)) | ||
{ | ||
auto const& choices = val->getFieldV256(sfAmendments); | ||
iter->second.upVotes.assign(choices.begin(), choices.end()); | ||
JLOG(j.debug()) | ||
<< "recordVotes: Validation from trusted " << pkHuman | ||
<< " has " << choices.size() << " amendment votes: " | ||
<< boost::algorithm::join( | ||
iter->second.upVotes | | ||
boost::adaptors::transformed( | ||
to_string<256, void>), | ||
", "); | ||
// TODO: Maybe transform using to_short_string once #5126 is | ||
// merged | ||
// | ||
// iter->second.upVotes | | ||
// boost::adaptors::transformed(to_short_string<256, void>) | ||
} | ||
else | ||
{ | ||
// This validator does not upVote any amendments right now. | ||
iter->second.upVotes.clear(); | ||
JLOG(j.debug()) << "recordVotes: Validation from trusted " | ||
<< pkHuman << " has no amendment votes."; | ||
} | ||
} | ||
else | ||
{ | ||
JLOG(j.debug()) | ||
<< "recordVotes: Ignoring validation from untrusted " | ||
<< pkHuman; | ||
} | ||
} | ||
|
||
// Now remove any expired records from recordedVotes_. | ||
std::for_each( | ||
recordedVotes_.begin(), | ||
recordedVotes_.end(), | ||
[&closeTime](decltype(recordedVotes_)::value_type& votes) { | ||
if (closeTime > votes.second.timeout) | ||
[&closeTime, newTimeout, &j]( | ||
decltype(recordedVotes_)::value_type& votes) { | ||
auto const pkHuman = | ||
toBase58(TokenType::NodePublic, votes.first); | ||
if (!votes.second.timeout) | ||
{ | ||
assert(votes.second.upVotes.empty()); | ||
JLOG(j.debug()) | ||
<< "recordVotes: Have not received any " | ||
"amendment votes from " | ||
<< pkHuman << " since last timeout or startup"; | ||
} | ||
else if (closeTime > votes.second.timeout) | ||
{ | ||
votes.second.timeout = maxTimeout; | ||
JLOG(j.debug()) | ||
<< "recordVotes: Timeout: Clearing votes from " | ||
<< pkHuman; | ||
votes.second.timeout.reset(); | ||
votes.second.upVotes.clear(); | ||
} | ||
else if (votes.second.timeout != newTimeout) | ||
{ | ||
assert(votes.second.timeout < newTimeout); | ||
using namespace std::chrono; | ||
auto const age = duration_cast<minutes>( | ||
newTimeout - *votes.second.timeout); | ||
JLOG(j.debug()) << "recordVotes: Using " << age.count() | ||
<< "min old cached votes from " << pkHuman; | ||
} | ||
}); | ||
} | ||
|
||
|
@@ -205,14 +259,20 @@ class TrustedVotes | |
getVotes(Rules const& rules, std::lock_guard<std::mutex> const& lock) const | ||
{ | ||
hash_map<uint256, int> ret; | ||
int available = 0; | ||
for (auto& validatorVotes : recordedVotes_) | ||
{ | ||
assert( | ||
validatorVotes.second.timeout || | ||
validatorVotes.second.upVotes.empty()); | ||
if (validatorVotes.second.timeout) | ||
++available; | ||
for (uint256 const& amendment : validatorVotes.second.upVotes) | ||
{ | ||
ret[amendment] += 1; | ||
} | ||
} | ||
return {recordedVotes_.size(), ret}; | ||
return {available, ret}; | ||
} | ||
}; | ||
|
||
|
@@ -789,13 +849,13 @@ AmendmentTableImpl::doVoting( | |
std::lock_guard lock(mutex_); | ||
|
||
// Keep a record of the votes we received. | ||
previousTrustedVotes_.recordVotes(rules, valSet, closeTime, lock); | ||
previousTrustedVotes_.recordVotes(rules, valSet, closeTime, j_, lock); | ||
|
||
// Tally the most recent votes. | ||
auto vote = | ||
std::make_unique<AmendmentSet>(rules, previousTrustedVotes_, lock); | ||
JLOG(j_.debug()) << "Received " << vote->trustedValidations() | ||
<< " trusted validations, threshold is: " | ||
JLOG(j_.debug()) << "Counted votes from " << vote->trustedValidations() | ||
<< " valid trusted validations, threshold is: " | ||
<< vote->threshold(); | ||
|
||
// Map of amendments to the action to be taken for each one. The action is | ||
|
@@ -805,43 +865,63 @@ AmendmentTableImpl::doVoting( | |
// process all amendments we know of | ||
for (auto const& entry : amendmentMap_) | ||
{ | ||
NetClock::time_point majorityTime = {}; | ||
if (enabledAmendments.count(entry.first) != 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe switch .count() to .contains(). can ignore as this was the existing code |
||
{ | ||
JLOG(j_.trace()) << entry.first << ": amendment already enabled"; | ||
|
||
continue; | ||
} | ||
|
||
bool const hasValMajority = vote->passes(entry.first); | ||
|
||
{ | ||
auto const majorityTime = [&]() -> std::optional<NetClock::time_point> { | ||
auto const it = majorityAmendments.find(entry.first); | ||
if (it != majorityAmendments.end()) | ||
majorityTime = it->second; | ||
} | ||
return it->second; | ||
return std::nullopt; | ||
}(); | ||
|
||
if (enabledAmendments.count(entry.first) != 0) | ||
{ | ||
JLOG(j_.debug()) << entry.first << ": amendment already enabled"; | ||
} | ||
else if ( | ||
hasValMajority && (majorityTime == NetClock::time_point{}) && | ||
bool const hasLedgerMajority = majorityTime.has_value(); | ||
|
||
std::stringstream ss; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] There is no real need to use streams here. We should move to using more modern and concise std::format in such cases.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use streams everywhere else we build strings this way. I don't see any instances of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, this didn't work on my end. I think |
||
ss << entry.first << " (" << entry.second.name << ") has " | ||
<< vote->votes(entry.first) << " votes"; | ||
Comment on lines
+887
to
+888
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
|
||
if (hasValMajority && !hasLedgerMajority && | ||
entry.second.vote == AmendmentVote::up) | ||
{ | ||
// Ledger says no majority, validators say yes | ||
JLOG(j_.debug()) << entry.first << ": amendment got majority"; | ||
// Ledger says no majority, validators say yes, and voting yes | ||
// locally | ||
JLOG(j_.debug()) << ss.str() << ": amendment got majority"; | ||
actions[entry.first] = tfGotMajority; | ||
} | ||
else if (!hasValMajority && (majorityTime != NetClock::time_point{})) | ||
else if (!hasValMajority && hasLedgerMajority) | ||
{ | ||
// Ledger says majority, validators say no | ||
JLOG(j_.debug()) << entry.first << ": amendment lost majority"; | ||
JLOG(j_.debug()) << ss.str() << ": amendment lost majority"; | ||
actions[entry.first] = tfLostMajority; | ||
} | ||
else if ( | ||
(majorityTime != NetClock::time_point{}) && | ||
((majorityTime + majorityTime_) <= closeTime) && | ||
hasLedgerMajority && | ||
((*majorityTime + majorityTime_) <= closeTime) && | ||
entry.second.vote == AmendmentVote::up) | ||
{ | ||
// Ledger says majority held | ||
JLOG(j_.debug()) << entry.first << ": amendment majority held"; | ||
JLOG(j_.debug()) << ss.str() << ": amendment majority held"; | ||
actions[entry.first] = 0; | ||
} | ||
// Logging only below this point | ||
else if (hasValMajority && hasLedgerMajority) | ||
{ | ||
JLOG(j_.debug()) | ||
<< ss.str() | ||
<< ": amendment holding majority, waiting to be enabled"; | ||
} | ||
else if (!hasValMajority) | ||
{ | ||
JLOG(j_.debug()) | ||
<< ss.str() << ": amendment does not have majority"; | ||
} | ||
} | ||
|
||
// Stash for reporting | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this is much clearer and safer, than using NetClock::time_point::max