Skip to content
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

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/xrpld/app/consensus/RCLConsensus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ RCLConsensus::Adaptor::onClose(
{
feeVote_->doVoting(prevLedger, validations, initialSet);
app_.getAmendmentTable().doVoting(
prevLedger, validations, initialSet);
prevLedger, validations, initialSet, j_);
}
}
else if (
Expand Down
7 changes: 6 additions & 1 deletion src/xrpld/app/misc/AmendmentTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ class AmendmentTable
doVoting(
std::shared_ptr<ReadView const> const& lastClosedLedger,
std::vector<std::shared_ptr<STValidation>> const& parentValidations,
std::shared_ptr<SHAMap> const& initialPosition)
std::shared_ptr<SHAMap> const& initialPosition,
beast::Journal j)
{
// Ask implementation what to do
auto actions = doVoting(
Expand All @@ -174,6 +175,10 @@ class AmendmentTable
Serializer s;
amendTx.add(s);

JLOG(j.debug()) << "Amendments: Adding pseudo-transaction: "
<< amendTx.getTransactionID() << ": "
<< strHex(s.slice()) << ": " << amendTx;

initialPosition->addGiveItem(
SHAMapNodeType::tnTRANSACTION_NM,
make_shamapitem(amendTx.getTransactionID(), s.slice()));
Expand Down
140 changes: 110 additions & 30 deletions src/xrpld/app/misc/detail/AmendmentTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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_;

Expand Down Expand Up @@ -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];
}
}
Expand All @@ -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
Expand All @@ -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;
}
});
}

Expand All @@ -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};
}
};

Expand Down Expand Up @@ -787,13 +847,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
Expand All @@ -803,43 +863,63 @@ AmendmentTableImpl::doVoting(
// process all amendments we know of
for (auto const& entry : amendmentMap_)
{
NetClock::time_point majorityTime = {};
if (enabledAmendments.count(entry.first) != 0)
{
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;
ss << entry.first << " (" << entry.second.name << ") has "
<< vote->votes(entry.first) << " votes";

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
Expand Down
Loading