-
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
Limit amendment flapping (fixes #4350) #4364
Changes from all commits
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 |
---|---|---|
|
@@ -98,11 +98,17 @@ class AmendmentSet | |
private: | ||
// How many yes votes each amendment received | ||
hash_map<uint256, int> votes_; | ||
|
||
Rules const& rules_; | ||
|
||
// number of trusted validations | ||
int trustedValidations_ = 0; | ||
// number of votes needed | ||
int threshold_ = 0; | ||
|
||
// minimum number of votes needed to begin activation countdown | ||
int activationThreshold_ = 0; | ||
|
||
// minimum number of votes needed to continue activate countdown | ||
int deactivationThreshold_ = 0; | ||
|
||
public: | ||
AmendmentSet( | ||
|
@@ -128,23 +134,25 @@ class AmendmentSet | |
} | ||
} | ||
|
||
threshold_ = !rules_.enabled(fixAmendmentMajorityCalc) | ||
activationThreshold_ = !rules_.enabled(fixAmendmentMajorityCalc) | ||
? std::max( | ||
1L, | ||
static_cast<long>( | ||
1, | ||
static_cast<int>( | ||
(trustedValidations_ * | ||
preFixAmendmentMajorityCalcThreshold.num) / | ||
preFixAmendmentMajorityCalcThreshold.den)) | ||
: std::max( | ||
1L, | ||
static_cast<long>( | ||
1, | ||
static_cast<int>( | ||
(trustedValidations_ * | ||
postFixAmendmentMajorityCalcThreshold.num) / | ||
postFixAmendmentMajorityCalcThreshold.den)); | ||
|
||
deactivationThreshold_ = activationThreshold_; | ||
} | ||
|
||
bool | ||
passes(uint256 const& amendment) const | ||
passes(uint256 const& amendment, int threshold) const | ||
{ | ||
auto const& it = votes_.find(amendment); | ||
|
||
|
@@ -158,9 +166,9 @@ class AmendmentSet | |
// to gain majority. | ||
if (!rules_.enabled(fixAmendmentMajorityCalc) || | ||
trustedValidations_ == 1) | ||
return it->second >= threshold_; | ||
return it->second >= threshold; | ||
|
||
return it->second > threshold_; | ||
return it->second > threshold; | ||
} | ||
|
||
int | ||
|
@@ -181,9 +189,15 @@ class AmendmentSet | |
} | ||
|
||
int | ||
threshold() const | ||
activationThreshold() const | ||
{ | ||
return threshold_; | ||
return activationThreshold_; | ||
} | ||
|
||
int | ||
deactivationThreshold() const | ||
{ | ||
return deactivationThreshold_; | ||
} | ||
}; | ||
|
||
|
@@ -619,8 +633,9 @@ AmendmentTableImpl::doVoting( | |
auto vote = std::make_unique<AmendmentSet>(rules, valSet); | ||
|
||
JLOG(j_.debug()) << "Received " << vote->trustedValidations() | ||
<< " trusted validations, threshold is: " | ||
<< vote->threshold(); | ||
<< " trusted validations. Thresholds are: " | ||
<< vote->activationThreshold() << " and " | ||
<< vote->deactivationThreshold(); | ||
|
||
// Map of amendments to the action to be taken for each one. The action is | ||
// the value of the flags in the pseudo-transaction | ||
|
@@ -633,7 +648,8 @@ AmendmentTableImpl::doVoting( | |
{ | ||
NetClock::time_point majorityTime = {}; | ||
|
||
bool const hasValMajority = vote->passes(entry.first); | ||
bool const hasValMajority = | ||
vote->passes(entry.first, vote->activationThreshold()); | ||
|
||
{ | ||
auto const it = majorityAmendments.find(entry.first); | ||
|
@@ -653,7 +669,9 @@ AmendmentTableImpl::doVoting( | |
JLOG(j_.debug()) << entry.first << ": amendment got majority"; | ||
actions[entry.first] = tfGotMajority; | ||
} | ||
else if (!hasValMajority && (majorityTime != NetClock::time_point{})) | ||
else if ( | ||
!hasValMajority && (majorityTime != NetClock::time_point{}) && | ||
!vote->passes(entry.first, vote->deactivationThreshold())) | ||
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. I think this is right and should have the same behavior as the existing code, but someone else should triple-check the logic when the amendment being checked is unknown. Here's the logic: 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. If the amendment is unknown, then how would |
||
{ | ||
// Ledger says majority, validators say no | ||
JLOG(j_.debug()) << entry.first << ": amendment lost majority"; | ||
|
@@ -740,7 +758,7 @@ AmendmentTableImpl::injectJson( | |
if (!fs.enabled && lastVote_) | ||
{ | ||
auto const votesTotal = lastVote_->trustedValidations(); | ||
auto const votesNeeded = lastVote_->threshold(); | ||
auto const votesNeeded = lastVote_->activationThreshold(); | ||
auto const votesFor = lastVote_->votes(id); | ||
|
||
v[jss::count] = votesFor; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -207,7 +207,7 @@ Change::activateTrustLinesToSelfFix() | |
TER | ||
Change::applyAmendment() | ||
{ | ||
uint256 amendment(ctx_.tx.getFieldH256(sfAmendment)); | ||
uint256 const amendment(ctx_.tx.getFieldH256(sfAmendment)); | ||
|
||
auto const k = keylet::amendments(); | ||
|
||
|
@@ -233,41 +233,67 @@ Change::applyAmendment() | |
if (gotMajority && lostMajority) | ||
return temINVALID_FLAG; | ||
|
||
STArray newMajorities(sfMajorities); | ||
STArray majorities = amendmentObject->getFieldArray(sfMajorities); | ||
|
||
bool found = false; | ||
if (amendmentObject->isFieldPresent(sfMajorities)) | ||
auto it = std::find_if( | ||
majorities.begin(), majorities.end(), [&amendment](STObject const& o) { | ||
return o[sfAmendment] == amendment; | ||
}); | ||
|
||
if (it == majorities.end() && lostMajority) | ||
return tefALREADY; | ||
|
||
if (it != majorities.end()) | ||
{ | ||
const STArray& oldMajorities = | ||
amendmentObject->getFieldArray(sfMajorities); | ||
for (auto const& majority : oldMajorities) | ||
if (gotMajority) | ||
return tefALREADY; | ||
|
||
majorities.erase(it); | ||
} | ||
Comment on lines
+238
to
+252
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. The first time an amendment loses majority, the program will reach line 251, remove the amendment from the There does not seem to be a new test for this new behavior. Have you added one? |
||
|
||
if (view().rules().enabled(fixAmendmentFlapping)) | ||
{ | ||
STVector256 flappingAmendments = | ||
amendmentObject->getFieldV256(sfFlappingAmendments); | ||
|
||
auto it = std::find( | ||
flappingAmendments.begin(), flappingAmendments.end(), amendment); | ||
|
||
// When amendment loses a majority and is not already on the list we | ||
// add it and give it second chance. | ||
if (lostMajority && it == flappingAmendments.end()) | ||
{ | ||
if (majority.getFieldH256(sfAmendment) == amendment) | ||
{ | ||
if (gotMajority) | ||
return tefALREADY; | ||
found = true; | ||
} | ||
flappingAmendments.push_back(amendment); | ||
amendmentObject->setFieldV256( | ||
sfFlappingAmendments, flappingAmendments); | ||
view().update(amendmentObject); | ||
return tesSUCCESS; | ||
} | ||
|
||
// Remove the amendment now because the tracking is | ||
// not needed any more. | ||
if (it != flappingAmendments.end()) | ||
{ | ||
flappingAmendments.erase(it); | ||
|
||
if (flappingAmendments.empty()) | ||
amendmentObject->makeFieldAbsent(sfFlappingAmendments); | ||
else | ||
{ | ||
// pass through | ||
newMajorities.push_back(majority); | ||
} | ||
amendmentObject->setFieldV256( | ||
sfFlappingAmendments, flappingAmendments); | ||
} | ||
} | ||
|
||
if (!found && lostMajority) | ||
return tefALREADY; | ||
|
||
if (gotMajority) | ||
{ | ||
// This amendment now has a majority | ||
newMajorities.push_back(STObject(sfMajority)); | ||
auto& entry = newMajorities.back(); | ||
entry.emplace_back(STUInt256(sfAmendment, amendment)); | ||
entry.emplace_back(STUInt32( | ||
STObject maj(sfMajority); | ||
maj.emplace_back(STUInt256(sfAmendment, amendment)); | ||
maj.emplace_back(STUInt32( | ||
sfCloseTime, view().parentCloseTime().time_since_epoch().count())); | ||
|
||
majorities.push_back(std::move(maj)); | ||
|
||
if (!ctx_.app.getAmendmentTable().isSupported(amendment)) | ||
{ | ||
JLOG(j_.warn()) << "Unsupported amendment " << amendment | ||
|
@@ -293,10 +319,10 @@ Change::applyAmendment() | |
} | ||
} | ||
|
||
if (newMajorities.empty()) | ||
if (majorities.empty()) | ||
amendmentObject->makeFieldAbsent(sfMajorities); | ||
else | ||
amendmentObject->setFieldArray(sfMajorities, newMajorities); | ||
amendmentObject->setFieldArray(sfMajorities, majorities); | ||
|
||
view().update(amendmentObject); | ||
|
||
|
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.