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 26 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3bcb787
Add logging ONLY for amendment voting decision process
ximinez Oct 28, 2024
f1348b8
Change how "received validations" are counted, and refactoring
ximinez Oct 28, 2024
44996f9
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Oct 31, 2024
c3a3a90
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Nov 4, 2024
a2994dd
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Nov 5, 2024
620a2a2
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Nov 8, 2024
2979d86
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Nov 13, 2024
3334704
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Nov 13, 2024
72e0dbb
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Nov 27, 2024
a3d06e1
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Dec 3, 2024
58b6f35
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Dec 5, 2024
d019338
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Dec 16, 2024
1cba48e
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Dec 20, 2024
b934598
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Jan 7, 2025
4af840e
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Jan 9, 2025
e6d83f6
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Jan 24, 2025
8eae829
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Jan 28, 2025
d7262e0
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Jan 30, 2025
da15e6b
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 4, 2025
bbc6140
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 6, 2025
d701b97
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 7, 2025
f7c01e7
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 8, 2025
bafe5bc
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 11, 2025
b5a5bca
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 12, 2025
0ff3dc0
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 13, 2025
b3dc834
Merge remote-tracking branch 'upstream/develop' into pr/val_logging
ximinez Feb 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/xrpld/app/consensus/RCLConsensus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,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();
Copy link
Collaborator

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

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 @@ -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
Expand All @@ -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)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

auto message = std::format("{} ({}) has {} votes", 
                           entry.first, 
                           entry.second.name, 
                           vote->votes(entry.first));

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 std::format. I don't object to starting this change process, but I can't promise it'll catch on.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this didn't work on my end. I think std::format doesn't like the unit256 type. I can look more into it later.

ss << entry.first << " (" << entry.second.name << ") has "
<< vote->votes(entry.first) << " votes";
Comment on lines +887 to +888
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ss itself is not used anywhere, only .str() result. Maybe create string variable logStr for this and provide it to JLOGs?


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