1#include <xrpl/ledger/AmendmentTable.h>
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/UnorderedContainers.h>
5#include <xrpl/basics/base_uint.h>
6#include <xrpl/basics/chrono.h>
7#include <xrpl/basics/contract.h>
8#include <xrpl/beast/utility/Journal.h>
9#include <xrpl/beast/utility/instrumentation.h>
10#include <xrpl/config/BasicConfig.h>
11#include <xrpl/core/ServiceRegistry.h>
12#include <xrpl/json/json_value.h>
13#include <xrpl/ledger/View.h>
14#include <xrpl/protocol/Feature.h>
15#include <xrpl/protocol/Protocol.h>
16#include <xrpl/protocol/PublicKey.h>
17#include <xrpl/protocol/Rules.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STValidation.h>
20#include <xrpl/protocol/SystemParameters.h>
21#include <xrpl/protocol/TxFlags.h>
22#include <xrpl/protocol/jss.h>
23#include <xrpl/protocol/tokens.h>
24#include <xrpl/server/Wallet.h>
26#include <boost/algorithm/string/join.hpp>
27#include <boost/optional/optional.hpp>
28#include <boost/range/adaptor/transformed.hpp>
29#include <boost/regex/v5/regbase.hpp>
30#include <boost/regex/v5/regex.hpp>
31#include <boost/regex/v5/regex_match.hpp>
50static std::vector<std::pair<UInt256, std::string>>
53 static boost::regex
const kRe1(
56 "([abcdefABCDEF0-9]{64})"
60 boost::regex_constants::optimize);
64 for (
auto const& line : section.
lines())
68 if (!boost::regex_match(line, match, kRe1))
76 if (!
id.parseHex(match[1]))
79 std::format(
"Invalid amendment ID '{}' in [{}]", match[1].str(), section.
name()));
138 newRecordedVotes.reserve(allTrusted.
size());
143 for (
auto& trusted : allTrusted)
154 newRecordedVotes[trusted];
184 using namespace std::chrono_literals;
187 auto const newTimeout = closeTime + kExpiresAfter;
191 for (
auto const& val : valSet)
195 if (
auto const iter =
recordedVotes_.find(val->getSignerPublic());
198 iter->second.timeout = newTimeout;
199 if (val->isFieldPresent(sfAmendments))
201 auto const& choices = val->getFieldV256(sfAmendments);
202 iter->second.upVotes.assign(choices.begin(), choices.end());
203 JLOG(j.
debug()) <<
"recordVotes: Validation from trusted " << pkHuman <<
" has "
204 << choices.size() <<
" amendment votes: "
205 << boost::algorithm::join(
206 iter->second.upVotes |
218 iter->second.upVotes.clear();
219 JLOG(j.
debug()) <<
"recordVotes: Validation from trusted " << pkHuman
220 <<
" has no amendment votes.";
225 JLOG(j.
debug()) <<
"recordVotes: Ignoring validation from untrusted " << pkHuman;
232 [&closeTime, newTimeout, &j](
decltype(
recordedVotes_)::value_type& votes) {
234 if (!votes.second.timeout)
237 votes.second.upVotes.empty(),
238 "xrpl::TrustedVotes::recordVotes : received no "
240 JLOG(j.
debug()) <<
"recordVotes: Have not received any "
241 "amendment votes from "
242 << pkHuman <<
" since last timeout or startup";
244 else if (closeTime > votes.second.timeout)
246 JLOG(j.
debug()) <<
"recordVotes: Timeout: Clearing votes from " << pkHuman;
247 votes.second.timeout.reset();
248 votes.second.upVotes.clear();
250 else if (votes.second.timeout != newTimeout)
253 votes.second.timeout < newTimeout,
254 "xrpl::TrustedVotes::recordVotes : votes not "
258 JLOG(j.
debug()) <<
"recordVotes: Using " << age.count()
259 <<
"min old cached votes from " << pkHuman;
275 validatorVotes.second.timeout || validatorVotes.second.upVotes.empty(),
276 "xrpl::TrustedVotes::getVotes : valid votes");
277 if (validatorVotes.second.timeout)
279 for (
UInt256 const& amendment : validatorVotes.second.upVotes)
284 return {available, ret};
341 auto [trustedCount, newVotes] = trustedVotes.
getVotes(rules,
lock);
356 auto const& it =
votes_.find(amendment);
372 auto const& it =
votes_.find(amendment);
533 bool const featureVotesExist = [
this]() {
534 auto db =
db_.checkoutDb();
539 for (
auto const& [name, amendment, votebehavior] : supported)
545 switch (votebehavior)
560 JLOG(
j_.debug()) <<
"Amendment " << amendment <<
" (" << s.
name
561 <<
") is supported and will be "
563 <<
" voted by default if not enabled on the ledger.";
570 if (featureVotesExist)
572 JLOG(
j_.warn()) <<
"[amendments] section in config file ignored"
573 " in favor of data in db/wallet.db.";
578 detectConflict.
insert(a.first);
585 if (featureVotesExist)
587 JLOG(
j_.warn()) <<
"[veto_amendments] section in config file ignored"
588 " in favor of data in db/wallet.db.";
593 if (!detectConflict.
contains(a.first))
599 JLOG(
j_.warn()) <<
"[veto_amendments] section in config has amendment " <<
'('
600 << a.first <<
", " << a.second
601 <<
") both [veto_amendments] and [amendments].";
606 auto db =
db_.checkoutDb();
609 [&](boost::optional<std::string> amendmentHash,
610 boost::optional<std::string> amendmentName,
611 boost::optional<AmendmentVote> vote) {
613 if (!amendmentHash || !amendmentName || !vote)
618 if (!amendHash.
parseHex(*amendmentHash))
621 std::format(
"Invalid amendment ID '{}' in wallet.db", *amendmentHash));
626 if (
auto s =
get(amendHash,
lock))
628 JLOG(
j_.info()) <<
"Amendment {" << *amendmentName <<
", " << amendHash
629 <<
"} is downvoted.";
630 if (!amendmentName->empty())
631 s->name = *amendmentName;
641 JLOG(
j_.debug()) <<
"Amendment {" << *amendmentName <<
", " << amendHash
643 if (!amendmentName->empty())
644 s.
name = *amendmentName;
686 if (name == e.second.name)
701 "xrpl::AmendmentTableImpl::persistVote : valid vote input");
702 auto db =
db_.checkoutDb();
745 JLOG(
j_.error()) <<
"Unsupported amendment " << amendment <<
" activated.";
757 return (s !=
nullptr) && s->
enabled;
797 amendments.push_back(e.first);
798 JLOG(
j_.info()) <<
"Voting for amendment " << e.second.name;
803 if (!amendments.empty())
825 << enabledAmendments.
size() <<
", " << majorityAmendments.
size() <<
", "
835 JLOG(
j_.debug()) <<
"Counted votes from " << vote->trustedValidations()
836 <<
" valid trusted validations, threshold is: " << vote->threshold();
845 if (enabledAmendments.
contains(entry.first))
847 JLOG(
j_.trace()) << entry.first <<
": amendment already enabled";
852 bool const hasValMajority = vote->passes(entry.first);
855 auto const it = majorityAmendments.
find(entry.first);
856 if (it != majorityAmendments.
end())
861 bool const hasLedgerMajority = majorityTime.
has_value();
863 auto const logStr = [&entry, &vote]() {
865 ss << entry.first <<
" (" << entry.second.name <<
") has " << vote->votes(entry.first)
870 if (hasValMajority && !hasLedgerMajority && entry.second.vote ==
AmendmentVote::Up)
874 JLOG(
j_.debug()) << logStr <<
": amendment got majority";
875 actions[entry.first] = tfGotMajority;
877 else if (!hasValMajority && hasLedgerMajority)
880 JLOG(
j_.debug()) << logStr <<
": amendment lost majority";
881 actions[entry.first] = tfLostMajority;
884 hasLedgerMajority && ((*majorityTime +
majorityTime_) <= closeTime) &&
888 JLOG(
j_.debug()) << logStr <<
": amendment majority held";
889 actions[entry.first] = 0;
892 else if (hasValMajority && hasLedgerMajority)
894 JLOG(
j_.debug()) << logStr <<
": amendment holding majority, waiting to be enabled";
896 else if (!hasValMajority)
898 JLOG(
j_.debug()) << logStr <<
": amendment does not have majority";
924 for (
auto& e : enabled)
936 for (
auto const& [
hash,
time] : majority)
945 JLOG(
j_.info()) <<
"Unsupported amendment " <<
hash <<
" reached majority at "
971 v[jss::name] = fs.
name;
978 v[jss::vetoed] =
"Obsolete";
989 auto const votesTotal =
lastVote_->trustedValidations();
990 auto const votesNeeded =
lastVote_->threshold();
991 auto const votesFor =
lastVote_->votes(
id);
993 v[jss::count] = votesFor;
994 v[jss::validations] = votesTotal;
996 if (votesNeeded != 0)
997 v[jss::threshold] = votesNeeded;
1048 registry, majorityTime, supported, enabled, vetoed, journal);
A generic endpoint for log messages.
int trustedValidations() const
AmendmentSet(Rules const &rules, TrustedVotes const &trustedVotes, std::scoped_lock< std::mutex > const &lock)
bool passes(UInt256 const &amendment) const
HashMap< UInt256, int > votes_
int votes(UInt256 const &amendment) const
std::vector< UInt256 > doValidation(std::set< UInt256 > const &enabledAmendments) const override
void persistVote(UInt256 const &amendment, std::string const &name, AmendmentVote vote) const
std::optional< NetClock::time_point > firstUnsupportedExpected() const override
std::optional< NetClock::time_point > firstUnsupportedExpected_
bool enable(UInt256 const &amendment) override
bool veto(UInt256 const &amendment) override
void doValidatedLedger(LedgerIndex seq, std::set< UInt256 > const &enabled, MajorityAmendmentsT const &majority) override
json::Value getJson(bool isAdmin) const override
TrustedVotes previousTrustedVotes_
void injectJson(json::Value &v, UInt256 const &amendment, AmendmentState const &state, bool isAdmin, std::scoped_lock< std::mutex > const &lock) const
AmendmentState & add(UInt256 const &amendment, std::scoped_lock< std::mutex > const &lock)
bool hasUnsupportedEnabled() const override
returns true if one or more amendments on the network have been enabled that this server does not sup...
std::uint32_t lastUpdateSeq_
bool isEnabled(UInt256 const &amendment) const override
void trustChanged(HashSet< PublicKey > const &allTrusted) override
bool isSupported(UInt256 const &amendment) const override
AmendmentTableImpl(ServiceRegistry ®istry, std::chrono::seconds majorityTime, std::vector< FeatureInfo > const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
HashMap< UInt256, AmendmentState > amendmentMap_
std::unique_ptr< AmendmentSet > lastVote_
std::chrono::seconds const majorityTime_
UInt256 find(std::string const &name) const override
std::map< UInt256, std::uint32_t > doVoting(Rules const &rules, NetClock::time_point closeTime, std::set< UInt256 > const &enabledAmendments, MajorityAmendmentsT const &majorityAmendments, std::vector< std::shared_ptr< STValidation > > const &validations) override
std::vector< UInt256 > getDesired() const override
AmendmentState * get(UInt256 const &amendment, std::scoped_lock< std::mutex > const &lock)
bool needValidatedLedger(LedgerIndex seq) const override
Called to determine whether the amendment logic needs to process a new validated ledger.
bool unVeto(UInt256 const &amendment) override
The amendment table stores the list of enabled and potential amendments.
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
std::chrono::time_point< NetClock > time_point
std::chrono::duration< rep, period > duration
Rules controlling protocol behavior.
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.
Service registry for dependency injection.
TrustedVotes records the most recent votes from trusted validators.
TrustedVotes & operator=(TrustedVotes const &rhs)=delete
void trustChanged(HashSet< PublicKey > const &allTrusted, std::scoped_lock< std::mutex > const &lock)
HashMap< PublicKey, UpvotesAndTimeout > recordedVotes_
void recordVotes(Rules const &rules, std::vector< std::shared_ptr< STValidation > > const &valSet, NetClock::time_point const closeTime, beast::Journal j, std::scoped_lock< std::mutex > const &lock)
std::pair< int, HashMap< UInt256, int > > getVotes(Rules const &rules, std::scoped_lock< std::mutex > const &lock) const
TrustedVotes(TrustedVotes const &rhs)=delete
T duration_cast(T... args)
T emplace_back(T... args)
@ Object
object value (collection of name/value pairs).
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::unique_ptr< AmendmentTable > makeAmendmentTable(ServiceRegistry ®istry, std::chrono::seconds majorityTime, std::vector< AmendmentTable::FeatureInfo > const &supported, Section const &enabled, Section const &vetoed, beast::Journal journal)
std::uint32_t LedgerIndex
A ledger index.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
void readAmendments(soci::session &session, std::function< void(boost::optional< std::string > amendmentHash, boost::optional< std::string > amendmentName, boost::optional< AmendmentVote > vote)> const &callback)
readAmendments Reads all amendments from the FeatureVotes table.
constexpr std::ratio< 80, 100 > kAmendmentMajorityCalcThreshold
The minimum amount of support an amendment should have.
std::string to_string(BaseUInt< Bits, Tag > const &a)
void voteAmendment(soci::session &session, UInt256 const &amendment, std::string const &name, AmendmentVote vote)
voteAmendment Set the veto value for a particular amendment.
static std::vector< std::pair< UInt256, std::string > > parseSection(Section const §ion)
bool isAdmin(Port const &port, json::Value const ¶ms, beast::ip::Address const &remoteIp)
std::map< UInt256, NetClock::time_point > MajorityAmendmentsT
std::unordered_set< Value, Hash, Pred, Allocator > HashSet
bool createFeatureVotes(soci::session &session)
createFeatureVotes Creates the FeatureVote table if it does not exist.
std::unordered_map< Key, Value, Hash, Pred, Allocator > HashMap
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Current state of an amendment.
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.
bool supported
Indicates an amendment that this server has code support for.
bool enabled
Indicates that the amendment has been enabled.
std::vector< UInt256 > upVotes
std::optional< NetClock::time_point > timeout
An unseated timeout indicates that either.
T time_since_epoch(T... args)