diff --git a/Consensus_8cpp_source.html b/Consensus_8cpp_source.html index a0cf5b55f0..1d82340853 100644 --- a/Consensus_8cpp_source.html +++ b/Consensus_8cpp_source.html @@ -217,11 +217,11 @@ $(function() {
139 return false;
140 }
141
-
142 // We only get stalled when every disputed transaction unequivocally has 80%
-
143 // (minConsensusPct) agreement, either for or against. That is: either under
-
144 // 20% or over 80% consensus (repectively "nay" or "yay"). This prevents
-
145 // manipulation by a minority of byzantine peers of which transactions make
-
146 // the cut to get into the ledger.
+
142 // We only get stalled when there are disputed transactions and all of them
+
143 // unequivocally have 80% (minConsensusPct) agreement, either for or
+
144 // against. That is: either under 20% or over 80% consensus (repectively
+
145 // "nay" or "yay"). This prevents manipulation by a minority of byzantine
+
146 // peers of which transactions make the cut to get into the ledger.
147 if (stalled)
148 {
149 CLOG(clog) << "consensus stalled. ";
diff --git a/Consensus_8h_source.html b/Consensus_8h_source.html index 3ac5b13376..6299369a45 100644 --- a/Consensus_8h_source.html +++ b/Consensus_8h_source.html @@ -1406,197 +1406,211 @@ $(function() {
1712 << ", disagree=" << disagree;
1713
1714 ConsensusParms const& parms = adaptor_.parms();
-
1715 // Stalling is BAD
-
1716 bool const stalled = haveCloseTimeConsensus_ &&
-
1717 std::ranges::all_of(result_->disputes,
-
1718 [this, &parms](auto const& dispute) {
-
1719 return dispute.second.stalled(
-
1720 parms,
-
1721 mode_.get() == ConsensusMode::proposing,
-
1722 peerUnchangedCounter_);
-
1723 });
-
1724
-
1725 // Determine if we actually have consensus or not
-
1726 result_->state = checkConsensus(
-
1727 prevProposers_,
-
1728 agree + disagree,
-
1729 agree,
-
1730 currentFinished,
-
1731 prevRoundTime_,
-
1732 result_->roundTime.read(),
-
1733 stalled,
-
1734 parms,
-
1735 mode_.get() == ConsensusMode::proposing,
-
1736 j_,
-
1737 clog);
+
1715 // Stalling is BAD. It means that we have a consensus on the close time, so
+
1716 // peers are talking, but we have disputed transactions that peers are
+
1717 // unable or unwilling to come to agreement on one way or the other.
+
1718 bool const stalled = haveCloseTimeConsensus_ &&
+
1719 !result_->disputes.empty() &&
+
1720 std::ranges::all_of(result_->disputes,
+
1721 [this, &parms, &clog](auto const& dispute) {
+
1722 return dispute.second.stalled(
+
1723 parms,
+
1724 mode_.get() == ConsensusMode::proposing,
+
1725 peerUnchangedCounter_,
+
1726 j_,
+
1727 clog);
+
1728 });
+
1729 if (stalled)
+
1730 {
+
1731 std::stringstream ss;
+
1732 ss << "Consensus detects as stalled with " << (agree + disagree) << "/"
+
1733 << prevProposers_ << " proposers, and " << result_->disputes.size()
+
1734 << " stalled disputed transactions.";
+
1735 JLOG(j_.error()) << ss.str();
+
1736 CLOG(clog) << ss.str();
+
1737 }
1738
-
1739 if (result_->state == ConsensusState::No)
-
1740 {
-
1741 CLOG(clog) << "No consensus. ";
-
1742 return false;
-
1743 }
-
1744
-
1745 // Consensus has taken far too long. Drop out of the round.
-
1746 if (result_->state == ConsensusState::Expired)
-
1747 {
-
1748 static auto const minimumCounter =
-
1749 parms.avalancheCutoffs.size() * parms.avMIN_ROUNDS;
-
1750 std::stringstream ss;
-
1751 if (establishCounter_ < minimumCounter)
-
1752 {
-
1753 // If each round of phaseEstablish takes a very long time, we may
-
1754 // "expire" before we've given consensus enough time at each
-
1755 // avalanche level to actually come to a consensus. In that case,
-
1756 // keep trying. This should only happen if there are an extremely
-
1757 // large number of disputes such that each round takes an inordinate
-
1758 // amount of time.
-
1759
-
1760 ss << "Consensus time has expired in round " << establishCounter_
-
1761 << "; continue until round " << minimumCounter << ". "
-
1762 << Json::Compact{getJson(false)};
-
1763 JLOG(j_.error()) << ss.str();
-
1764 CLOG(clog) << ss.str() << ". ";
-
1765 return false;
-
1766 }
-
1767 ss << "Consensus expired. " << Json::Compact{getJson(true)};
-
1768 JLOG(j_.error()) << ss.str();
-
1769 CLOG(clog) << ss.str() << ". ";
-
1770 leaveConsensus(clog);
-
1771 }
-
1772 // There is consensus, but we need to track if the network moved on
-
1773 // without us.
-
1774 if (result_->state == ConsensusState::MovedOn)
-
1775 {
-
1776 JLOG(j_.error()) << "Unable to reach consensus";
-
1777 JLOG(j_.error()) << Json::Compact{getJson(true)};
-
1778 CLOG(clog) << "Unable to reach consensus "
-
1779 << Json::Compact{getJson(true)} << ". ";
-
1780 }
-
1781
-
1782 CLOG(clog) << "Consensus has been reached. ";
-
1783 return true;
-
1784}
-
1785
-
1786template <class Adaptor>
-
1787void
-
1788Consensus<Adaptor>::leaveConsensus(
-
1789 std::unique_ptr<std::stringstream> const& clog)
-
1790{
-
1791 if (mode_.get() == ConsensusMode::proposing)
-
1792 {
-
1793 if (result_ && !result_->position.isBowOut())
-
1794 {
-
1795 result_->position.bowOut(now_);
-
1796 adaptor_.propose(result_->position);
-
1797 }
-
1798
-
1799 mode_.set(ConsensusMode::observing, adaptor_);
-
1800 JLOG(j_.info()) << "Bowing out of consensus";
-
1801 CLOG(clog) << "Bowing out of consensus. ";
-
1802 }
-
1803}
-
1804
-
1805template <class Adaptor>
-
1806void
-
1807Consensus<Adaptor>::createDisputes(
-
1808 TxSet_t const& o,
-
1809 std::unique_ptr<std::stringstream> const& clog)
-
1810{
-
1811 // Cannot create disputes without our stance
-
1812 XRPL_ASSERT(result_, "ripple::Consensus::createDisputes : result is set");
-
1813
-
1814 // Only create disputes if this is a new set
-
1815 auto const emplaced = result_->compares.emplace(o.id()).second;
-
1816 CLOG(clog) << "createDisputes: new set? " << !emplaced << ". ";
-
1817 if (!emplaced)
-
1818 return;
-
1819
-
1820 // Nothing to dispute if we agree
-
1821 if (result_->txns.id() == o.id())
-
1822 {
-
1823 CLOG(clog) << "both sets are identical. ";
-
1824 return;
-
1825 }
-
1826
-
1827 CLOG(clog) << "comparing existing with new set: " << result_->txns.id()
-
1828 << ',' << o.id() << ". ";
-
1829 JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to "
-
1830 << o.id();
-
1831
-
1832 auto differences = result_->txns.compare(o);
+
1739 // Determine if we actually have consensus or not
+
1740 result_->state = checkConsensus(
+
1741 prevProposers_,
+
1742 agree + disagree,
+
1743 agree,
+
1744 currentFinished,
+
1745 prevRoundTime_,
+
1746 result_->roundTime.read(),
+
1747 stalled,
+
1748 parms,
+
1749 mode_.get() == ConsensusMode::proposing,
+
1750 j_,
+
1751 clog);
+
1752
+
1753 if (result_->state == ConsensusState::No)
+
1754 {
+
1755 CLOG(clog) << "No consensus. ";
+
1756 return false;
+
1757 }
+
1758
+
1759 // Consensus has taken far too long. Drop out of the round.
+
1760 if (result_->state == ConsensusState::Expired)
+
1761 {
+
1762 static auto const minimumCounter =
+
1763 parms.avalancheCutoffs.size() * parms.avMIN_ROUNDS;
+
1764 std::stringstream ss;
+
1765 if (establishCounter_ < minimumCounter)
+
1766 {
+
1767 // If each round of phaseEstablish takes a very long time, we may
+
1768 // "expire" before we've given consensus enough time at each
+
1769 // avalanche level to actually come to a consensus. In that case,
+
1770 // keep trying. This should only happen if there are an extremely
+
1771 // large number of disputes such that each round takes an inordinate
+
1772 // amount of time.
+
1773
+
1774 ss << "Consensus time has expired in round " << establishCounter_
+
1775 << "; continue until round " << minimumCounter << ". "
+
1776 << Json::Compact{getJson(false)};
+
1777 JLOG(j_.error()) << ss.str();
+
1778 CLOG(clog) << ss.str() << ". ";
+
1779 return false;
+
1780 }
+
1781 ss << "Consensus expired. " << Json::Compact{getJson(true)};
+
1782 JLOG(j_.error()) << ss.str();
+
1783 CLOG(clog) << ss.str() << ". ";
+
1784 leaveConsensus(clog);
+
1785 }
+
1786 // There is consensus, but we need to track if the network moved on
+
1787 // without us.
+
1788 if (result_->state == ConsensusState::MovedOn)
+
1789 {
+
1790 JLOG(j_.error()) << "Unable to reach consensus";
+
1791 JLOG(j_.error()) << Json::Compact{getJson(true)};
+
1792 CLOG(clog) << "Unable to reach consensus "
+
1793 << Json::Compact{getJson(true)} << ". ";
+
1794 }
+
1795
+
1796 CLOG(clog) << "Consensus has been reached. ";
+
1797 return true;
+
1798}
+
1799
+
1800template <class Adaptor>
+
1801void
+
1802Consensus<Adaptor>::leaveConsensus(
+
1803 std::unique_ptr<std::stringstream> const& clog)
+
1804{
+
1805 if (mode_.get() == ConsensusMode::proposing)
+
1806 {
+
1807 if (result_ && !result_->position.isBowOut())
+
1808 {
+
1809 result_->position.bowOut(now_);
+
1810 adaptor_.propose(result_->position);
+
1811 }
+
1812
+
1813 mode_.set(ConsensusMode::observing, adaptor_);
+
1814 JLOG(j_.info()) << "Bowing out of consensus";
+
1815 CLOG(clog) << "Bowing out of consensus. ";
+
1816 }
+
1817}
+
1818
+
1819template <class Adaptor>
+
1820void
+
1821Consensus<Adaptor>::createDisputes(
+
1822 TxSet_t const& o,
+
1823 std::unique_ptr<std::stringstream> const& clog)
+
1824{
+
1825 // Cannot create disputes without our stance
+
1826 XRPL_ASSERT(result_, "ripple::Consensus::createDisputes : result is set");
+
1827
+
1828 // Only create disputes if this is a new set
+
1829 auto const emplaced = result_->compares.emplace(o.id()).second;
+
1830 CLOG(clog) << "createDisputes: new set? " << !emplaced << ". ";
+
1831 if (!emplaced)
+
1832 return;
1833
-
1834 int dc = 0;
-
1835
-
1836 for (auto const& [txId, inThisSet] : differences)
-
1837 {
-
1838 ++dc;
-
1839 // create disputed transactions (from the ledger that has them)
-
1840 XRPL_ASSERT(
-
1841 (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
-
1842 (!inThisSet && !result_->txns.find(txId) && o.find(txId)),
-
1843 "ripple::Consensus::createDisputes : has disputed transactions");
-
1844
-
1845 Tx_t tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
-
1846 auto txID = tx.id();
+
1834 // Nothing to dispute if we agree
+
1835 if (result_->txns.id() == o.id())
+
1836 {
+
1837 CLOG(clog) << "both sets are identical. ";
+
1838 return;
+
1839 }
+
1840
+
1841 CLOG(clog) << "comparing existing with new set: " << result_->txns.id()
+
1842 << ',' << o.id() << ". ";
+
1843 JLOG(j_.debug()) << "createDisputes " << result_->txns.id() << " to "
+
1844 << o.id();
+
1845
+
1846 auto differences = result_->txns.compare(o);
1847
-
1848 if (result_->disputes.find(txID) != result_->disputes.end())
-
1849 continue;
-
1850
-
1851 JLOG(j_.debug()) << "Transaction " << txID << " is disputed";
-
1852
-
1853 typename Result::Dispute_t dtx{
-
1854 tx,
-
1855 result_->txns.exists(txID),
-
1856 std::max(prevProposers_, currPeerPositions_.size()),
-
1857 j_};
+
1848 int dc = 0;
+
1849
+
1850 for (auto const& [txId, inThisSet] : differences)
+
1851 {
+
1852 ++dc;
+
1853 // create disputed transactions (from the ledger that has them)
+
1854 XRPL_ASSERT(
+
1855 (inThisSet && result_->txns.find(txId) && !o.find(txId)) ||
+
1856 (!inThisSet && !result_->txns.find(txId) && o.find(txId)),
+
1857 "ripple::Consensus::createDisputes : has disputed transactions");
1858
-
1859 // Update all of the available peer's votes on the disputed transaction
-
1860 for (auto const& [nodeId, peerPos] : currPeerPositions_)
-
1861 {
-
1862 Proposal_t const& peerProp = peerPos.proposal();
-
1863 auto const cit = acquired_.find(peerProp.position());
-
1864 if (cit != acquired_.end() &&
-
1865 dtx.setVote(nodeId, cit->second.exists(txID)))
-
1866 peerUnchangedCounter_ = 0;
-
1867 }
-
1868 adaptor_.share(dtx.tx());
-
1869
-
1870 result_->disputes.emplace(txID, std::move(dtx));
-
1871 }
-
1872 JLOG(j_.debug()) << dc << " differences found";
-
1873 CLOG(clog) << "disputes: " << dc << ". ";
-
1874}
-
1875
-
1876template <class Adaptor>
-
1877void
-
1878Consensus<Adaptor>::updateDisputes(NodeID_t const& node, TxSet_t const& other)
-
1879{
-
1880 // Cannot updateDisputes without our stance
-
1881 XRPL_ASSERT(result_, "ripple::Consensus::updateDisputes : result is set");
-
1882
-
1883 // Ensure we have created disputes against this set if we haven't seen
-
1884 // it before
-
1885 if (result_->compares.find(other.id()) == result_->compares.end())
-
1886 createDisputes(other);
-
1887
-
1888 for (auto& it : result_->disputes)
-
1889 {
-
1890 auto& d = it.second;
-
1891 if (d.setVote(node, other.exists(d.tx().id())))
-
1892 peerUnchangedCounter_ = 0;
-
1893 }
-
1894}
-
1895
-
1896template <class Adaptor>
-
1897NetClock::time_point
-
1898Consensus<Adaptor>::asCloseTime(NetClock::time_point raw) const
-
1899{
-
1900 return roundCloseTime(raw, closeResolution_);
-
1901}
-
1902
-
1903} // namespace ripple
-
1904
-
1905#endif
+
1859 Tx_t tx = inThisSet ? result_->txns.find(txId) : o.find(txId);
+
1860 auto txID = tx.id();
+
1861
+
1862 if (result_->disputes.find(txID) != result_->disputes.end())
+
1863 continue;
+
1864
+
1865 JLOG(j_.debug()) << "Transaction " << txID << " is disputed";
+
1866
+
1867 typename Result::Dispute_t dtx{
+
1868 tx,
+
1869 result_->txns.exists(txID),
+
1870 std::max(prevProposers_, currPeerPositions_.size()),
+
1871 j_};
+
1872
+
1873 // Update all of the available peer's votes on the disputed transaction
+
1874 for (auto const& [nodeId, peerPos] : currPeerPositions_)
+
1875 {
+
1876 Proposal_t const& peerProp = peerPos.proposal();
+
1877 auto const cit = acquired_.find(peerProp.position());
+
1878 if (cit != acquired_.end() &&
+
1879 dtx.setVote(nodeId, cit->second.exists(txID)))
+
1880 peerUnchangedCounter_ = 0;
+
1881 }
+
1882 adaptor_.share(dtx.tx());
+
1883
+
1884 result_->disputes.emplace(txID, std::move(dtx));
+
1885 }
+
1886 JLOG(j_.debug()) << dc << " differences found";
+
1887 CLOG(clog) << "disputes: " << dc << ". ";
+
1888}
+
1889
+
1890template <class Adaptor>
+
1891void
+
1892Consensus<Adaptor>::updateDisputes(NodeID_t const& node, TxSet_t const& other)
+
1893{
+
1894 // Cannot updateDisputes without our stance
+
1895 XRPL_ASSERT(result_, "ripple::Consensus::updateDisputes : result is set");
+
1896
+
1897 // Ensure we have created disputes against this set if we haven't seen
+
1898 // it before
+
1899 if (result_->compares.find(other.id()) == result_->compares.end())
+
1900 createDisputes(other);
+
1901
+
1902 for (auto& it : result_->disputes)
+
1903 {
+
1904 auto& d = it.second;
+
1905 if (d.setVote(node, other.exists(d.tx().id())))
+
1906 peerUnchangedCounter_ = 0;
+
1907 }
+
1908}
+
1909
+
1910template <class Adaptor>
+
1911NetClock::time_point
+
1912Consensus<Adaptor>::asCloseTime(NetClock::time_point raw) const
+
1913{
+
1914 return roundCloseTime(raw, closeResolution_);
+
1915}
+
1916
+
1917} // namespace ripple
+
1918
+
1919#endif
algorithm
std::ranges::all_of
T all_of(T... args)
std::stringstream
@@ -1636,8 +1650,8 @@ $(function() {
ripple::Consensus::phase_
ConsensusPhase phase_
Definition: Consensus.h:567
ripple::Consensus::prevCloseTime_
NetClock::time_point prevCloseTime_
Definition: Consensus.h:595
ripple::Consensus::clock_
clock_type const & clock_
Definition: Consensus.h:572
-
ripple::Consensus::leaveConsensus
void leaveConsensus(std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:1788
-
ripple::Consensus::updateDisputes
void updateDisputes(NodeID_t const &node, TxSet_t const &other)
Definition: Consensus.h:1878
+
ripple::Consensus::leaveConsensus
void leaveConsensus(std::unique_ptr< std::stringstream > const &clog)
Definition: Consensus.h:1802
+
ripple::Consensus::updateDisputes
void updateDisputes(NodeID_t const &node, TxSet_t const &other)
Definition: Consensus.h:1892
ripple::Consensus::previousLedger_
Ledger_t previousLedger_
Definition: Consensus.h:603
ripple::Consensus::TxSet_t
typename Adaptor::TxSet_t TxSet_t
Definition: Consensus.h:300
ripple::Consensus::closeTimeAvalancheState_
ConsensusParms::AvalancheState closeTimeAvalancheState_
Definition: Consensus.h:583
@@ -1656,9 +1670,9 @@ $(function() {
ripple::Consensus::Consensus
Consensus(Consensus &&) noexcept=default
ripple::Consensus::now_
NetClock::time_point now_
Definition: Consensus.h:594
ripple::Consensus::prevProposers_
std::size_t prevProposers_
Definition: Consensus.h:629
-
ripple::Consensus::asCloseTime
NetClock::time_point asCloseTime(NetClock::time_point raw) const
Definition: Consensus.h:1898
+
ripple::Consensus::asCloseTime
NetClock::time_point asCloseTime(NetClock::time_point raw) const
Definition: Consensus.h:1912
ripple::Consensus::j_
beast::Journal const j_
Definition: Consensus.h:635
-
ripple::Consensus::createDisputes
void createDisputes(TxSet_t const &o, std::unique_ptr< std::stringstream > const &clog={})
Definition: Consensus.h:1807
+
ripple::Consensus::createDisputes
void createDisputes(TxSet_t const &o, std::unique_ptr< std::stringstream > const &clog={})
Definition: Consensus.h:1821
ripple::Consensus::gotTxSet
void gotTxSet(NetClock::time_point const &now, TxSet_t const &txSet)
Process a transaction set acquired from the network.
Definition: Consensus.h:892
ripple::Consensus::adaptor_
Adaptor & adaptor_
Definition: Consensus.h:565
ripple::Consensus::Ledger_t
typename Adaptor::Ledger_t Ledger_t
Definition: Consensus.h:299
diff --git a/Consensus__test_8cpp_source.html b/Consensus__test_8cpp_source.html index 9056a592ad..4610632baf 100644 --- a/Consensus__test_8cpp_source.html +++ b/Consensus__test_8cpp_source.html @@ -1212,307 +1212,410 @@ $(function() {
1136 ConsensusParms p;
1137 std::size_t peersUnchanged = 0;
1138
-
1139 // Three cases:
-
1140 // 1 proposing, initial vote yes
-
1141 // 2 proposing, initial vote no
-
1142 // 3 not proposing, initial vote doesn't matter after the first update,
-
1143 // use yes
-
1144 {
-
1145 Dispute proposingTrue{txTrue.id(), true, numPeers, journal_};
-
1146 Dispute proposingFalse{txFalse.id(), false, numPeers, journal_};
-
1147 Dispute followingTrue{
-
1148 txFollowingTrue.id(), true, numPeers, journal_};
-
1149 Dispute followingFalse{
-
1150 txFollowingFalse.id(), false, numPeers, journal_};
-
1151 BEAST_EXPECT(proposingTrue.ID() == 99);
-
1152 BEAST_EXPECT(proposingFalse.ID() == 98);
-
1153 BEAST_EXPECT(followingTrue.ID() == 97);
-
1154 BEAST_EXPECT(followingFalse.ID() == 96);
-
1155
-
1156 // Create an even split in the peer votes
-
1157 for (int i = 0; i < numPeers; ++i)
-
1158 {
-
1159 BEAST_EXPECT(proposingTrue.setVote(PeerID(i), i < 50));
-
1160 BEAST_EXPECT(proposingFalse.setVote(PeerID(i), i < 50));
-
1161 BEAST_EXPECT(followingTrue.setVote(PeerID(i), i < 50));
-
1162 BEAST_EXPECT(followingFalse.setVote(PeerID(i), i < 50));
-
1163 }
-
1164 // Switch the middle vote to match mine
-
1165 BEAST_EXPECT(proposingTrue.setVote(PeerID(50), true));
-
1166 BEAST_EXPECT(proposingFalse.setVote(PeerID(49), false));
-
1167 BEAST_EXPECT(followingTrue.setVote(PeerID(50), true));
-
1168 BEAST_EXPECT(followingFalse.setVote(PeerID(49), false));
-
1169
-
1170 // no changes yet
-
1171 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1172 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1173 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1174 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1175 BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
-
1176 BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
-
1177 BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
-
1178 BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
-
1179
-
1180 // I'm in the majority, my vote should not change
-
1181 BEAST_EXPECT(!proposingTrue.updateVote(5, true, p));
-
1182 BEAST_EXPECT(!proposingFalse.updateVote(5, true, p));
-
1183 BEAST_EXPECT(!followingTrue.updateVote(5, false, p));
-
1184 BEAST_EXPECT(!followingFalse.updateVote(5, false, p));
-
1185
-
1186 BEAST_EXPECT(!proposingTrue.updateVote(10, true, p));
-
1187 BEAST_EXPECT(!proposingFalse.updateVote(10, true, p));
-
1188 BEAST_EXPECT(!followingTrue.updateVote(10, false, p));
-
1189 BEAST_EXPECT(!followingFalse.updateVote(10, false, p));
-
1190
-
1191 peersUnchanged = 2;
-
1192 BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
-
1193 BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
-
1194 BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
-
1195 BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
-
1196
-
1197 // Right now, the vote is 51%. The requirement is about to jump to
-
1198 // 65%
-
1199 BEAST_EXPECT(proposingTrue.updateVote(55, true, p));
-
1200 BEAST_EXPECT(!proposingFalse.updateVote(55, true, p));
-
1201 BEAST_EXPECT(!followingTrue.updateVote(55, false, p));
-
1202 BEAST_EXPECT(!followingFalse.updateVote(55, false, p));
-
1203
-
1204 BEAST_EXPECT(proposingTrue.getOurVote() == false);
-
1205 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1206 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1207 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1208 // 16 validators change their vote to match my original vote
-
1209 for (int i = 0; i < 16; ++i)
-
1210 {
-
1211 auto pTrue = PeerID(numPeers - i - 1);
-
1212 auto pFalse = PeerID(i);
-
1213 BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
-
1214 BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
-
1215 BEAST_EXPECT(followingTrue.setVote(pTrue, true));
-
1216 BEAST_EXPECT(followingFalse.setVote(pFalse, false));
-
1217 }
-
1218 // The vote should now be 66%, threshold is 65%
-
1219 BEAST_EXPECT(proposingTrue.updateVote(60, true, p));
-
1220 BEAST_EXPECT(!proposingFalse.updateVote(60, true, p));
-
1221 BEAST_EXPECT(!followingTrue.updateVote(60, false, p));
-
1222 BEAST_EXPECT(!followingFalse.updateVote(60, false, p));
-
1223
-
1224 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1225 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1226 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1227 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1228
-
1229 // Threshold jumps to 70%
-
1230 BEAST_EXPECT(proposingTrue.updateVote(86, true, p));
-
1231 BEAST_EXPECT(!proposingFalse.updateVote(86, true, p));
-
1232 BEAST_EXPECT(!followingTrue.updateVote(86, false, p));
-
1233 BEAST_EXPECT(!followingFalse.updateVote(86, false, p));
-
1234
-
1235 BEAST_EXPECT(proposingTrue.getOurVote() == false);
-
1236 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1237 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1238 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1239
-
1240 // 5 more validators change their vote to match my original vote
-
1241 for (int i = 16; i < 21; ++i)
-
1242 {
-
1243 auto pTrue = PeerID(numPeers - i - 1);
-
1244 auto pFalse = PeerID(i);
-
1245 BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
-
1246 BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
-
1247 BEAST_EXPECT(followingTrue.setVote(pTrue, true));
-
1248 BEAST_EXPECT(followingFalse.setVote(pFalse, false));
-
1249 }
-
1250
-
1251 // The vote should now be 71%, threshold is 70%
-
1252 BEAST_EXPECT(proposingTrue.updateVote(90, true, p));
-
1253 BEAST_EXPECT(!proposingFalse.updateVote(90, true, p));
-
1254 BEAST_EXPECT(!followingTrue.updateVote(90, false, p));
-
1255 BEAST_EXPECT(!followingFalse.updateVote(90, false, p));
-
1256
-
1257 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1258 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1259 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1260 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1261
-
1262 // The vote should now be 71%, threshold is 70%
-
1263 BEAST_EXPECT(!proposingTrue.updateVote(150, true, p));
-
1264 BEAST_EXPECT(!proposingFalse.updateVote(150, true, p));
-
1265 BEAST_EXPECT(!followingTrue.updateVote(150, false, p));
-
1266 BEAST_EXPECT(!followingFalse.updateVote(150, false, p));
-
1267
-
1268 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1269 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1270 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1271 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1272
-
1273 // The vote should now be 71%, threshold is 70%
-
1274 BEAST_EXPECT(!proposingTrue.updateVote(190, true, p));
-
1275 BEAST_EXPECT(!proposingFalse.updateVote(190, true, p));
-
1276 BEAST_EXPECT(!followingTrue.updateVote(190, false, p));
-
1277 BEAST_EXPECT(!followingFalse.updateVote(190, false, p));
-
1278
-
1279 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1280 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1281 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1282 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1283
-
1284 peersUnchanged = 3;
-
1285 BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
-
1286 BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
-
1287 BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
-
1288 BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
-
1289
-
1290 // Threshold jumps to 95%
-
1291 BEAST_EXPECT(proposingTrue.updateVote(220, true, p));
-
1292 BEAST_EXPECT(!proposingFalse.updateVote(220, true, p));
-
1293 BEAST_EXPECT(!followingTrue.updateVote(220, false, p));
-
1294 BEAST_EXPECT(!followingFalse.updateVote(220, false, p));
-
1295
-
1296 BEAST_EXPECT(proposingTrue.getOurVote() == false);
-
1297 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1298 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1299 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1300
-
1301 // 25 more validators change their vote to match my original vote
-
1302 for (int i = 21; i < 46; ++i)
-
1303 {
-
1304 auto pTrue = PeerID(numPeers - i - 1);
-
1305 auto pFalse = PeerID(i);
-
1306 BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
-
1307 BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
-
1308 BEAST_EXPECT(followingTrue.setVote(pTrue, true));
-
1309 BEAST_EXPECT(followingFalse.setVote(pFalse, false));
-
1310 }
-
1311
-
1312 // The vote should now be 96%, threshold is 95%
-
1313 BEAST_EXPECT(proposingTrue.updateVote(250, true, p));
-
1314 BEAST_EXPECT(!proposingFalse.updateVote(250, true, p));
-
1315 BEAST_EXPECT(!followingTrue.updateVote(250, false, p));
-
1316 BEAST_EXPECT(!followingFalse.updateVote(250, false, p));
-
1317
-
1318 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1319 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1320 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1321 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1322
-
1323 for (peersUnchanged = 0; peersUnchanged < 6; ++peersUnchanged)
-
1324 {
-
1325 BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
-
1326 BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
-
1327 BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
-
1328 BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
+
1139 auto logs = std::make_unique<Logs>(beast::severities::kError);
+
1140 auto j = logs->journal("Test");
+
1141 auto clog = std::make_unique<std::stringstream>();
+
1142
+
1143 // Three cases:
+
1144 // 1 proposing, initial vote yes
+
1145 // 2 proposing, initial vote no
+
1146 // 3 not proposing, initial vote doesn't matter after the first update,
+
1147 // use yes
+
1148 {
+
1149 Dispute proposingTrue{txTrue.id(), true, numPeers, journal_};
+
1150 Dispute proposingFalse{txFalse.id(), false, numPeers, journal_};
+
1151 Dispute followingTrue{
+
1152 txFollowingTrue.id(), true, numPeers, journal_};
+
1153 Dispute followingFalse{
+
1154 txFollowingFalse.id(), false, numPeers, journal_};
+
1155 BEAST_EXPECT(proposingTrue.ID() == 99);
+
1156 BEAST_EXPECT(proposingFalse.ID() == 98);
+
1157 BEAST_EXPECT(followingTrue.ID() == 97);
+
1158 BEAST_EXPECT(followingFalse.ID() == 96);
+
1159
+
1160 // Create an even split in the peer votes
+
1161 for (int i = 0; i < numPeers; ++i)
+
1162 {
+
1163 BEAST_EXPECT(proposingTrue.setVote(PeerID(i), i < 50));
+
1164 BEAST_EXPECT(proposingFalse.setVote(PeerID(i), i < 50));
+
1165 BEAST_EXPECT(followingTrue.setVote(PeerID(i), i < 50));
+
1166 BEAST_EXPECT(followingFalse.setVote(PeerID(i), i < 50));
+
1167 }
+
1168 // Switch the middle vote to match mine
+
1169 BEAST_EXPECT(proposingTrue.setVote(PeerID(50), true));
+
1170 BEAST_EXPECT(proposingFalse.setVote(PeerID(49), false));
+
1171 BEAST_EXPECT(followingTrue.setVote(PeerID(50), true));
+
1172 BEAST_EXPECT(followingFalse.setVote(PeerID(49), false));
+
1173
+
1174 // no changes yet
+
1175 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1176 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1177 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1178 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1179 BEAST_EXPECT(
+
1180 !proposingTrue.stalled(p, true, peersUnchanged, j, clog));
+
1181 BEAST_EXPECT(
+
1182 !proposingFalse.stalled(p, true, peersUnchanged, j, clog));
+
1183 BEAST_EXPECT(
+
1184 !followingTrue.stalled(p, false, peersUnchanged, j, clog));
+
1185 BEAST_EXPECT(
+
1186 !followingFalse.stalled(p, false, peersUnchanged, j, clog));
+
1187 BEAST_EXPECT(clog->str() == "");
+
1188
+
1189 // I'm in the majority, my vote should not change
+
1190 BEAST_EXPECT(!proposingTrue.updateVote(5, true, p));
+
1191 BEAST_EXPECT(!proposingFalse.updateVote(5, true, p));
+
1192 BEAST_EXPECT(!followingTrue.updateVote(5, false, p));
+
1193 BEAST_EXPECT(!followingFalse.updateVote(5, false, p));
+
1194
+
1195 BEAST_EXPECT(!proposingTrue.updateVote(10, true, p));
+
1196 BEAST_EXPECT(!proposingFalse.updateVote(10, true, p));
+
1197 BEAST_EXPECT(!followingTrue.updateVote(10, false, p));
+
1198 BEAST_EXPECT(!followingFalse.updateVote(10, false, p));
+
1199
+
1200 peersUnchanged = 2;
+
1201 BEAST_EXPECT(
+
1202 !proposingTrue.stalled(p, true, peersUnchanged, j, clog));
+
1203 BEAST_EXPECT(
+
1204 !proposingFalse.stalled(p, true, peersUnchanged, j, clog));
+
1205 BEAST_EXPECT(
+
1206 !followingTrue.stalled(p, false, peersUnchanged, j, clog));
+
1207 BEAST_EXPECT(
+
1208 !followingFalse.stalled(p, false, peersUnchanged, j, clog));
+
1209 BEAST_EXPECT(clog->str() == "");
+
1210
+
1211 // Right now, the vote is 51%. The requirement is about to jump to
+
1212 // 65%
+
1213 BEAST_EXPECT(proposingTrue.updateVote(55, true, p));
+
1214 BEAST_EXPECT(!proposingFalse.updateVote(55, true, p));
+
1215 BEAST_EXPECT(!followingTrue.updateVote(55, false, p));
+
1216 BEAST_EXPECT(!followingFalse.updateVote(55, false, p));
+
1217
+
1218 BEAST_EXPECT(proposingTrue.getOurVote() == false);
+
1219 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1220 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1221 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1222 // 16 validators change their vote to match my original vote
+
1223 for (int i = 0; i < 16; ++i)
+
1224 {
+
1225 auto pTrue = PeerID(numPeers - i - 1);
+
1226 auto pFalse = PeerID(i);
+
1227 BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
+
1228 BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
+
1229 BEAST_EXPECT(followingTrue.setVote(pTrue, true));
+
1230 BEAST_EXPECT(followingFalse.setVote(pFalse, false));
+
1231 }
+
1232 // The vote should now be 66%, threshold is 65%
+
1233 BEAST_EXPECT(proposingTrue.updateVote(60, true, p));
+
1234 BEAST_EXPECT(!proposingFalse.updateVote(60, true, p));
+
1235 BEAST_EXPECT(!followingTrue.updateVote(60, false, p));
+
1236 BEAST_EXPECT(!followingFalse.updateVote(60, false, p));
+
1237
+
1238 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1239 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1240 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1241 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1242
+
1243 // Threshold jumps to 70%
+
1244 BEAST_EXPECT(proposingTrue.updateVote(86, true, p));
+
1245 BEAST_EXPECT(!proposingFalse.updateVote(86, true, p));
+
1246 BEAST_EXPECT(!followingTrue.updateVote(86, false, p));
+
1247 BEAST_EXPECT(!followingFalse.updateVote(86, false, p));
+
1248
+
1249 BEAST_EXPECT(proposingTrue.getOurVote() == false);
+
1250 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1251 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1252 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1253
+
1254 // 5 more validators change their vote to match my original vote
+
1255 for (int i = 16; i < 21; ++i)
+
1256 {
+
1257 auto pTrue = PeerID(numPeers - i - 1);
+
1258 auto pFalse = PeerID(i);
+
1259 BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
+
1260 BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
+
1261 BEAST_EXPECT(followingTrue.setVote(pTrue, true));
+
1262 BEAST_EXPECT(followingFalse.setVote(pFalse, false));
+
1263 }
+
1264
+
1265 // The vote should now be 71%, threshold is 70%
+
1266 BEAST_EXPECT(proposingTrue.updateVote(90, true, p));
+
1267 BEAST_EXPECT(!proposingFalse.updateVote(90, true, p));
+
1268 BEAST_EXPECT(!followingTrue.updateVote(90, false, p));
+
1269 BEAST_EXPECT(!followingFalse.updateVote(90, false, p));
+
1270
+
1271 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1272 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1273 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1274 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1275
+
1276 // The vote should now be 71%, threshold is 70%
+
1277 BEAST_EXPECT(!proposingTrue.updateVote(150, true, p));
+
1278 BEAST_EXPECT(!proposingFalse.updateVote(150, true, p));
+
1279 BEAST_EXPECT(!followingTrue.updateVote(150, false, p));
+
1280 BEAST_EXPECT(!followingFalse.updateVote(150, false, p));
+
1281
+
1282 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1283 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1284 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1285 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1286
+
1287 // The vote should now be 71%, threshold is 70%
+
1288 BEAST_EXPECT(!proposingTrue.updateVote(190, true, p));
+
1289 BEAST_EXPECT(!proposingFalse.updateVote(190, true, p));
+
1290 BEAST_EXPECT(!followingTrue.updateVote(190, false, p));
+
1291 BEAST_EXPECT(!followingFalse.updateVote(190, false, p));
+
1292
+
1293 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1294 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1295 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1296 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1297
+
1298 peersUnchanged = 3;
+
1299 BEAST_EXPECT(
+
1300 !proposingTrue.stalled(p, true, peersUnchanged, j, clog));
+
1301 BEAST_EXPECT(
+
1302 !proposingFalse.stalled(p, true, peersUnchanged, j, clog));
+
1303 BEAST_EXPECT(
+
1304 !followingTrue.stalled(p, false, peersUnchanged, j, clog));
+
1305 BEAST_EXPECT(
+
1306 !followingFalse.stalled(p, false, peersUnchanged, j, clog));
+
1307 BEAST_EXPECT(clog->str() == "");
+
1308
+
1309 // Threshold jumps to 95%
+
1310 BEAST_EXPECT(proposingTrue.updateVote(220, true, p));
+
1311 BEAST_EXPECT(!proposingFalse.updateVote(220, true, p));
+
1312 BEAST_EXPECT(!followingTrue.updateVote(220, false, p));
+
1313 BEAST_EXPECT(!followingFalse.updateVote(220, false, p));
+
1314
+
1315 BEAST_EXPECT(proposingTrue.getOurVote() == false);
+
1316 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1317 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1318 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1319
+
1320 // 25 more validators change their vote to match my original vote
+
1321 for (int i = 21; i < 46; ++i)
+
1322 {
+
1323 auto pTrue = PeerID(numPeers - i - 1);
+
1324 auto pFalse = PeerID(i);
+
1325 BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
+
1326 BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
+
1327 BEAST_EXPECT(followingTrue.setVote(pTrue, true));
+
1328 BEAST_EXPECT(followingFalse.setVote(pFalse, false));
1329 }
1330
-
1331 for (int i = 0; i < 1; ++i)
-
1332 {
-
1333 BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
-
1334 BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
-
1335 BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
-
1336 BEAST_EXPECT(
-
1337 !followingFalse.updateVote(250 + 10 * i, false, p));
-
1338
-
1339 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1340 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1341 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1342 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1343
-
1344 // true vote has changed recently, so not stalled
-
1345 BEAST_EXPECT(!proposingTrue.stalled(p, true, 0));
-
1346 // remaining votes have been unchanged in so long that we only
-
1347 // need to hit the second round at 95% to be stalled, regardless
-
1348 // of peers
-
1349 BEAST_EXPECT(proposingFalse.stalled(p, true, 0));
-
1350 BEAST_EXPECT(followingTrue.stalled(p, false, 0));
-
1351 BEAST_EXPECT(followingFalse.stalled(p, false, 0));
-
1352
-
1353 // true vote has changed recently, so not stalled
-
1354 BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
-
1355 // remaining votes have been unchanged in so long that we only
-
1356 // need to hit the second round at 95% to be stalled, regardless
-
1357 // of peers
-
1358 BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged));
-
1359 BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged));
-
1360 BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged));
-
1361 }
-
1362 for (int i = 1; i < 3; ++i)
-
1363 {
-
1364 BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
-
1365 BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
-
1366 BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
-
1367 BEAST_EXPECT(
-
1368 !followingFalse.updateVote(250 + 10 * i, false, p));
-
1369
-
1370 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1371 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1372 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1373 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1374
-
1375 // true vote changed 2 rounds ago, and peers are changing, so
-
1376 // not stalled
-
1377 BEAST_EXPECT(!proposingTrue.stalled(p, true, 0));
-
1378 // still stalled
-
1379 BEAST_EXPECT(proposingFalse.stalled(p, true, 0));
-
1380 BEAST_EXPECT(followingTrue.stalled(p, false, 0));
-
1381 BEAST_EXPECT(followingFalse.stalled(p, false, 0));
-
1382
-
1383 // true vote changed 2 rounds ago, and peers are NOT changing,
-
1384 // so stalled
-
1385 BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged));
-
1386 // still stalled
-
1387 BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged));
-
1388 BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged));
-
1389 BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged));
-
1390 }
-
1391 for (int i = 3; i < 5; ++i)
-
1392 {
-
1393 BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
-
1394 BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
-
1395 BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
-
1396 BEAST_EXPECT(
-
1397 !followingFalse.updateVote(250 + 10 * i, false, p));
-
1398
-
1399 BEAST_EXPECT(proposingTrue.getOurVote() == true);
-
1400 BEAST_EXPECT(proposingFalse.getOurVote() == false);
-
1401 BEAST_EXPECT(followingTrue.getOurVote() == true);
-
1402 BEAST_EXPECT(followingFalse.getOurVote() == false);
-
1403
-
1404 BEAST_EXPECT(proposingTrue.stalled(p, true, 0));
-
1405 BEAST_EXPECT(proposingFalse.stalled(p, true, 0));
-
1406 BEAST_EXPECT(followingTrue.stalled(p, false, 0));
-
1407 BEAST_EXPECT(followingFalse.stalled(p, false, 0));
-
1408
-
1409 BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged));
-
1410 BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged));
-
1411 BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged));
-
1412 BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged));
-
1413 }
-
1414 }
-
1415 }
-
1416
-
1417 void
-
1418 run() override
-
1419 {
-
1420 testShouldCloseLedger();
-
1421 testCheckConsensus();
-
1422
-
1423 testStandalone();
-
1424 testPeersAgree();
-
1425 testSlowPeers();
-
1426 testCloseTimeDisagree();
-
1427 testWrongLCL();
-
1428 testConsensusCloseTimeRounding();
-
1429 testFork();
-
1430 testHubNetwork();
-
1431 testPreferredByBranch();
-
1432 testPauseForLaggards();
-
1433 testDisputes();
-
1434 }
-
1435};
-
1436
-
1437BEAST_DEFINE_TESTSUITE(Consensus, consensus, ripple);
-
1438} // namespace test
-
1439} // namespace ripple
+
1331 // The vote should now be 96%, threshold is 95%
+
1332 BEAST_EXPECT(proposingTrue.updateVote(250, true, p));
+
1333 BEAST_EXPECT(!proposingFalse.updateVote(250, true, p));
+
1334 BEAST_EXPECT(!followingTrue.updateVote(250, false, p));
+
1335 BEAST_EXPECT(!followingFalse.updateVote(250, false, p));
+
1336
+
1337 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1338 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1339 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1340 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1341
+
1342 for (peersUnchanged = 0; peersUnchanged < 6; ++peersUnchanged)
+
1343 {
+
1344 BEAST_EXPECT(
+
1345 !proposingTrue.stalled(p, true, peersUnchanged, j, clog));
+
1346 BEAST_EXPECT(
+
1347 !proposingFalse.stalled(p, true, peersUnchanged, j, clog));
+
1348 BEAST_EXPECT(
+
1349 !followingTrue.stalled(p, false, peersUnchanged, j, clog));
+
1350 BEAST_EXPECT(
+
1351 !followingFalse.stalled(p, false, peersUnchanged, j, clog));
+
1352 BEAST_EXPECT(clog->str() == "");
+
1353 }
+
1354
+
1355 auto expectStalled = [this, &clog](
+
1356 int txid,
+
1357 bool ourVote,
+
1358 int ourTime,
+
1359 int peerTime,
+
1360 int support,
+
1361 std::uint32_t line) {
+
1362 using namespace std::string_literals;
+
1363
+
1364 auto const s = clog->str();
+
1365 expect(s.find("stalled"), s, __FILE__, line);
+
1366 expect(
+
1367 s.starts_with("Transaction "s + std::to_string(txid)),
+
1368 s,
+
1369 __FILE__,
+
1370 line);
+
1371 expect(
+
1372 s.find("voting "s + (ourVote ? "YES" : "NO")) != s.npos,
+
1373 s,
+
1374 __FILE__,
+
1375 line);
+
1376 expect(
+
1377 s.find("for "s + std::to_string(ourTime) + " rounds."s) !=
+
1378 s.npos,
+
1379 s,
+
1380 __FILE__,
+
1381 line);
+
1382 expect(
+
1383 s.find(
+
1384 "votes in "s + std::to_string(peerTime) + " rounds.") !=
+
1385 s.npos,
+
1386 s,
+
1387 __FILE__,
+
1388 line);
+
1389 expect(
+
1390 s.ends_with(
+
1391 "has "s + std::to_string(support) + "% support. "s),
+
1392 s,
+
1393 __FILE__,
+
1394 line);
+
1395 clog = std::make_unique<std::stringstream>();
+
1396 };
+
1397
+
1398 for (int i = 0; i < 1; ++i)
+
1399 {
+
1400 BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
+
1401 BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
+
1402 BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
+
1403 BEAST_EXPECT(
+
1404 !followingFalse.updateVote(250 + 10 * i, false, p));
+
1405
+
1406 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1407 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1408 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1409 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1410
+
1411 // true vote has changed recently, so not stalled
+
1412 BEAST_EXPECT(!proposingTrue.stalled(p, true, 0, j, clog));
+
1413 BEAST_EXPECT(clog->str() == "");
+
1414 // remaining votes have been unchanged in so long that we only
+
1415 // need to hit the second round at 95% to be stalled, regardless
+
1416 // of peers
+
1417 BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog));
+
1418 expectStalled(98, false, 11, 0, 2, __LINE__);
+
1419 BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog));
+
1420 expectStalled(97, true, 11, 0, 97, __LINE__);
+
1421 BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog));
+
1422 expectStalled(96, false, 11, 0, 3, __LINE__);
+
1423
+
1424 // true vote has changed recently, so not stalled
+
1425 BEAST_EXPECT(
+
1426 !proposingTrue.stalled(p, true, peersUnchanged, j, clog));
+
1427 BEAST_EXPECTS(clog->str() == "", clog->str());
+
1428 // remaining votes have been unchanged in so long that we only
+
1429 // need to hit the second round at 95% to be stalled, regardless
+
1430 // of peers
+
1431 BEAST_EXPECT(
+
1432 proposingFalse.stalled(p, true, peersUnchanged, j, clog));
+
1433 expectStalled(98, false, 11, 6, 2, __LINE__);
+
1434 BEAST_EXPECT(
+
1435 followingTrue.stalled(p, false, peersUnchanged, j, clog));
+
1436 expectStalled(97, true, 11, 6, 97, __LINE__);
+
1437 BEAST_EXPECT(
+
1438 followingFalse.stalled(p, false, peersUnchanged, j, clog));
+
1439 expectStalled(96, false, 11, 6, 3, __LINE__);
+
1440 }
+
1441 for (int i = 1; i < 3; ++i)
+
1442 {
+
1443 BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
+
1444 BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
+
1445 BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
+
1446 BEAST_EXPECT(
+
1447 !followingFalse.updateVote(250 + 10 * i, false, p));
+
1448
+
1449 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1450 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1451 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1452 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1453
+
1454 // true vote changed 2 rounds ago, and peers are changing, so
+
1455 // not stalled
+
1456 BEAST_EXPECT(!proposingTrue.stalled(p, true, 0, j, clog));
+
1457 BEAST_EXPECTS(clog->str() == "", clog->str());
+
1458 // still stalled
+
1459 BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog));
+
1460 expectStalled(98, false, 11 + i, 0, 2, __LINE__);
+
1461 BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog));
+
1462 expectStalled(97, true, 11 + i, 0, 97, __LINE__);
+
1463 BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog));
+
1464 expectStalled(96, false, 11 + i, 0, 3, __LINE__);
+
1465
+
1466 // true vote changed 2 rounds ago, and peers are NOT changing,
+
1467 // so stalled
+
1468 BEAST_EXPECT(
+
1469 proposingTrue.stalled(p, true, peersUnchanged, j, clog));
+
1470 expectStalled(99, true, 1 + i, 6, 97, __LINE__);
+
1471 // still stalled
+
1472 BEAST_EXPECT(
+
1473 proposingFalse.stalled(p, true, peersUnchanged, j, clog));
+
1474 expectStalled(98, false, 11 + i, 6, 2, __LINE__);
+
1475 BEAST_EXPECT(
+
1476 followingTrue.stalled(p, false, peersUnchanged, j, clog));
+
1477 expectStalled(97, true, 11 + i, 6, 97, __LINE__);
+
1478 BEAST_EXPECT(
+
1479 followingFalse.stalled(p, false, peersUnchanged, j, clog));
+
1480 expectStalled(96, false, 11 + i, 6, 3, __LINE__);
+
1481 }
+
1482 for (int i = 3; i < 5; ++i)
+
1483 {
+
1484 BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
+
1485 BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
+
1486 BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
+
1487 BEAST_EXPECT(
+
1488 !followingFalse.updateVote(250 + 10 * i, false, p));
+
1489
+
1490 BEAST_EXPECT(proposingTrue.getOurVote() == true);
+
1491 BEAST_EXPECT(proposingFalse.getOurVote() == false);
+
1492 BEAST_EXPECT(followingTrue.getOurVote() == true);
+
1493 BEAST_EXPECT(followingFalse.getOurVote() == false);
+
1494
+
1495 BEAST_EXPECT(proposingTrue.stalled(p, true, 0, j, clog));
+
1496 expectStalled(99, true, 1 + i, 0, 97, __LINE__);
+
1497 BEAST_EXPECT(proposingFalse.stalled(p, true, 0, j, clog));
+
1498 expectStalled(98, false, 11 + i, 0, 2, __LINE__);
+
1499 BEAST_EXPECT(followingTrue.stalled(p, false, 0, j, clog));
+
1500 expectStalled(97, true, 11 + i, 0, 97, __LINE__);
+
1501 BEAST_EXPECT(followingFalse.stalled(p, false, 0, j, clog));
+
1502 expectStalled(96, false, 11 + i, 0, 3, __LINE__);
+
1503
+
1504 BEAST_EXPECT(
+
1505 proposingTrue.stalled(p, true, peersUnchanged, j, clog));
+
1506 expectStalled(99, true, 1 + i, 6, 97, __LINE__);
+
1507 BEAST_EXPECT(
+
1508 proposingFalse.stalled(p, true, peersUnchanged, j, clog));
+
1509 expectStalled(98, false, 11 + i, 6, 2, __LINE__);
+
1510 BEAST_EXPECT(
+
1511 followingTrue.stalled(p, false, peersUnchanged, j, clog));
+
1512 expectStalled(97, true, 11 + i, 6, 97, __LINE__);
+
1513 BEAST_EXPECT(
+
1514 followingFalse.stalled(p, false, peersUnchanged, j, clog));
+
1515 expectStalled(96, false, 11 + i, 6, 3, __LINE__);
+
1516 }
+
1517 }
+
1518 }
+
1519
+
1520 void
+
1521 run() override
+
1522 {
+
1523 testShouldCloseLedger();
+
1524 testCheckConsensus();
+
1525
+
1526 testStandalone();
+
1527 testPeersAgree();
+
1528 testSlowPeers();
+
1529 testCloseTimeDisagree();
+
1530 testWrongLCL();
+
1531 testConsensusCloseTimeRounding();
+
1532 testFork();
+
1533 testHubNetwork();
+
1534 testPreferredByBranch();
+
1535 testPauseForLaggards();
+
1536 testDisputes();
+
1537 }
+
1538};
+
1539
+
1540BEAST_DEFINE_TESTSUITE(Consensus, consensus, ripple);
+
1541} // namespace test
+
1542} // namespace ripple
beast::unit_test::suite
A testsuite class.
Definition: suite.h:55
beast::unit_test::suite::testcase
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:155
ripple::Consensus
Generic implementation of consensus algorithm.
Definition: Consensus.h:298
@@ -1526,7 +1629,7 @@ $(function() {
ripple::test::Consensus_test::testSlowPeers
void testSlowPeers()
Definition: Consensus_test.cpp:245
ripple::test::Consensus_test::journal_
SuiteJournal journal_
Definition: Consensus_test.cpp:33
ripple::test::Consensus_test::testPauseForLaggards
void testPauseForLaggards()
Definition: Consensus_test.cpp:1031
-
ripple::test::Consensus_test::run
void run() override
Runs the suite.
Definition: Consensus_test.cpp:1418
+
ripple::test::Consensus_test::run
void run() override
Runs the suite.
Definition: Consensus_test.cpp:1521
ripple::test::Consensus_test::testConsensusCloseTimeRounding
void testConsensusCloseTimeRounding()
Definition: Consensus_test.cpp:652
ripple::test::Consensus_test::testPeersAgree
void testPeersAgree()
Definition: Consensus_test.cpp:207
ripple::test::Consensus_test::testHubNetwork
void testHubNetwork()
Definition: Consensus_test.cpp:810
@@ -1542,10 +1645,12 @@ $(function() {
ripple::test::csf::PeerGroup
A group of simulation Peers.
Definition: PeerGroup.h:42
ripple::test::csf::PeerGroup::disconnect
void disconnect(PeerGroup const &o)
Destroy network connection.
Definition: PeerGroup.h:186
ripple::test::csf::PeerGroup::connect
void connect(PeerGroup const &o, SimDuration delay)
Establish network connection.
Definition: PeerGroup.h:166
+
std::clog
std::cout
std::chrono::duration
std::unordered_set::insert
T insert(T... args)
std::uint32_t
+
beast::severities::kError
@ kError
Definition: Journal.h:39
beast::severities::kAll
@ kAll
Definition: Journal.h:33
ripple::test::csf::SimDuration
typename SimClock::duration SimDuration
Definition: SimTime.h:36
ripple::test::csf::SimTime
typename SimClock::time_point SimTime
Definition: SimTime.h:37
@@ -1583,6 +1688,7 @@ $(function() {
ripple::test::csf::FullyValidateLedger::ledger
Ledger ledger
The new fully validated ledger.
Definition: events.h:142
ripple::test::csf::Peer
A single peer in the simulation.
Definition: test/csf/Peer.h:60
std::chrono::time_point
+
std::to_string
T to_string(T... args)
std::unordered_set
std::vector
diff --git a/DisputedTx_8h_source.html b/DisputedTx_8h_source.html index 3a6b52bdba..1eef64b500 100644 --- a/DisputedTx_8h_source.html +++ b/DisputedTx_8h_source.html @@ -138,261 +138,284 @@ $(function() {
83 }
84
87 bool
-
88 stalled(ConsensusParms const& p, bool proposing, int peersUnchanged) const
-
89 {
-
90 // at() can throw, but the map is built by hand to ensure all valid
-
91 // values are available.
-
92 auto const& currentCutoff = p.avalancheCutoffs.at(avalancheState_);
-
93 auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
-
94
-
95 // We're have not reached the final avalanche state, or been there long
-
96 // enough, so there's room for change. Check the times in case the state
-
97 // machine is altered to allow states to loop.
-
98 if (nextCutoff.consensusTime > currentCutoff.consensusTime ||
-
99 avalancheCounter_ < p.avMIN_ROUNDS)
-
100 return false;
-
101
-
102 // We've haven't had this vote for minimum rounds yet. Things could
-
103 // change.
-
104 if (proposing && currentVoteCounter_ < p.avMIN_ROUNDS)
+
88 stalled(
+
89 ConsensusParms const& p,
+
90 bool proposing,
+
91 int peersUnchanged,
+
92 beast::Journal j,
+
93 std::unique_ptr<std::stringstream> const& clog) const
+
94 {
+
95 // at() can throw, but the map is built by hand to ensure all valid
+
96 // values are available.
+
97 auto const& currentCutoff = p.avalancheCutoffs.at(avalancheState_);
+
98 auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
+
99
+
100 // We're have not reached the final avalanche state, or been there long
+
101 // enough, so there's room for change. Check the times in case the state
+
102 // machine is altered to allow states to loop.
+
103 if (nextCutoff.consensusTime > currentCutoff.consensusTime ||
+
104 avalancheCounter_ < p.avMIN_ROUNDS)
105 return false;
106
-
107 // If we or any peers have changed a vote in several rounds, then
-
108 // things could still change. But if _either_ has not changed in that
-
109 // long, we're unlikely to change our vote any time soon. (This prevents
-
110 // a malicious peer from flip-flopping a vote to prevent consensus.)
-
111 if (peersUnchanged < p.avSTALLED_ROUNDS &&
-
112 (proposing && currentVoteCounter_ < p.avSTALLED_ROUNDS))
-
113 return false;
-
114
-
115 // Does this transaction have more than 80% agreement
-
116
-
117 // Compute the percentage of nodes voting 'yes' (possibly including us)
-
118 int const support = (yays_ + (proposing && ourVote_ ? 1 : 0)) * 100;
-
119 int total = nays_ + yays_ + (proposing ? 1 : 0);
-
120 if (!total)
-
121 // There are no votes, so we know nothing
-
122 return false;
-
123 int const weight = support / total;
-
124 // Returns true if the tx has more than minCONSENSUS_PCT (80) percent
-
125 // agreement. Either voting for _or_ voting against the tx.
-
126 return weight > p.minCONSENSUS_PCT ||
-
127 weight < (100 - p.minCONSENSUS_PCT);
-
128 }
-
129
-
131 Tx_t const&
-
132 tx() const
-
133 {
-
134 return tx_;
-
135 }
-
136
-
138 void
-
139 setOurVote(bool o)
-
140 {
-
141 ourVote_ = o;
-
142 }
-
143
-
152 [[nodiscard]] bool
-
153 setVote(NodeID_t const& peer, bool votesYes);
-
154
+
107 // We've haven't had this vote for minimum rounds yet. Things could
+
108 // change.
+
109 if (proposing && currentVoteCounter_ < p.avMIN_ROUNDS)
+
110 return false;
+
111
+
112 // If we or any peers have changed a vote in several rounds, then
+
113 // things could still change. But if _either_ has not changed in that
+
114 // long, we're unlikely to change our vote any time soon. (This prevents
+
115 // a malicious peer from flip-flopping a vote to prevent consensus.)
+
116 if (peersUnchanged < p.avSTALLED_ROUNDS &&
+
117 (proposing && currentVoteCounter_ < p.avSTALLED_ROUNDS))
+
118 return false;
+
119
+
120 // Does this transaction have more than 80% agreement
+
121
+
122 // Compute the percentage of nodes voting 'yes' (possibly including us)
+
123 int const support = (yays_ + (proposing && ourVote_ ? 1 : 0)) * 100;
+
124 int total = nays_ + yays_ + (proposing ? 1 : 0);
+
125 if (!total)
+
126 // There are no votes, so we know nothing
+
127 return false;
+
128 int const weight = support / total;
+
129 // Returns true if the tx has more than minCONSENSUS_PCT (80) percent
+
130 // agreement. Either voting for _or_ voting against the tx.
+
131 bool const stalled =
+
132 weight > p.minCONSENSUS_PCT || weight < (100 - p.minCONSENSUS_PCT);
+
133
+
134 if (stalled)
+
135 {
+
136 // stalling is an error condition for even a single
+
137 // transaction.
+
138 std::stringstream s;
+
139 s << "Transaction " << ID() << " is stalled. We have been voting "
+
140 << (getOurVote() ? "YES" : "NO") << " for " << currentVoteCounter_
+
141 << " rounds. Peers have not changed their votes in "
+
142 << peersUnchanged << " rounds. The transaction has " << weight
+
143 << "% support. ";
+
144 JLOG(j_.error()) << s.str();
+
145 CLOG(clog) << s.str();
+
146 }
+
147
+
148 return stalled;
+
149 }
+
150
+
152 Tx_t const&
+
153 tx() const
+
154 {
+
155 return tx_;
+
156 }
+
157
159 void
-
160 unVote(NodeID_t const& peer);
-
161
-
173 bool
-
174 updateVote(int percentTime, bool proposing, ConsensusParms const& p);
+
160 setOurVote(bool o)
+
161 {
+
162 ourVote_ = o;
+
163 }
+
164
+
173 [[nodiscard]] bool
+
174 setVote(NodeID_t const& peer, bool votesYes);
175
-
177 Json::Value
-
178 getJson() const;
-
179
-
180private:
-
181 int yays_; //< Number of yes votes
-
182 int nays_; //< Number of no votes
-
183 bool ourVote_; //< Our vote (true is yes)
-
184 Tx_t tx_; //< Transaction under dispute
-
185 Map_t votes_; //< Map from NodeID to vote
-
187 std::size_t currentVoteCounter_ = 0;
-
189 ConsensusParms::AvalancheState avalancheState_ = ConsensusParms::init;
-
191 std::size_t avalancheCounter_ = 0;
-
192 beast::Journal const j_;
-
193};
-
194
-
195// Track a peer's yes/no vote on a particular disputed tx_
-
196template <class Tx_t, class NodeID_t>
-
197bool
-
198DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
-
199{
-
200 auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
-
201
-
202 // new vote
-
203 if (inserted)
-
204 {
-
205 if (votesYes)
-
206 {
-
207 JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
-
208 ++yays_;
-
209 }
-
210 else
-
211 {
-
212 JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
-
213 ++nays_;
-
214 }
-
215 return true;
-
216 }
-
217 // changes vote to yes
-
218 else if (votesYes && !it->second)
-
219 {
-
220 JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
-
221 --nays_;
-
222 ++yays_;
-
223 it->second = true;
-
224 return true;
-
225 }
-
226 // changes vote to no
-
227 else if (!votesYes && it->second)
-
228 {
-
229 JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
-
230 ++nays_;
-
231 --yays_;
-
232 it->second = false;
-
233 return true;
-
234 }
-
235 return false;
-
236}
-
237
-
238// Remove a peer's vote on this disputed transaction
-
239template <class Tx_t, class NodeID_t>
-
240void
-
241DisputedTx<Tx_t, NodeID_t>::unVote(NodeID_t const& peer)
-
242{
-
243 auto it = votes_.find(peer);
-
244
-
245 if (it != votes_.end())
-
246 {
-
247 if (it->second)
-
248 --yays_;
-
249 else
-
250 --nays_;
-
251
-
252 votes_.erase(it);
-
253 }
-
254}
-
255
-
256template <class Tx_t, class NodeID_t>
-
257bool
-
258DisputedTx<Tx_t, NodeID_t>::updateVote(
-
259 int percentTime,
-
260 bool proposing,
-
261 ConsensusParms const& p)
-
262{
-
263 if (ourVote_ && (nays_ == 0))
-
264 return false;
+
180 void
+
181 unVote(NodeID_t const& peer);
+
182
+
194 bool
+
195 updateVote(int percentTime, bool proposing, ConsensusParms const& p);
+
196
+
198 Json::Value
+
199 getJson() const;
+
200
+
201private:
+
202 int yays_; //< Number of yes votes
+
203 int nays_; //< Number of no votes
+
204 bool ourVote_; //< Our vote (true is yes)
+
205 Tx_t tx_; //< Transaction under dispute
+
206 Map_t votes_; //< Map from NodeID to vote
+
208 std::size_t currentVoteCounter_ = 0;
+
210 ConsensusParms::AvalancheState avalancheState_ = ConsensusParms::init;
+
212 std::size_t avalancheCounter_ = 0;
+
213 beast::Journal const j_;
+
214};
+
215
+
216// Track a peer's yes/no vote on a particular disputed tx_
+
217template <class Tx_t, class NodeID_t>
+
218bool
+
219DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
+
220{
+
221 auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
+
222
+
223 // new vote
+
224 if (inserted)
+
225 {
+
226 if (votesYes)
+
227 {
+
228 JLOG(j_.debug()) << "Peer " << peer << " votes YES on " << tx_.id();
+
229 ++yays_;
+
230 }
+
231 else
+
232 {
+
233 JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
+
234 ++nays_;
+
235 }
+
236 return true;
+
237 }
+
238 // changes vote to yes
+
239 else if (votesYes && !it->second)
+
240 {
+
241 JLOG(j_.debug()) << "Peer " << peer << " now votes YES on " << tx_.id();
+
242 --nays_;
+
243 ++yays_;
+
244 it->second = true;
+
245 return true;
+
246 }
+
247 // changes vote to no
+
248 else if (!votesYes && it->second)
+
249 {
+
250 JLOG(j_.debug()) << "Peer " << peer << " now votes NO on " << tx_.id();
+
251 ++nays_;
+
252 --yays_;
+
253 it->second = false;
+
254 return true;
+
255 }
+
256 return false;
+
257}
+
258
+
259// Remove a peer's vote on this disputed transaction
+
260template <class Tx_t, class NodeID_t>
+
261void
+
262DisputedTx<Tx_t, NodeID_t>::unVote(NodeID_t const& peer)
+
263{
+
264 auto it = votes_.find(peer);
265
-
266 if (!ourVote_ && (yays_ == 0))
-
267 return false;
-
268
-
269 bool newPosition;
-
270 int weight;
-
271
-
272 // When proposing, to prevent avalanche stalls, we increase the needed
-
273 // weight slightly over time. We also need to ensure that the consensus has
-
274 // made a minimum number of attempts at each "state" before moving
-
275 // to the next.
-
276 // Proposing or not, we need to keep track of which state we've reached so
-
277 // we can determine if the vote has stalled.
-
278 auto const [requiredPct, newState] = getNeededWeight(
-
279 p, avalancheState_, percentTime, ++avalancheCounter_, p.avMIN_ROUNDS);
-
280 if (newState)
-
281 {
-
282 avalancheState_ = *newState;
-
283 avalancheCounter_ = 0;
-
284 }
-
285
-
286 if (proposing) // give ourselves full weight
-
287 {
-
288 // This is basically the percentage of nodes voting 'yes' (including us)
-
289 weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
-
290
-
291 newPosition = weight > requiredPct;
-
292 }
-
293 else
-
294 {
-
295 // don't let us outweigh a proposing node, just recognize consensus
-
296 weight = -1;
-
297 newPosition = yays_ > nays_;
-
298 }
-
299
-
300 if (newPosition == ourVote_)
-
301 {
-
302 ++currentVoteCounter_;
-
303 JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO") << ") on "
-
304 << tx_.id() << " : weight " << weight << ", percent "
-
305 << percentTime
-
306 << ", round(s) with this vote: " << currentVoteCounter_;
-
307 JLOG(j_.debug()) << Json::Compact{getJson()};
-
308 return false;
-
309 }
-
310
-
311 currentVoteCounter_ = 0;
-
312 ourVote_ = newPosition;
-
313 JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
-
314 << tx_.id();
-
315 JLOG(j_.debug()) << Json::Compact{getJson()};
-
316 return true;
-
317}
-
318
-
319template <class Tx_t, class NodeID_t>
-
320Json::Value
-
321DisputedTx<Tx_t, NodeID_t>::getJson() const
-
322{
-
323 using std::to_string;
-
324
-
325 Json::Value ret(Json::objectValue);
-
326
-
327 ret["yays"] = yays_;
-
328 ret["nays"] = nays_;
-
329 ret["our_vote"] = ourVote_;
-
330
-
331 if (!votes_.empty())
-
332 {
-
333 Json::Value votesj(Json::objectValue);
-
334 for (auto const& [nodeId, vote] : votes_)
-
335 votesj[to_string(nodeId)] = vote;
-
336 ret["votes"] = std::move(votesj);
-
337 }
-
338
-
339 return ret;
-
340}
-
341
-
342} // namespace ripple
-
343
-
344#endif
+
266 if (it != votes_.end())
+
267 {
+
268 if (it->second)
+
269 --yays_;
+
270 else
+
271 --nays_;
+
272
+
273 votes_.erase(it);
+
274 }
+
275}
+
276
+
277template <class Tx_t, class NodeID_t>
+
278bool
+
279DisputedTx<Tx_t, NodeID_t>::updateVote(
+
280 int percentTime,
+
281 bool proposing,
+
282 ConsensusParms const& p)
+
283{
+
284 if (ourVote_ && (nays_ == 0))
+
285 return false;
+
286
+
287 if (!ourVote_ && (yays_ == 0))
+
288 return false;
+
289
+
290 bool newPosition;
+
291 int weight;
+
292
+
293 // When proposing, to prevent avalanche stalls, we increase the needed
+
294 // weight slightly over time. We also need to ensure that the consensus has
+
295 // made a minimum number of attempts at each "state" before moving
+
296 // to the next.
+
297 // Proposing or not, we need to keep track of which state we've reached so
+
298 // we can determine if the vote has stalled.
+
299 auto const [requiredPct, newState] = getNeededWeight(
+
300 p, avalancheState_, percentTime, ++avalancheCounter_, p.avMIN_ROUNDS);
+
301 if (newState)
+
302 {
+
303 avalancheState_ = *newState;
+
304 avalancheCounter_ = 0;
+
305 }
+
306
+
307 if (proposing) // give ourselves full weight
+
308 {
+
309 // This is basically the percentage of nodes voting 'yes' (including us)
+
310 weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
+
311
+
312 newPosition = weight > requiredPct;
+
313 }
+
314 else
+
315 {
+
316 // don't let us outweigh a proposing node, just recognize consensus
+
317 weight = -1;
+
318 newPosition = yays_ > nays_;
+
319 }
+
320
+
321 if (newPosition == ourVote_)
+
322 {
+
323 ++currentVoteCounter_;
+
324 JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO") << ") on "
+
325 << tx_.id() << " : weight " << weight << ", percent "
+
326 << percentTime
+
327 << ", round(s) with this vote: " << currentVoteCounter_;
+
328 JLOG(j_.debug()) << Json::Compact{getJson()};
+
329 return false;
+
330 }
+
331
+
332 currentVoteCounter_ = 0;
+
333 ourVote_ = newPosition;
+
334 JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
+
335 << tx_.id();
+
336 JLOG(j_.debug()) << Json::Compact{getJson()};
+
337 return true;
+
338}
+
339
+
340template <class Tx_t, class NodeID_t>
+
341Json::Value
+
342DisputedTx<Tx_t, NodeID_t>::getJson() const
+
343{
+
344 using std::to_string;
+
345
+
346 Json::Value ret(Json::objectValue);
+
347
+
348 ret["yays"] = yays_;
+
349 ret["nays"] = nays_;
+
350 ret["our_vote"] = ourVote_;
+
351
+
352 if (!votes_.empty())
+
353 {
+
354 Json::Value votesj(Json::objectValue);
+
355 for (auto const& [nodeId, vote] : votes_)
+
356 votesj[to_string(nodeId)] = vote;
+
357 ret["votes"] = std::move(votesj);
+
358 }
+
359
+
360 return ret;
+
361}
+
362
+
363} // namespace ripple
+
364
+
365#endif
+
std::stringstream
Json::Compact
Decorator for streaming out compact json.
Definition: json_writer.h:318
Json::Value
Represents a JSON value.
Definition: json_value.h:149
beast::Journal
A generic endpoint for log messages.
Definition: Journal.h:60
+
beast::Journal::error
Stream error() const
Definition: Journal.h:346
beast::Journal::debug
Stream debug() const
Definition: Journal.h:328
beast::Journal::info
Stream info() const
Definition: Journal.h:334
ripple::DisputedTx
A transaction discovered to be in dispute during consensus.
Definition: DisputedTx.h:49
-
ripple::DisputedTx::votes_
Map_t votes_
Definition: DisputedTx.h:185
-
ripple::DisputedTx::avalancheCounter_
std::size_t avalancheCounter_
How long we have been in the current acceptance phase.
Definition: DisputedTx.h:191
-
ripple::DisputedTx::currentVoteCounter_
std::size_t currentVoteCounter_
The number of rounds we've gone without changing our vote.
Definition: DisputedTx.h:187
-
ripple::DisputedTx::getJson
Json::Value getJson() const
JSON representation of dispute, used for debugging.
Definition: DisputedTx.h:321
-
ripple::DisputedTx::j_
beast::Journal const j_
Definition: DisputedTx.h:192
-
ripple::DisputedTx::updateVote
bool updateVote(int percentTime, bool proposing, ConsensusParms const &p)
Update our vote given progression of consensus.
Definition: DisputedTx.h:258
+
ripple::DisputedTx::votes_
Map_t votes_
Definition: DisputedTx.h:206
+
ripple::DisputedTx::avalancheCounter_
std::size_t avalancheCounter_
How long we have been in the current acceptance phase.
Definition: DisputedTx.h:212
+
ripple::DisputedTx::currentVoteCounter_
std::size_t currentVoteCounter_
The number of rounds we've gone without changing our vote.
Definition: DisputedTx.h:208
+
ripple::DisputedTx::getJson
Json::Value getJson() const
JSON representation of dispute, used for debugging.
Definition: DisputedTx.h:342
+
ripple::DisputedTx::j_
beast::Journal const j_
Definition: DisputedTx.h:213
+
ripple::DisputedTx::updateVote
bool updateVote(int percentTime, bool proposing, ConsensusParms const &p)
Update our vote given progression of consensus.
Definition: DisputedTx.h:279
ripple::DisputedTx::Map_t
boost::container::flat_map< NodeID_t, bool > Map_t
Definition: DisputedTx.h:51
-
ripple::DisputedTx::setVote
bool setVote(NodeID_t const &peer, bool votesYes)
Change a peer's vote.
Definition: DisputedTx.h:198
-
ripple::DisputedTx::avalancheState_
ConsensusParms::AvalancheState avalancheState_
Which minimum acceptance percentage phase we are currently in.
Definition: DisputedTx.h:189
-
ripple::DisputedTx::ourVote_
bool ourVote_
Definition: DisputedTx.h:183
-
ripple::DisputedTx::tx
Tx_t const & tx() const
The disputed transaction.
Definition: DisputedTx.h:132
+
ripple::DisputedTx::setVote
bool setVote(NodeID_t const &peer, bool votesYes)
Change a peer's vote.
Definition: DisputedTx.h:219
+
ripple::DisputedTx::avalancheState_
ConsensusParms::AvalancheState avalancheState_
Which minimum acceptance percentage phase we are currently in.
Definition: DisputedTx.h:210
+
ripple::DisputedTx::ourVote_
bool ourVote_
Definition: DisputedTx.h:204
+
ripple::DisputedTx::tx
Tx_t const & tx() const
The disputed transaction.
Definition: DisputedTx.h:153
ripple::DisputedTx::TxID_t
typename Tx_t::ID TxID_t
Definition: DisputedTx.h:50
+
ripple::DisputedTx::stalled
bool stalled(ConsensusParms const &p, bool proposing, int peersUnchanged, beast::Journal j, std::unique_ptr< std::stringstream > const &clog) const
Are we and our peers "stalled" where we probably won't change our vote?
Definition: DisputedTx.h:88
ripple::DisputedTx::DisputedTx
DisputedTx(Tx_t const &tx, bool ourVote, std::size_t numPeers, beast::Journal j)
Constructor.
Definition: DisputedTx.h:61
-
ripple::DisputedTx::tx_
Tx_t tx_
Definition: DisputedTx.h:184
+
ripple::DisputedTx::tx_
Tx_t tx_
Definition: DisputedTx.h:205
ripple::DisputedTx::getOurVote
bool getOurVote() const
Our vote on whether the transaction should be included.
Definition: DisputedTx.h:80
-
ripple::DisputedTx::setOurVote
void setOurVote(bool o)
Change our vote.
Definition: DisputedTx.h:139
-
ripple::DisputedTx::nays_
int nays_
Definition: DisputedTx.h:182
+
ripple::DisputedTx::setOurVote
void setOurVote(bool o)
Change our vote.
Definition: DisputedTx.h:160
+
ripple::DisputedTx::nays_
int nays_
Definition: DisputedTx.h:203
ripple::DisputedTx::ID
TxID_t const & ID() const
The unique id/hash of the disputed transaction.
Definition: DisputedTx.h:73
-
ripple::DisputedTx::unVote
void unVote(NodeID_t const &peer)
Remove a peer's vote.
Definition: DisputedTx.h:241
-
ripple::DisputedTx::stalled
bool stalled(ConsensusParms const &p, bool proposing, int peersUnchanged) const
Are we and our peers "stalled" where we probably won't change our vote?
Definition: DisputedTx.h:88
-
ripple::DisputedTx::yays_
int yays_
Definition: DisputedTx.h:181
+
ripple::DisputedTx::unVote
void unVote(NodeID_t const &peer)
Remove a peer's vote.
Definition: DisputedTx.h:262
+
ripple::DisputedTx::yays_
int yays_
Definition: DisputedTx.h:202
std::make_pair
T make_pair(T... args)
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:45
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
@@ -401,6 +424,7 @@ $(function() {
ripple::to_string
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
ripple::getJson
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
Definition: LedgerToJson.cpp:357
std::size_t
+
std::stringstream::str
T str(T... args)
ripple::ConsensusParms
Consensus algorithm parameters.
Definition: ConsensusParms.h:39
ripple::ConsensusParms::avSTALLED_ROUNDS
std::size_t const avSTALLED_ROUNDS
Number of rounds before a stuck vote is considered unlikely to change because voting stalled.
Definition: ConsensusParms.h:169
ripple::ConsensusParms::avMIN_ROUNDS
std::size_t const avMIN_ROUNDS
Number of rounds before certain actions can happen.
Definition: ConsensusParms.h:165
@@ -409,6 +433,7 @@ $(function() {
ripple::ConsensusParms::minCONSENSUS_PCT
std::size_t const minCONSENSUS_PCT
The percentage threshold above which we can declare consensus.
Definition: ConsensusParms.h:79
ripple::ConsensusParms::avalancheCutoffs
std::map< AvalancheState, AvalancheCutoff > const avalancheCutoffs
Map the consensus requirement avalanche state to the amount of time that must pass before moving to t...
Definition: ConsensusParms.h:146
std::to_string
T to_string(T... args)
+
std::unique_ptr