20#include <xrpld/app/main/Application.h>
21#include <xrpld/app/misc/AmendmentTable.h>
22#include <xrpld/app/rdb/Wallet.h>
23#include <xrpld/core/ConfigSections.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/STValidation.h>
26#include <xrpl/protocol/TxFlags.h>
27#include <xrpl/protocol/jss.h>
29#include <boost/algorithm/string.hpp>
30#include <boost/format.hpp>
31#include <boost/range/adaptor/transformed.hpp>
32#include <boost/regex.hpp>
41 static boost::regex
const re1(
44 "([abcdefABCDEF0-9]{64})"
48 boost::regex_constants::optimize);
52 for (
auto const& line : section.
lines())
56 if (!boost::regex_match(line,
match, re1))
57 Throw<std::runtime_error>(
58 "Invalid entry '" + line +
"' in [" + section.
name() +
"]");
62 if (!
id.parseHex(
match[1]))
63 Throw<std::runtime_error>(
64 "Invalid amendment ID '" +
match[1] +
"' in [" +
65 section.
name() +
"]");
123 newRecordedVotes.reserve(allTrusted.
size());
128 for (
auto& trusted : allTrusted)
139 newRecordedVotes[trusted];
169 using namespace std::chrono_literals;
172 auto const newTimeout = closeTime + expiresAfter;
176 for (
auto const& val : valSet)
181 if (
auto const iter =
recordedVotes_.find(val->getSignerPublic());
184 iter->second.timeout = newTimeout;
185 if (val->isFieldPresent(sfAmendments))
187 auto const& choices = val->getFieldV256(sfAmendments);
188 iter->second.upVotes.assign(choices.begin(), choices.end());
190 <<
"recordVotes: Validation from trusted " << pkHuman
191 <<
" has " << choices.size() <<
" amendment votes: "
192 << boost::algorithm::join(
193 iter->second.upVotes |
194 boost::adaptors::transformed(
195 to_string<256, void>),
206 iter->second.upVotes.clear();
207 JLOG(j.
debug()) <<
"recordVotes: Validation from trusted "
208 << pkHuman <<
" has no amendment votes.";
214 <<
"recordVotes: Ignoring validation from untrusted "
223 [&closeTime, newTimeout, &j](
226 toBase58(TokenType::NodePublic, votes.first);
227 if (!votes.second.timeout)
230 votes.second.upVotes.empty(),
231 "ripple::TrustedVotes::recordVotes : received no "
234 <<
"recordVotes: Have not received any "
235 "amendment votes from "
236 << pkHuman <<
" since last timeout or startup";
238 else if (closeTime > votes.second.timeout)
241 <<
"recordVotes: Timeout: Clearing votes from "
243 votes.second.timeout.reset();
244 votes.second.upVotes.clear();
246 else if (votes.second.timeout != newTimeout)
249 votes.second.timeout < newTimeout,
250 "ripple::TrustedVotes::recordVotes : votes not "
252 using namespace std::chrono;
253 auto const age = duration_cast<minutes>(
254 newTimeout - *votes.second.timeout);
255 JLOG(j.debug()) <<
"recordVotes: Using " << age.count()
256 <<
"min old cached votes from " << pkHuman;
269 for (
auto& validatorVotes : recordedVotes_)
272 validatorVotes.second.timeout ||
273 validatorVotes.second.upVotes.empty(),
274 "ripple::TrustedVotes::getVotes : valid votes");
275 if (validatorVotes.second.timeout)
277 for (
uint256 const& amendment : validatorVotes.second.upVotes)
300 bool enabled =
false;
303 bool supported =
false;
319 int trustedValidations_ = 0;
326 threshold_ = !rules_.
enabled(fixAmendmentMajorityCalc)
330 (trustedValidations_ *
336 (trustedValidations_ *
349 auto [trustedCount, newVotes] = trustedVotes.
getVotes(rules, lock);
351 trustedValidations_ = trustedCount;
352 votes_.
swap(newVotes);
354 computeThreshold(trustedValidations_, rules);
360 auto const& it = votes_.
find(amendment);
362 if (it == votes_.
end())
370 if (!rules_.
enabled(fixAmendmentMajorityCalc) ||
371 trustedValidations_ == 1)
372 return it->second >= threshold_;
374 return it->second > threshold_;
380 auto const& it = votes_.
find(amendment);
382 if (it == votes_.
end())
391 return trustedValidations_;
480 veto(
uint256 const& amendment)
override;
482 unVeto(
uint256 const& amendment)
override;
485 enable(
uint256 const& amendment)
override;
488 isEnabled(
uint256 const& amendment)
const override;
490 isSupported(
uint256 const& amendment)
const override;
493 hasUnsupportedEnabled()
const override;
496 firstUnsupportedExpected()
const override;
499 getJson(
bool isAdmin)
const override;
504 needValidatedLedger(
LedgerIndex seq)
const override;
519 getDesired()
const override;
532AmendmentTableImpl::AmendmentTableImpl(
540 , majorityTime_(majorityTime)
541 , unsupportedEnabled_(false)
543 , db_(app.getWalletDB())
548 bool const featureVotesExist = [
this]() {
554 for (
auto const& [name, amendment, votebehavior] : supported)
560 switch (votebehavior)
575 JLOG(
j_.
debug()) <<
"Amendment " << amendment <<
" (" << s.
name
576 <<
") is supported and will be "
578 <<
" voted by default if not enabled on the ledger.";
585 if (featureVotesExist)
587 JLOG(
j_.
warn()) <<
"[amendments] section in config file ignored"
588 " in favor of data in db/wallet.db.";
593 detect_conflict.
insert(a.first);
601 if (featureVotesExist)
604 <<
"[veto_amendments] section in config file ignored"
605 " in favor of data in db/wallet.db.";
610 if (detect_conflict.
count(a.first) == 0)
617 <<
"[veto_amendments] section in config has amendment "
618 <<
'(' << a.first <<
", " << a.second
619 <<
") both [veto_amendments] and [amendments].";
628 [&](boost::optional<std::string> amendment_hash,
629 boost::optional<std::string> amendment_name,
630 boost::optional<AmendmentVote> vote) {
632 if (!amendment_hash || !amendment_name || !vote)
635 Throw<std::runtime_error>(
636 "Invalid FeatureVotes row in wallet.db");
638 if (!amend_hash.
parseHex(*amendment_hash))
640 Throw<std::runtime_error>(
641 "Invalid amendment ID '" + *amendment_hash +
647 if (
auto s =
get(amend_hash, lock))
649 JLOG(
j_.
info()) <<
"Amendment {" << *amendment_name <<
", "
650 << amend_hash <<
"} is downvoted.";
651 if (!amendment_name->empty())
652 s->name = *amendment_name;
662 JLOG(
j_.
debug()) <<
"Amendment {" << *amendment_name <<
", "
663 << amend_hash <<
"} is upvoted.";
664 if (!amendment_name->empty())
665 s.
name = *amendment_name;
713 if (name == e.second.name)
728 "ripple::AmendmentTableImpl::persistVote : valid vote input");
772 JLOG(
j_.
error()) <<
"Unsupported amendment " << amendment
823 (enabled.
count(e.first) == 0))
825 amendments.push_back(e.first);
826 JLOG(
j_.
info()) <<
"Voting for amendment " << e.second.name;
831 if (!amendments.empty())
832 std::sort(amendments.begin(), amendments.end());
853 <<
": " << enabledAmendments.
size() <<
", "
854 << majorityAmendments.
size() <<
", " << valSet.size();
864 JLOG(
j_.
debug()) <<
"Counted votes from " << vote->trustedValidations()
865 <<
" valid trusted validations, threshold is: "
866 << vote->threshold();
875 if (enabledAmendments.
contains(entry.first))
877 JLOG(
j_.
trace()) << entry.first <<
": amendment already enabled";
882 bool const hasValMajority = vote->passes(entry.first);
885 auto const it = majorityAmendments.
find(entry.first);
886 if (it != majorityAmendments.
end())
891 bool const hasLedgerMajority = majorityTime.has_value();
893 auto const logStr = [&entry, &vote]() {
895 ss << entry.first <<
" (" << entry.second.name <<
") has "
896 << vote->votes(entry.first) <<
" votes";
900 if (hasValMajority && !hasLedgerMajority &&
905 JLOG(
j_.
debug()) << logStr <<
": amendment got majority";
908 else if (!hasValMajority && hasLedgerMajority)
911 JLOG(
j_.
debug()) << logStr <<
": amendment lost majority";
920 JLOG(
j_.
debug()) << logStr <<
": amendment majority held";
921 actions[entry.first] = 0;
924 else if (hasValMajority && hasLedgerMajority)
928 <<
": amendment holding majority, waiting to be enabled";
930 else if (!hasValMajority)
932 JLOG(
j_.
debug()) << logStr <<
": amendment does not have majority";
958 for (
auto& e : enabled)
970 for (
auto const& [hash, time] : majority)
979 JLOG(
j_.
info()) <<
"Unsupported amendment " << hash
980 <<
" reached majority at " <<
to_string(time);
1005 v[jss::name] = fs.
name;
1011 v[jss::vetoed] =
"Obsolete";
1019 auto const votesTotal =
lastVote_->trustedValidations();
1020 auto const votesNeeded =
lastVote_->threshold();
1021 auto const votesFor =
lastVote_->votes(
id);
1023 v[jss::count] = votesFor;
1024 v[jss::validations] = votesTotal;
1027 v[jss::threshold] = votesNeeded;
1078 return std::make_unique<AmendmentTableImpl>(
1079 app, majorityTime, supported, enabled, vetoed, journal);
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
The status of all amendments requested in a given window.
int votes(uint256 const &amendment) const
int trustedValidations() const
void computeThreshold(int trustedValidations, Rules const &rules)
bool passes(uint256 const &amendment) const
hash_map< uint256, int > votes_
AmendmentSet(Rules const &rules, TrustedVotes const &trustedVotes, std::lock_guard< std::mutex > const &lock)
Track the list of "amendments".
uint256 find(std::string const &name) const override
bool unVeto(uint256 const &amendment) override
bool enable(uint256 const &amendment) override
bool needValidatedLedger(LedgerIndex seq) const override
Called to determine whether the amendment logic needs to process a new validated ledger.
std::optional< NetClock::time_point > firstUnsupportedExpected_
std::vector< uint256 > getDesired() const override
bool veto(uint256 const &amendment) override
std::unique_ptr< AmendmentSet > lastVote_
std::chrono::seconds const majorityTime_
void doValidatedLedger(LedgerIndex seq, std::set< uint256 > const &enabled, majorityAmendments_t const &majority) override
bool isEnabled(uint256 const &amendment) const override
AmendmentState & add(uint256 const &amendment, std::lock_guard< std::mutex > const &lock)
TrustedVotes previousTrustedVotes_
hash_map< uint256, AmendmentState > amendmentMap_
void injectJson(Json::Value &v, uint256 const &amendment, AmendmentState const &state, bool isAdmin, std::lock_guard< std::mutex > const &lock) const
Json::Value getJson(bool isAdmin) const override
void trustChanged(hash_set< PublicKey > const &allTrusted) override
std::vector< uint256 > doValidation(std::set< uint256 > const &enabledAmendments) const override
AmendmentState * get(uint256 const &amendment, std::lock_guard< std::mutex > const &lock)
void persistVote(uint256 const &amendment, std::string const &name, AmendmentVote vote) const
bool hasUnsupportedEnabled() const override
returns true if one or more amendments on the network have been enabled that this server does not sup...
std::map< uint256, std::uint32_t > doVoting(Rules const &rules, NetClock::time_point closeTime, std::set< uint256 > const &enabledAmendments, majorityAmendments_t const &majorityAmendments, std::vector< std::shared_ptr< STValidation > > const &validations) override
bool isSupported(uint256 const &amendment) const override
std::optional< NetClock::time_point > firstUnsupportedExpected() const override
std::uint32_t lastUpdateSeq_
The amendment table stores the list of enabled and potential amendments.
LockedSociSession checkoutDb()
Rules controlling protocol behavior.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Holds a collection of configuration values.
std::string const & name() const
Returns the name of this section.
std::vector< std::string > const & lines() const
Returns all the lines in the section.
TrustedVotes records the most recent votes from trusted validators.
void recordVotes(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)
std::pair< int, hash_map< uint256, int > > getVotes(Rules const &rules, std::lock_guard< std::mutex > const &lock) const
hash_map< PublicKey, UpvotesAndTimeout > recordedVotes_
TrustedVotes & operator=(TrustedVotes const &rhs)=delete
void trustChanged(hash_set< PublicKey > const &allTrusted, std::lock_guard< std::mutex > const &lock)
TrustedVotes(TrustedVotes const &rhs)=delete
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
@ objectValue
object value (collection of name/value pairs).
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static std::vector< std::pair< uint256, std::string > > parseSection(Section const §ion)
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
constexpr std::uint32_t tfGotMajority
bool isAdmin(Port const &port, Json::Value const ¶ms, beast::IP::Address const &remoteIp)
bool createFeatureVotes(soci::session &session)
createFeatureVotes Creates the FeatureVote table if it does not exist.
void readAmendments(soci::session &session, std::function< void(boost::optional< std::string > amendment_hash, boost::optional< std::string > amendment_name, boost::optional< AmendmentVote > vote)> const &callback)
readAmendments Reads all amendments from the FeatureVotes table.
constexpr std::ratio< 80, 100 > postFixAmendmentMajorityCalcThreshold
constexpr std::ratio< 204, 256 > preFixAmendmentMajorityCalcThreshold
The minimum amount of support an amendment should have.
std::string to_string(base_uint< Bits, Tag > const &a)
T get(Section const §ion, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
std::unique_ptr< AmendmentTable > make_AmendmentTable(Application &app, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
constexpr std::uint32_t tfLostMajority
void voteAmendment(soci::session &session, uint256 const &amendment, std::string const &name, AmendmentVote vote)
voteAmendment Set the veto value for a particular amendment.
Current state of an amendment.
bool supported
Indicates an amendment that this server has code support for.
bool enabled
Indicates that the amendment has been enabled.
std::string name
The name of this amendment, possibly empty.
AmendmentVote vote
If an amendment is down-voted, a server will not vote to enable it.
std::vector< uint256 > upVotes
std::optional< NetClock::time_point > timeout
An unseated timeout indicates that either.
T time_since_epoch(T... args)