mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-23 20:45:51 +00:00
Merge branch 'develop' into vault
This commit is contained in:
@@ -43,6 +43,7 @@ test.consensus > xrpl.basics
|
|||||||
test.consensus > xrpld.app
|
test.consensus > xrpld.app
|
||||||
test.consensus > xrpld.consensus
|
test.consensus > xrpld.consensus
|
||||||
test.consensus > xrpld.ledger
|
test.consensus > xrpld.ledger
|
||||||
|
test.consensus > xrpl.json
|
||||||
test.core > test.jtx
|
test.core > test.jtx
|
||||||
test.core > test.toplevel
|
test.core > test.toplevel
|
||||||
test.core > test.unit_test
|
test.core > test.unit_test
|
||||||
|
|||||||
@@ -558,7 +558,7 @@ struct ConsensusResult
|
|||||||
ConsensusTimer roundTime;
|
ConsensusTimer roundTime;
|
||||||
|
|
||||||
// Indicates state in which consensus ended. Once in the accept phase
|
// Indicates state in which consensus ended. Once in the accept phase
|
||||||
// will be either Yes or MovedOn
|
// will be either Yes or MovedOn or Expired
|
||||||
ConsensusState state = ConsensusState::No;
|
ConsensusState state = ConsensusState::No;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ using TERUnderlyingType = int;
|
|||||||
enum TELcodes : TERUnderlyingType {
|
enum TELcodes : TERUnderlyingType {
|
||||||
// Note: Range is stable.
|
// Note: Range is stable.
|
||||||
// Exact numbers are used in ripple-binary-codec:
|
// Exact numbers are used in ripple-binary-codec:
|
||||||
// https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json
|
// https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/enums/definitions.json
|
||||||
// Use tokens.
|
// Use tokens.
|
||||||
|
|
||||||
// -399 .. -300: L Local error (transaction fee inadequate, exceeds local
|
// -399 .. -300: L Local error (transaction fee inadequate, exceeds local
|
||||||
@@ -73,7 +73,7 @@ enum TELcodes : TERUnderlyingType {
|
|||||||
enum TEMcodes : TERUnderlyingType {
|
enum TEMcodes : TERUnderlyingType {
|
||||||
// Note: Range is stable.
|
// Note: Range is stable.
|
||||||
// Exact numbers are used in ripple-binary-codec:
|
// Exact numbers are used in ripple-binary-codec:
|
||||||
// https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json
|
// https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/enums/definitions.json
|
||||||
// Use tokens.
|
// Use tokens.
|
||||||
|
|
||||||
// -299 .. -200: M Malformed (bad signature)
|
// -299 .. -200: M Malformed (bad signature)
|
||||||
@@ -148,7 +148,7 @@ enum TEMcodes : TERUnderlyingType {
|
|||||||
enum TEFcodes : TERUnderlyingType {
|
enum TEFcodes : TERUnderlyingType {
|
||||||
// Note: Range is stable.
|
// Note: Range is stable.
|
||||||
// Exact numbers are used in ripple-binary-codec:
|
// Exact numbers are used in ripple-binary-codec:
|
||||||
// https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json
|
// https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/enums/definitions.json
|
||||||
// Use tokens.
|
// Use tokens.
|
||||||
|
|
||||||
// -199 .. -100: F
|
// -199 .. -100: F
|
||||||
@@ -192,7 +192,7 @@ enum TEFcodes : TERUnderlyingType {
|
|||||||
enum TERcodes : TERUnderlyingType {
|
enum TERcodes : TERUnderlyingType {
|
||||||
// Note: Range is stable.
|
// Note: Range is stable.
|
||||||
// Exact numbers are used in ripple-binary-codec:
|
// Exact numbers are used in ripple-binary-codec:
|
||||||
// https://github.com/ripple/ripple-binary-codec/blob/master/src/enums/definitions.json
|
// https://github.com/XRPLF/xrpl.js/blob/main/packages/ripple-binary-codec/src/enums/definitions.json
|
||||||
// Use tokens.
|
// Use tokens.
|
||||||
|
|
||||||
// -99 .. -1: R Retry
|
// -99 .. -1: R Retry
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <xrpld/consensus/Consensus.h>
|
#include <xrpld/consensus/Consensus.h>
|
||||||
|
|
||||||
#include <xrpl/beast/unit_test.h>
|
#include <xrpl/beast/unit_test.h>
|
||||||
|
#include <xrpl/json/to_string.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
namespace test {
|
namespace test {
|
||||||
@@ -40,6 +41,7 @@ public:
|
|||||||
testShouldCloseLedger()
|
testShouldCloseLedger()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
testcase("should close ledger");
|
||||||
|
|
||||||
// Use default parameters
|
// Use default parameters
|
||||||
ConsensusParms const p{};
|
ConsensusParms const p{};
|
||||||
@@ -78,46 +80,102 @@ public:
|
|||||||
testCheckConsensus()
|
testCheckConsensus()
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
testcase("check consensus");
|
||||||
|
|
||||||
// Use default parameterss
|
// Use default parameterss
|
||||||
ConsensusParms const p{};
|
ConsensusParms const p{};
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Disputes still in doubt
|
||||||
|
//
|
||||||
// Not enough time has elapsed
|
// Not enough time has elapsed
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
ConsensusState::No ==
|
ConsensusState::No ==
|
||||||
checkConsensus(10, 2, 2, 0, 3s, 2s, p, true, journal_));
|
checkConsensus(10, 2, 2, 0, 3s, 2s, false, p, true, journal_));
|
||||||
|
|
||||||
// If not enough peers have propsed, ensure
|
// If not enough peers have propsed, ensure
|
||||||
// more time for proposals
|
// more time for proposals
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
ConsensusState::No ==
|
ConsensusState::No ==
|
||||||
checkConsensus(10, 2, 2, 0, 3s, 4s, p, true, journal_));
|
checkConsensus(10, 2, 2, 0, 3s, 4s, false, p, true, journal_));
|
||||||
|
|
||||||
// Enough time has elapsed and we all agree
|
// Enough time has elapsed and we all agree
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
ConsensusState::Yes ==
|
ConsensusState::Yes ==
|
||||||
checkConsensus(10, 2, 2, 0, 3s, 10s, p, true, journal_));
|
checkConsensus(10, 2, 2, 0, 3s, 10s, false, p, true, journal_));
|
||||||
|
|
||||||
// Enough time has elapsed and we don't yet agree
|
// Enough time has elapsed and we don't yet agree
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
ConsensusState::No ==
|
ConsensusState::No ==
|
||||||
checkConsensus(10, 2, 1, 0, 3s, 10s, p, true, journal_));
|
checkConsensus(10, 2, 1, 0, 3s, 10s, false, p, true, journal_));
|
||||||
|
|
||||||
// Our peers have moved on
|
// Our peers have moved on
|
||||||
// Enough time has elapsed and we all agree
|
// Enough time has elapsed and we all agree
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
ConsensusState::MovedOn ==
|
ConsensusState::MovedOn ==
|
||||||
checkConsensus(10, 2, 1, 8, 3s, 10s, p, true, journal_));
|
checkConsensus(10, 2, 1, 8, 3s, 10s, false, p, true, journal_));
|
||||||
|
|
||||||
// If no peers, don't agree until time has passed.
|
// If no peers, don't agree until time has passed.
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
ConsensusState::No ==
|
ConsensusState::No ==
|
||||||
checkConsensus(0, 0, 0, 0, 3s, 10s, p, true, journal_));
|
checkConsensus(0, 0, 0, 0, 3s, 10s, false, p, true, journal_));
|
||||||
|
|
||||||
// Agree if no peers and enough time has passed.
|
// Agree if no peers and enough time has passed.
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
ConsensusState::Yes ==
|
ConsensusState::Yes ==
|
||||||
checkConsensus(0, 0, 0, 0, 3s, 16s, p, true, journal_));
|
checkConsensus(0, 0, 0, 0, 3s, 16s, false, p, true, journal_));
|
||||||
|
|
||||||
|
// Expire if too much time has passed without agreement
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::Expired ==
|
||||||
|
checkConsensus(10, 8, 1, 0, 1s, 19s, false, p, true, journal_));
|
||||||
|
|
||||||
|
///////////////
|
||||||
|
// Stalled
|
||||||
|
//
|
||||||
|
// Not enough time has elapsed
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::No ==
|
||||||
|
checkConsensus(10, 2, 2, 0, 3s, 2s, true, p, true, journal_));
|
||||||
|
|
||||||
|
// If not enough peers have propsed, ensure
|
||||||
|
// more time for proposals
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::No ==
|
||||||
|
checkConsensus(10, 2, 2, 0, 3s, 4s, true, p, true, journal_));
|
||||||
|
|
||||||
|
// Enough time has elapsed and we all agree
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::Yes ==
|
||||||
|
checkConsensus(10, 2, 2, 0, 3s, 10s, true, p, true, journal_));
|
||||||
|
|
||||||
|
// Enough time has elapsed and we don't yet agree, but there's nothing
|
||||||
|
// left to dispute
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::Yes ==
|
||||||
|
checkConsensus(10, 2, 1, 0, 3s, 10s, true, p, true, journal_));
|
||||||
|
|
||||||
|
// Our peers have moved on
|
||||||
|
// Enough time has elapsed and we all agree, nothing left to dispute
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::Yes ==
|
||||||
|
checkConsensus(10, 2, 1, 8, 3s, 10s, true, p, true, journal_));
|
||||||
|
|
||||||
|
// If no peers, don't agree until time has passed.
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::No ==
|
||||||
|
checkConsensus(0, 0, 0, 0, 3s, 10s, true, p, true, journal_));
|
||||||
|
|
||||||
|
// Agree if no peers and enough time has passed.
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::Yes ==
|
||||||
|
checkConsensus(0, 0, 0, 0, 3s, 16s, true, p, true, journal_));
|
||||||
|
|
||||||
|
// We are done if there's nothing left to dispute, no matter how much
|
||||||
|
// time has passed
|
||||||
|
BEAST_EXPECT(
|
||||||
|
ConsensusState::Yes ==
|
||||||
|
checkConsensus(10, 8, 1, 0, 1s, 19s, true, p, true, journal_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -125,6 +183,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
|
testcase("standalone");
|
||||||
|
|
||||||
Sim s;
|
Sim s;
|
||||||
PeerGroup peers = s.createGroup(1);
|
PeerGroup peers = s.createGroup(1);
|
||||||
@@ -149,6 +208,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("peers agree");
|
||||||
|
|
||||||
ConsensusParms const parms{};
|
ConsensusParms const parms{};
|
||||||
Sim sim;
|
Sim sim;
|
||||||
@@ -186,6 +246,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("slow peers");
|
||||||
|
|
||||||
// Several tests of a complete trust graph with a subset of peers
|
// Several tests of a complete trust graph with a subset of peers
|
||||||
// that have significantly longer network delays to the rest of the
|
// that have significantly longer network delays to the rest of the
|
||||||
@@ -351,6 +412,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("close time disagree");
|
||||||
|
|
||||||
// This is a very specialized test to get ledgers to disagree on
|
// This is a very specialized test to get ledgers to disagree on
|
||||||
// the close time. It unfortunately assumes knowledge about current
|
// the close time. It unfortunately assumes knowledge about current
|
||||||
@@ -417,6 +479,8 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("wrong LCL");
|
||||||
|
|
||||||
// Specialized test to exercise a temporary fork in which some peers
|
// Specialized test to exercise a temporary fork in which some peers
|
||||||
// are working on an incorrect prior ledger.
|
// are working on an incorrect prior ledger.
|
||||||
|
|
||||||
@@ -589,6 +653,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("consensus close time rounding");
|
||||||
|
|
||||||
// This is a specialized test engineered to yield ledgers with different
|
// This is a specialized test engineered to yield ledgers with different
|
||||||
// close times even though the peers believe they had close time
|
// close times even though the peers believe they had close time
|
||||||
@@ -604,9 +669,6 @@ public:
|
|||||||
PeerGroup fast = sim.createGroup(4);
|
PeerGroup fast = sim.createGroup(4);
|
||||||
PeerGroup network = fast + slow;
|
PeerGroup network = fast + slow;
|
||||||
|
|
||||||
for (Peer* peer : network)
|
|
||||||
peer->consensusParms = parms;
|
|
||||||
|
|
||||||
// Connected trust graph
|
// Connected trust graph
|
||||||
network.trust(network);
|
network.trust(network);
|
||||||
|
|
||||||
@@ -692,6 +754,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("fork");
|
||||||
|
|
||||||
std::uint32_t numPeers = 10;
|
std::uint32_t numPeers = 10;
|
||||||
// Vary overlap between two UNLs
|
// Vary overlap between two UNLs
|
||||||
@@ -748,6 +811,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("hub network");
|
||||||
|
|
||||||
// Simulate a set of 5 validators that aren't directly connected but
|
// Simulate a set of 5 validators that aren't directly connected but
|
||||||
// rely on a single hub node for communication
|
// rely on a single hub node for communication
|
||||||
@@ -835,6 +899,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("preferred by branch");
|
||||||
|
|
||||||
// Simulate network splits that are prevented from forking when using
|
// Simulate network splits that are prevented from forking when using
|
||||||
// preferred ledger by trie. This is a contrived example that involves
|
// preferred ledger by trie. This is a contrived example that involves
|
||||||
@@ -967,6 +1032,7 @@ public:
|
|||||||
{
|
{
|
||||||
using namespace csf;
|
using namespace csf;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
testcase("pause for laggards");
|
||||||
|
|
||||||
// Test that validators that jump ahead of the network slow
|
// Test that validators that jump ahead of the network slow
|
||||||
// down.
|
// down.
|
||||||
@@ -1052,6 +1118,302 @@ public:
|
|||||||
BEAST_EXPECT(sim.synchronized());
|
BEAST_EXPECT(sim.synchronized());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testDisputes()
|
||||||
|
{
|
||||||
|
testcase("disputes");
|
||||||
|
|
||||||
|
using namespace csf;
|
||||||
|
|
||||||
|
// Test dispute objects directly
|
||||||
|
using Dispute = DisputedTx<Tx, PeerID>;
|
||||||
|
|
||||||
|
Tx const txTrue{99};
|
||||||
|
Tx const txFalse{98};
|
||||||
|
Tx const txFollowingTrue{97};
|
||||||
|
Tx const txFollowingFalse{96};
|
||||||
|
int const numPeers = 100;
|
||||||
|
ConsensusParms p;
|
||||||
|
std::size_t peersUnchanged = 0;
|
||||||
|
|
||||||
|
// Three cases:
|
||||||
|
// 1 proposing, initial vote yes
|
||||||
|
// 2 proposing, initial vote no
|
||||||
|
// 3 not proposing, initial vote doesn't matter after the first update,
|
||||||
|
// use yes
|
||||||
|
{
|
||||||
|
Dispute proposingTrue{txTrue.id(), true, numPeers, journal_};
|
||||||
|
Dispute proposingFalse{txFalse.id(), false, numPeers, journal_};
|
||||||
|
Dispute followingTrue{
|
||||||
|
txFollowingTrue.id(), true, numPeers, journal_};
|
||||||
|
Dispute followingFalse{
|
||||||
|
txFollowingFalse.id(), false, numPeers, journal_};
|
||||||
|
BEAST_EXPECT(proposingTrue.ID() == 99);
|
||||||
|
BEAST_EXPECT(proposingFalse.ID() == 98);
|
||||||
|
BEAST_EXPECT(followingTrue.ID() == 97);
|
||||||
|
BEAST_EXPECT(followingFalse.ID() == 96);
|
||||||
|
|
||||||
|
// Create an even split in the peer votes
|
||||||
|
for (int i = 0; i < numPeers; ++i)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(proposingTrue.setVote(PeerID(i), i < 50));
|
||||||
|
BEAST_EXPECT(proposingFalse.setVote(PeerID(i), i < 50));
|
||||||
|
BEAST_EXPECT(followingTrue.setVote(PeerID(i), i < 50));
|
||||||
|
BEAST_EXPECT(followingFalse.setVote(PeerID(i), i < 50));
|
||||||
|
}
|
||||||
|
// Switch the middle vote to match mine
|
||||||
|
BEAST_EXPECT(proposingTrue.setVote(PeerID(50), true));
|
||||||
|
BEAST_EXPECT(proposingFalse.setVote(PeerID(49), false));
|
||||||
|
BEAST_EXPECT(followingTrue.setVote(PeerID(50), true));
|
||||||
|
BEAST_EXPECT(followingFalse.setVote(PeerID(49), false));
|
||||||
|
|
||||||
|
// no changes yet
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
|
||||||
|
|
||||||
|
// I'm in the majority, my vote should not change
|
||||||
|
BEAST_EXPECT(!proposingTrue.updateVote(5, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(5, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(5, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(5, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(!proposingTrue.updateVote(10, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(10, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(10, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(10, false, p));
|
||||||
|
|
||||||
|
peersUnchanged = 2;
|
||||||
|
BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
|
||||||
|
|
||||||
|
// Right now, the vote is 51%. The requirement is about to jump to
|
||||||
|
// 65%
|
||||||
|
BEAST_EXPECT(proposingTrue.updateVote(55, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(55, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(55, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(55, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
// 16 validators change their vote to match my original vote
|
||||||
|
for (int i = 0; i < 16; ++i)
|
||||||
|
{
|
||||||
|
auto pTrue = PeerID(numPeers - i - 1);
|
||||||
|
auto pFalse = PeerID(i);
|
||||||
|
BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
|
||||||
|
BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
|
||||||
|
BEAST_EXPECT(followingTrue.setVote(pTrue, true));
|
||||||
|
BEAST_EXPECT(followingFalse.setVote(pFalse, false));
|
||||||
|
}
|
||||||
|
// The vote should now be 66%, threshold is 65%
|
||||||
|
BEAST_EXPECT(proposingTrue.updateVote(60, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(60, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(60, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(60, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
// Threshold jumps to 70%
|
||||||
|
BEAST_EXPECT(proposingTrue.updateVote(86, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(86, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(86, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(86, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
// 5 more validators change their vote to match my original vote
|
||||||
|
for (int i = 16; i < 21; ++i)
|
||||||
|
{
|
||||||
|
auto pTrue = PeerID(numPeers - i - 1);
|
||||||
|
auto pFalse = PeerID(i);
|
||||||
|
BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
|
||||||
|
BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
|
||||||
|
BEAST_EXPECT(followingTrue.setVote(pTrue, true));
|
||||||
|
BEAST_EXPECT(followingFalse.setVote(pFalse, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The vote should now be 71%, threshold is 70%
|
||||||
|
BEAST_EXPECT(proposingTrue.updateVote(90, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(90, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(90, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(90, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
// The vote should now be 71%, threshold is 70%
|
||||||
|
BEAST_EXPECT(!proposingTrue.updateVote(150, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(150, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(150, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(150, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
// The vote should now be 71%, threshold is 70%
|
||||||
|
BEAST_EXPECT(!proposingTrue.updateVote(190, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(190, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(190, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(190, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
peersUnchanged = 3;
|
||||||
|
BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
|
||||||
|
|
||||||
|
// Threshold jumps to 95%
|
||||||
|
BEAST_EXPECT(proposingTrue.updateVote(220, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(220, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(220, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(220, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
// 25 more validators change their vote to match my original vote
|
||||||
|
for (int i = 21; i < 46; ++i)
|
||||||
|
{
|
||||||
|
auto pTrue = PeerID(numPeers - i - 1);
|
||||||
|
auto pFalse = PeerID(i);
|
||||||
|
BEAST_EXPECT(proposingTrue.setVote(pTrue, true));
|
||||||
|
BEAST_EXPECT(proposingFalse.setVote(pFalse, false));
|
||||||
|
BEAST_EXPECT(followingTrue.setVote(pTrue, true));
|
||||||
|
BEAST_EXPECT(followingFalse.setVote(pFalse, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The vote should now be 96%, threshold is 95%
|
||||||
|
BEAST_EXPECT(proposingTrue.updateVote(250, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(250, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(250, false, p));
|
||||||
|
BEAST_EXPECT(!followingFalse.updateVote(250, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
for (peersUnchanged = 0; peersUnchanged < 6; ++peersUnchanged)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!proposingFalse.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingTrue.stalled(p, false, peersUnchanged));
|
||||||
|
BEAST_EXPECT(!followingFalse.stalled(p, false, peersUnchanged));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 1; ++i)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
!followingFalse.updateVote(250 + 10 * i, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
// true vote has changed recently, so not stalled
|
||||||
|
BEAST_EXPECT(!proposingTrue.stalled(p, true, 0));
|
||||||
|
// remaining votes have been unchanged in so long that we only
|
||||||
|
// need to hit the second round at 95% to be stalled, regardless
|
||||||
|
// of peers
|
||||||
|
BEAST_EXPECT(proposingFalse.stalled(p, true, 0));
|
||||||
|
BEAST_EXPECT(followingTrue.stalled(p, false, 0));
|
||||||
|
BEAST_EXPECT(followingFalse.stalled(p, false, 0));
|
||||||
|
|
||||||
|
// true vote has changed recently, so not stalled
|
||||||
|
BEAST_EXPECT(!proposingTrue.stalled(p, true, peersUnchanged));
|
||||||
|
// remaining votes have been unchanged in so long that we only
|
||||||
|
// need to hit the second round at 95% to be stalled, regardless
|
||||||
|
// of peers
|
||||||
|
BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged));
|
||||||
|
BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged));
|
||||||
|
}
|
||||||
|
for (int i = 1; i < 3; ++i)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
!followingFalse.updateVote(250 + 10 * i, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
// true vote changed 2 rounds ago, and peers are changing, so
|
||||||
|
// not stalled
|
||||||
|
BEAST_EXPECT(!proposingTrue.stalled(p, true, 0));
|
||||||
|
// still stalled
|
||||||
|
BEAST_EXPECT(proposingFalse.stalled(p, true, 0));
|
||||||
|
BEAST_EXPECT(followingTrue.stalled(p, false, 0));
|
||||||
|
BEAST_EXPECT(followingFalse.stalled(p, false, 0));
|
||||||
|
|
||||||
|
// true vote changed 2 rounds ago, and peers are NOT changing,
|
||||||
|
// so stalled
|
||||||
|
BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged));
|
||||||
|
// still stalled
|
||||||
|
BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged));
|
||||||
|
BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged));
|
||||||
|
}
|
||||||
|
for (int i = 3; i < 5; ++i)
|
||||||
|
{
|
||||||
|
BEAST_EXPECT(!proposingTrue.updateVote(250 + 10 * i, true, p));
|
||||||
|
BEAST_EXPECT(!proposingFalse.updateVote(250 + 10 * i, true, p));
|
||||||
|
BEAST_EXPECT(!followingTrue.updateVote(250 + 10 * i, false, p));
|
||||||
|
BEAST_EXPECT(
|
||||||
|
!followingFalse.updateVote(250 + 10 * i, false, p));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(proposingFalse.getOurVote() == false);
|
||||||
|
BEAST_EXPECT(followingTrue.getOurVote() == true);
|
||||||
|
BEAST_EXPECT(followingFalse.getOurVote() == false);
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.stalled(p, true, 0));
|
||||||
|
BEAST_EXPECT(proposingFalse.stalled(p, true, 0));
|
||||||
|
BEAST_EXPECT(followingTrue.stalled(p, false, 0));
|
||||||
|
BEAST_EXPECT(followingFalse.stalled(p, false, 0));
|
||||||
|
|
||||||
|
BEAST_EXPECT(proposingTrue.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(proposingFalse.stalled(p, true, peersUnchanged));
|
||||||
|
BEAST_EXPECT(followingTrue.stalled(p, false, peersUnchanged));
|
||||||
|
BEAST_EXPECT(followingFalse.stalled(p, false, peersUnchanged));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
run() override
|
run() override
|
||||||
{
|
{
|
||||||
@@ -1068,6 +1430,7 @@ public:
|
|||||||
testHubNetwork();
|
testHubNetwork();
|
||||||
testPreferredByBranch();
|
testPreferredByBranch();
|
||||||
testPauseForLaggards();
|
testPauseForLaggards();
|
||||||
|
testDisputes();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ID
|
ID const&
|
||||||
id() const
|
id() const
|
||||||
{
|
{
|
||||||
return id_;
|
return id_;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include <xrpld/app/misc/NetworkOPs.h>
|
#include <xrpld/app/misc/NetworkOPs.h>
|
||||||
#include <xrpld/app/misc/Transaction.h>
|
#include <xrpld/app/misc/Transaction.h>
|
||||||
#include <xrpld/app/misc/TxQ.h>
|
#include <xrpld/app/misc/TxQ.h>
|
||||||
|
#include <xrpld/app/misc/ValidatorKeys.h>
|
||||||
#include <xrpld/app/misc/ValidatorList.h>
|
#include <xrpld/app/misc/ValidatorList.h>
|
||||||
#include <xrpld/app/misc/detail/AccountTxPaging.h>
|
#include <xrpld/app/misc/detail/AccountTxPaging.h>
|
||||||
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
|
#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
|
||||||
@@ -249,6 +250,12 @@ public:
|
|||||||
beast::get_abstract_clock<std::chrono::steady_clock>(),
|
beast::get_abstract_clock<std::chrono::steady_clock>(),
|
||||||
validatorKeys,
|
validatorKeys,
|
||||||
app_.logs().journal("LedgerConsensus"))
|
app_.logs().journal("LedgerConsensus"))
|
||||||
|
, validatorPK_(
|
||||||
|
validatorKeys.keys ? validatorKeys.keys->publicKey
|
||||||
|
: decltype(validatorPK_){})
|
||||||
|
, validatorMasterPK_(
|
||||||
|
validatorKeys.keys ? validatorKeys.keys->masterPublicKey
|
||||||
|
: decltype(validatorMasterPK_){})
|
||||||
, m_ledgerMaster(ledgerMaster)
|
, m_ledgerMaster(ledgerMaster)
|
||||||
, m_job_queue(job_queue)
|
, m_job_queue(job_queue)
|
||||||
, m_standalone(standalone)
|
, m_standalone(standalone)
|
||||||
@@ -732,6 +739,9 @@ private:
|
|||||||
|
|
||||||
RCLConsensus mConsensus;
|
RCLConsensus mConsensus;
|
||||||
|
|
||||||
|
std::optional<PublicKey> const validatorPK_;
|
||||||
|
std::optional<PublicKey> const validatorMasterPK_;
|
||||||
|
|
||||||
ConsensusPhase mLastConsensusPhase;
|
ConsensusPhase mLastConsensusPhase;
|
||||||
|
|
||||||
LedgerMaster& m_ledgerMaster;
|
LedgerMaster& m_ledgerMaster;
|
||||||
@@ -1917,6 +1927,23 @@ NetworkOPsImp::beginConsensus(
|
|||||||
bool
|
bool
|
||||||
NetworkOPsImp::processTrustedProposal(RCLCxPeerPos peerPos)
|
NetworkOPsImp::processTrustedProposal(RCLCxPeerPos peerPos)
|
||||||
{
|
{
|
||||||
|
auto const& peerKey = peerPos.publicKey();
|
||||||
|
if (validatorPK_ == peerKey || validatorMasterPK_ == peerKey)
|
||||||
|
{
|
||||||
|
// Could indicate a operator misconfiguration where two nodes are
|
||||||
|
// running with the same validator key configured, so this isn't fatal,
|
||||||
|
// and it doesn't necessarily indicate peer misbehavior. But since this
|
||||||
|
// is a trusted message, it could be a very big deal. Either way, we
|
||||||
|
// don't want to relay the proposal. Note that the byzantine behavior
|
||||||
|
// detection in handleNewValidation will notify other peers.
|
||||||
|
UNREACHABLE(
|
||||||
|
"ripple::NetworkOPsImp::processTrustedProposal : received own "
|
||||||
|
"proposal");
|
||||||
|
JLOG(m_journal.error())
|
||||||
|
<< "Received a TRUSTED proposal signed with my key from a peer";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return mConsensus.peerProposal(app_.timeKeeper().closeTime(), peerPos);
|
return mConsensus.peerProposal(app_.timeKeeper().closeTime(), peerPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ checkConsensusReached(
|
|||||||
bool count_self,
|
bool count_self,
|
||||||
std::size_t minConsensusPct,
|
std::size_t minConsensusPct,
|
||||||
bool reachedMax,
|
bool reachedMax,
|
||||||
|
bool stalled,
|
||||||
std::unique_ptr<std::stringstream> const& clog)
|
std::unique_ptr<std::stringstream> const& clog)
|
||||||
{
|
{
|
||||||
CLOG(clog) << "checkConsensusReached params: agreeing: " << agreeing
|
CLOG(clog) << "checkConsensusReached params: agreeing: " << agreeing
|
||||||
@@ -138,6 +139,17 @@ checkConsensusReached(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We only get stalled when every disputed transaction unequivocally has 80%
|
||||||
|
// (minConsensusPct) agreement, either for or against. That is: either under
|
||||||
|
// 20% or over 80% consensus (repectively "nay" or "yay"). This prevents
|
||||||
|
// manipulation by a minority of byzantine peers of which transactions make
|
||||||
|
// the cut to get into the ledger.
|
||||||
|
if (stalled)
|
||||||
|
{
|
||||||
|
CLOG(clog) << "consensus stalled. ";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (count_self)
|
if (count_self)
|
||||||
{
|
{
|
||||||
++agreeing;
|
++agreeing;
|
||||||
@@ -147,6 +159,7 @@ checkConsensusReached(
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::size_t currentPercentage = (agreeing * 100) / total;
|
std::size_t currentPercentage = (agreeing * 100) / total;
|
||||||
|
|
||||||
CLOG(clog) << "currentPercentage: " << currentPercentage;
|
CLOG(clog) << "currentPercentage: " << currentPercentage;
|
||||||
bool const ret = currentPercentage >= minConsensusPct;
|
bool const ret = currentPercentage >= minConsensusPct;
|
||||||
if (ret)
|
if (ret)
|
||||||
@@ -168,6 +181,7 @@ checkConsensus(
|
|||||||
std::size_t currentFinished,
|
std::size_t currentFinished,
|
||||||
std::chrono::milliseconds previousAgreeTime,
|
std::chrono::milliseconds previousAgreeTime,
|
||||||
std::chrono::milliseconds currentAgreeTime,
|
std::chrono::milliseconds currentAgreeTime,
|
||||||
|
bool stalled,
|
||||||
ConsensusParms const& parms,
|
ConsensusParms const& parms,
|
||||||
bool proposing,
|
bool proposing,
|
||||||
beast::Journal j,
|
beast::Journal j,
|
||||||
@@ -181,7 +195,7 @@ checkConsensus(
|
|||||||
<< " minimum duration to reach consensus: "
|
<< " minimum duration to reach consensus: "
|
||||||
<< parms.ledgerMIN_CONSENSUS.count() << "ms"
|
<< parms.ledgerMIN_CONSENSUS.count() << "ms"
|
||||||
<< " max consensus time " << parms.ledgerMAX_CONSENSUS.count()
|
<< " max consensus time " << parms.ledgerMAX_CONSENSUS.count()
|
||||||
<< "s"
|
<< "ms"
|
||||||
<< " minimum consensus percentage: " << parms.minCONSENSUS_PCT
|
<< " minimum consensus percentage: " << parms.minCONSENSUS_PCT
|
||||||
<< ". ";
|
<< ". ";
|
||||||
|
|
||||||
@@ -211,10 +225,12 @@ checkConsensus(
|
|||||||
proposing,
|
proposing,
|
||||||
parms.minCONSENSUS_PCT,
|
parms.minCONSENSUS_PCT,
|
||||||
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
|
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
|
||||||
|
stalled,
|
||||||
clog))
|
clog))
|
||||||
{
|
{
|
||||||
JLOG(j.debug()) << "normal consensus";
|
JLOG((stalled ? j.warn() : j.debug()))
|
||||||
CLOG(clog) << "reached. ";
|
<< "normal consensus" << (stalled ? ", but stalled" : "");
|
||||||
|
CLOG(clog) << "reached" << (stalled ? ", but stalled." : ".");
|
||||||
return ConsensusState::Yes;
|
return ConsensusState::Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +242,7 @@ checkConsensus(
|
|||||||
false,
|
false,
|
||||||
parms.minCONSENSUS_PCT,
|
parms.minCONSENSUS_PCT,
|
||||||
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
|
currentAgreeTime > parms.ledgerMAX_CONSENSUS,
|
||||||
|
false,
|
||||||
clog))
|
clog))
|
||||||
{
|
{
|
||||||
JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
|
JLOG(j.warn()) << "We see no consensus, but 80% of nodes have moved on";
|
||||||
@@ -233,6 +250,19 @@ checkConsensus(
|
|||||||
return ConsensusState::MovedOn;
|
return ConsensusState::MovedOn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::chrono::milliseconds const maxAgreeTime =
|
||||||
|
previousAgreeTime * parms.ledgerABANDON_CONSENSUS_FACTOR;
|
||||||
|
if (currentAgreeTime > std::clamp(
|
||||||
|
maxAgreeTime,
|
||||||
|
parms.ledgerMAX_CONSENSUS,
|
||||||
|
parms.ledgerABANDON_CONSENSUS))
|
||||||
|
{
|
||||||
|
JLOG(j.warn()) << "consensus taken too long";
|
||||||
|
CLOG(clog) << "Consensus taken too long. ";
|
||||||
|
// Note the Expired result may be overridden by the caller.
|
||||||
|
return ConsensusState::Expired;
|
||||||
|
}
|
||||||
|
|
||||||
// no consensus yet
|
// no consensus yet
|
||||||
JLOG(j.trace()) << "no consensus";
|
JLOG(j.trace()) << "no consensus";
|
||||||
CLOG(clog) << "No consensus. ";
|
CLOG(clog) << "No consensus. ";
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include <xrpl/beast/utility/Journal.h>
|
#include <xrpl/beast/utility/Journal.h>
|
||||||
#include <xrpl/json/json_writer.h>
|
#include <xrpl/json/json_writer.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -81,6 +82,10 @@ shouldCloseLedger(
|
|||||||
last ledger
|
last ledger
|
||||||
@param currentAgreeTime how long, in milliseconds, we've been trying to
|
@param currentAgreeTime how long, in milliseconds, we've been trying to
|
||||||
agree
|
agree
|
||||||
|
@param stalled the network appears to be stalled, where
|
||||||
|
neither we nor our peers have changed their vote on any disputes in a
|
||||||
|
while. This is undesirable, and will cause us to end consensus
|
||||||
|
without 80% agreement.
|
||||||
@param parms Consensus constant parameters
|
@param parms Consensus constant parameters
|
||||||
@param proposing whether we should count ourselves
|
@param proposing whether we should count ourselves
|
||||||
@param j journal for logging
|
@param j journal for logging
|
||||||
@@ -94,6 +99,7 @@ checkConsensus(
|
|||||||
std::size_t currentFinished,
|
std::size_t currentFinished,
|
||||||
std::chrono::milliseconds previousAgreeTime,
|
std::chrono::milliseconds previousAgreeTime,
|
||||||
std::chrono::milliseconds currentAgreeTime,
|
std::chrono::milliseconds currentAgreeTime,
|
||||||
|
bool stalled,
|
||||||
ConsensusParms const& parms,
|
ConsensusParms const& parms,
|
||||||
bool proposing,
|
bool proposing,
|
||||||
beast::Journal j,
|
beast::Journal j,
|
||||||
@@ -574,6 +580,9 @@ private:
|
|||||||
|
|
||||||
NetClock::duration closeResolution_ = ledgerDefaultTimeResolution;
|
NetClock::duration closeResolution_ = ledgerDefaultTimeResolution;
|
||||||
|
|
||||||
|
ConsensusParms::AvalancheState closeTimeAvalancheState_ =
|
||||||
|
ConsensusParms::init;
|
||||||
|
|
||||||
// Time it took for the last consensus round to converge
|
// Time it took for the last consensus round to converge
|
||||||
std::chrono::milliseconds prevRoundTime_;
|
std::chrono::milliseconds prevRoundTime_;
|
||||||
|
|
||||||
@@ -599,6 +608,13 @@ private:
|
|||||||
std::optional<Result> result_;
|
std::optional<Result> result_;
|
||||||
ConsensusCloseTimes rawCloseTimes_;
|
ConsensusCloseTimes rawCloseTimes_;
|
||||||
|
|
||||||
|
// The number of calls to phaseEstablish where none of our peers
|
||||||
|
// have changed any votes on disputed transactions.
|
||||||
|
std::size_t peerUnchangedCounter_ = 0;
|
||||||
|
|
||||||
|
// The total number of times we have called phaseEstablish
|
||||||
|
std::size_t establishCounter_ = 0;
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Peer related consensus data
|
// Peer related consensus data
|
||||||
|
|
||||||
@@ -696,6 +712,7 @@ Consensus<Adaptor>::startRoundInternal(
|
|||||||
previousLedger_ = prevLedger;
|
previousLedger_ = prevLedger;
|
||||||
result_.reset();
|
result_.reset();
|
||||||
convergePercent_ = 0;
|
convergePercent_ = 0;
|
||||||
|
closeTimeAvalancheState_ = ConsensusParms::init;
|
||||||
haveCloseTimeConsensus_ = false;
|
haveCloseTimeConsensus_ = false;
|
||||||
openTime_.reset(clock_.now());
|
openTime_.reset(clock_.now());
|
||||||
currPeerPositions_.clear();
|
currPeerPositions_.clear();
|
||||||
@@ -1351,6 +1368,9 @@ Consensus<Adaptor>::phaseEstablish(
|
|||||||
// can only establish consensus if we already took a stance
|
// can only establish consensus if we already took a stance
|
||||||
XRPL_ASSERT(result_, "ripple::Consensus::phaseEstablish : result is set");
|
XRPL_ASSERT(result_, "ripple::Consensus::phaseEstablish : result is set");
|
||||||
|
|
||||||
|
++peerUnchangedCounter_;
|
||||||
|
++establishCounter_;
|
||||||
|
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
ConsensusParms const& parms = adaptor_.parms();
|
ConsensusParms const& parms = adaptor_.parms();
|
||||||
|
|
||||||
@@ -1417,6 +1437,8 @@ Consensus<Adaptor>::closeLedger(std::unique_ptr<std::stringstream> const& clog)
|
|||||||
phase_ = ConsensusPhase::establish;
|
phase_ = ConsensusPhase::establish;
|
||||||
JLOG(j_.debug()) << "transitioned to ConsensusPhase::establish";
|
JLOG(j_.debug()) << "transitioned to ConsensusPhase::establish";
|
||||||
rawCloseTimes_.self = now_;
|
rawCloseTimes_.self = now_;
|
||||||
|
peerUnchangedCounter_ = 0;
|
||||||
|
establishCounter_ = 0;
|
||||||
|
|
||||||
result_.emplace(adaptor_.onClose(previousLedger_, now_, mode_.get()));
|
result_.emplace(adaptor_.onClose(previousLedger_, now_, mode_.get()));
|
||||||
result_->roundTime.reset(clock_.now());
|
result_->roundTime.reset(clock_.now());
|
||||||
@@ -1550,16 +1572,11 @@ Consensus<Adaptor>::updateOurPositions(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int neededWeight;
|
// We don't track rounds for close time, so just pass 0s
|
||||||
|
auto const [neededWeight, newState] = getNeededWeight(
|
||||||
if (convergePercent_ < parms.avMID_CONSENSUS_TIME)
|
parms, closeTimeAvalancheState_, convergePercent_, 0, 0);
|
||||||
neededWeight = parms.avINIT_CONSENSUS_PCT;
|
if (newState)
|
||||||
else if (convergePercent_ < parms.avLATE_CONSENSUS_TIME)
|
closeTimeAvalancheState_ = *newState;
|
||||||
neededWeight = parms.avMID_CONSENSUS_PCT;
|
|
||||||
else if (convergePercent_ < parms.avSTUCK_CONSENSUS_TIME)
|
|
||||||
neededWeight = parms.avLATE_CONSENSUS_PCT;
|
|
||||||
else
|
|
||||||
neededWeight = parms.avSTUCK_CONSENSUS_PCT;
|
|
||||||
CLOG(clog) << "neededWeight " << neededWeight << ". ";
|
CLOG(clog) << "neededWeight " << neededWeight << ". ";
|
||||||
|
|
||||||
int participants = currPeerPositions_.size();
|
int participants = currPeerPositions_.size();
|
||||||
@@ -1681,7 +1698,8 @@ Consensus<Adaptor>::haveConsensus(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JLOG(j_.debug()) << nodeId << " has " << peerProp.position();
|
JLOG(j_.debug()) << "Proposal disagreement: Peer " << nodeId
|
||||||
|
<< " has " << peerProp.position();
|
||||||
++disagree;
|
++disagree;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1691,6 +1709,17 @@ Consensus<Adaptor>::haveConsensus(
|
|||||||
JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
|
JLOG(j_.debug()) << "Checking for TX consensus: agree=" << agree
|
||||||
<< ", disagree=" << disagree;
|
<< ", disagree=" << disagree;
|
||||||
|
|
||||||
|
ConsensusParms const& parms = adaptor_.parms();
|
||||||
|
// Stalling is BAD
|
||||||
|
bool const stalled = haveCloseTimeConsensus_ &&
|
||||||
|
std::ranges::all_of(result_->disputes,
|
||||||
|
[this, &parms](auto const& dispute) {
|
||||||
|
return dispute.second.stalled(
|
||||||
|
parms,
|
||||||
|
mode_.get() == ConsensusMode::proposing,
|
||||||
|
peerUnchangedCounter_);
|
||||||
|
});
|
||||||
|
|
||||||
// Determine if we actually have consensus or not
|
// Determine if we actually have consensus or not
|
||||||
result_->state = checkConsensus(
|
result_->state = checkConsensus(
|
||||||
prevProposers_,
|
prevProposers_,
|
||||||
@@ -1699,7 +1728,8 @@ Consensus<Adaptor>::haveConsensus(
|
|||||||
currentFinished,
|
currentFinished,
|
||||||
prevRoundTime_,
|
prevRoundTime_,
|
||||||
result_->roundTime.read(),
|
result_->roundTime.read(),
|
||||||
adaptor_.parms(),
|
stalled,
|
||||||
|
parms,
|
||||||
mode_.get() == ConsensusMode::proposing,
|
mode_.get() == ConsensusMode::proposing,
|
||||||
j_,
|
j_,
|
||||||
clog);
|
clog);
|
||||||
@@ -1710,6 +1740,33 @@ Consensus<Adaptor>::haveConsensus(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Consensus has taken far too long. Drop out of the round.
|
||||||
|
if (result_->state == ConsensusState::Expired)
|
||||||
|
{
|
||||||
|
static auto const minimumCounter =
|
||||||
|
parms.avalancheCutoffs.size() * parms.avMIN_ROUNDS;
|
||||||
|
std::stringstream ss;
|
||||||
|
if (establishCounter_ < minimumCounter)
|
||||||
|
{
|
||||||
|
// If each round of phaseEstablish takes a very long time, we may
|
||||||
|
// "expire" before we've given consensus enough time at each
|
||||||
|
// avalanche level to actually come to a consensus. In that case,
|
||||||
|
// keep trying. This should only happen if there are an extremely
|
||||||
|
// large number of disputes such that each round takes an inordinate
|
||||||
|
// amount of time.
|
||||||
|
|
||||||
|
ss << "Consensus time has expired in round " << establishCounter_
|
||||||
|
<< "; continue until round " << minimumCounter << ". "
|
||||||
|
<< Json::Compact{getJson(false)};
|
||||||
|
JLOG(j_.error()) << ss.str();
|
||||||
|
CLOG(clog) << ss.str() << ". ";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ss << "Consensus expired. " << Json::Compact{getJson(true)};
|
||||||
|
JLOG(j_.error()) << ss.str();
|
||||||
|
CLOG(clog) << ss.str() << ". ";
|
||||||
|
leaveConsensus(clog);
|
||||||
|
}
|
||||||
// There is consensus, but we need to track if the network moved on
|
// There is consensus, but we need to track if the network moved on
|
||||||
// without us.
|
// without us.
|
||||||
if (result_->state == ConsensusState::MovedOn)
|
if (result_->state == ConsensusState::MovedOn)
|
||||||
@@ -1802,8 +1859,9 @@ Consensus<Adaptor>::createDisputes(
|
|||||||
{
|
{
|
||||||
Proposal_t const& peerProp = peerPos.proposal();
|
Proposal_t const& peerProp = peerPos.proposal();
|
||||||
auto const cit = acquired_.find(peerProp.position());
|
auto const cit = acquired_.find(peerProp.position());
|
||||||
if (cit != acquired_.end())
|
if (cit != acquired_.end() &&
|
||||||
dtx.setVote(nodeId, cit->second.exists(txID));
|
dtx.setVote(nodeId, cit->second.exists(txID)))
|
||||||
|
peerUnchangedCounter_ = 0;
|
||||||
}
|
}
|
||||||
adaptor_.share(dtx.tx());
|
adaptor_.share(dtx.tx());
|
||||||
|
|
||||||
@@ -1828,7 +1886,8 @@ Consensus<Adaptor>::updateDisputes(NodeID_t const& node, TxSet_t const& other)
|
|||||||
for (auto& it : result_->disputes)
|
for (auto& it : result_->disputes)
|
||||||
{
|
{
|
||||||
auto& d = it.second;
|
auto& d = it.second;
|
||||||
d.setVote(node, other.exists(d.tx().id()));
|
if (d.setVote(node, other.exists(d.tx().id())))
|
||||||
|
peerUnchangedCounter_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,13 @@
|
|||||||
#ifndef RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED
|
#ifndef RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED
|
||||||
#define RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED
|
#define RIPPLE_CONSENSUS_CONSENSUS_PARMS_H_INCLUDED
|
||||||
|
|
||||||
|
#include <xrpl/beast/utility/instrumentation.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ struct ConsensusParms
|
|||||||
This is a safety to protect against very old validations and the time
|
This is a safety to protect against very old validations and the time
|
||||||
it takes to adjust the close time accuracy window.
|
it takes to adjust the close time accuracy window.
|
||||||
*/
|
*/
|
||||||
std::chrono::seconds validationVALID_WALL = std::chrono::minutes{5};
|
std::chrono::seconds const validationVALID_WALL = std::chrono::minutes{5};
|
||||||
|
|
||||||
/** Duration a validation remains current after first observed.
|
/** Duration a validation remains current after first observed.
|
||||||
|
|
||||||
@@ -51,33 +56,34 @@ struct ConsensusParms
|
|||||||
first saw it. This provides faster recovery in very rare cases where the
|
first saw it. This provides faster recovery in very rare cases where the
|
||||||
number of validations produced by the network is lower than normal
|
number of validations produced by the network is lower than normal
|
||||||
*/
|
*/
|
||||||
std::chrono::seconds validationVALID_LOCAL = std::chrono::minutes{3};
|
std::chrono::seconds const validationVALID_LOCAL = std::chrono::minutes{3};
|
||||||
|
|
||||||
/** Duration pre-close in which validations are acceptable.
|
/** Duration pre-close in which validations are acceptable.
|
||||||
|
|
||||||
The number of seconds before a close time that we consider a validation
|
The number of seconds before a close time that we consider a validation
|
||||||
acceptable. This protects against extreme clock errors
|
acceptable. This protects against extreme clock errors
|
||||||
*/
|
*/
|
||||||
std::chrono::seconds validationVALID_EARLY = std::chrono::minutes{3};
|
std::chrono::seconds const validationVALID_EARLY = std::chrono::minutes{3};
|
||||||
|
|
||||||
//! How long we consider a proposal fresh
|
//! How long we consider a proposal fresh
|
||||||
std::chrono::seconds proposeFRESHNESS = std::chrono::seconds{20};
|
std::chrono::seconds const proposeFRESHNESS = std::chrono::seconds{20};
|
||||||
|
|
||||||
//! How often we force generating a new proposal to keep ours fresh
|
//! How often we force generating a new proposal to keep ours fresh
|
||||||
std::chrono::seconds proposeINTERVAL = std::chrono::seconds{12};
|
std::chrono::seconds const proposeINTERVAL = std::chrono::seconds{12};
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Consensus durations are relative to the internal Consensus clock and use
|
// Consensus durations are relative to the internal Consensus clock and use
|
||||||
// millisecond resolution.
|
// millisecond resolution.
|
||||||
|
|
||||||
//! The percentage threshold above which we can declare consensus.
|
//! The percentage threshold above which we can declare consensus.
|
||||||
std::size_t minCONSENSUS_PCT = 80;
|
std::size_t const minCONSENSUS_PCT = 80;
|
||||||
|
|
||||||
//! The duration a ledger may remain idle before closing
|
//! The duration a ledger may remain idle before closing
|
||||||
std::chrono::milliseconds ledgerIDLE_INTERVAL = std::chrono::seconds{15};
|
std::chrono::milliseconds const ledgerIDLE_INTERVAL =
|
||||||
|
std::chrono::seconds{15};
|
||||||
|
|
||||||
//! The number of seconds we wait minimum to ensure participation
|
//! The number of seconds we wait minimum to ensure participation
|
||||||
std::chrono::milliseconds ledgerMIN_CONSENSUS =
|
std::chrono::milliseconds const ledgerMIN_CONSENSUS =
|
||||||
std::chrono::milliseconds{1950};
|
std::chrono::milliseconds{1950};
|
||||||
|
|
||||||
/** The maximum amount of time to spend pausing for laggards.
|
/** The maximum amount of time to spend pausing for laggards.
|
||||||
@@ -86,13 +92,26 @@ struct ConsensusParms
|
|||||||
* validators don't appear to be offline that are merely waiting for
|
* validators don't appear to be offline that are merely waiting for
|
||||||
* laggards.
|
* laggards.
|
||||||
*/
|
*/
|
||||||
std::chrono::milliseconds ledgerMAX_CONSENSUS = std::chrono::seconds{15};
|
std::chrono::milliseconds const ledgerMAX_CONSENSUS =
|
||||||
|
std::chrono::seconds{15};
|
||||||
|
|
||||||
//! Minimum number of seconds to wait to ensure others have computed the LCL
|
//! Minimum number of seconds to wait to ensure others have computed the LCL
|
||||||
std::chrono::milliseconds ledgerMIN_CLOSE = std::chrono::seconds{2};
|
std::chrono::milliseconds const ledgerMIN_CLOSE = std::chrono::seconds{2};
|
||||||
|
|
||||||
//! How often we check state or change positions
|
//! How often we check state or change positions
|
||||||
std::chrono::milliseconds ledgerGRANULARITY = std::chrono::seconds{1};
|
std::chrono::milliseconds const ledgerGRANULARITY = std::chrono::seconds{1};
|
||||||
|
|
||||||
|
//! How long to wait before completely abandoning consensus
|
||||||
|
std::size_t const ledgerABANDON_CONSENSUS_FACTOR = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum amount of time to give a consensus round
|
||||||
|
*
|
||||||
|
* Does not include the time to build the LCL, so there is no reason for a
|
||||||
|
* round to go this long, regardless of how big the ledger is.
|
||||||
|
*/
|
||||||
|
std::chrono::milliseconds const ledgerABANDON_CONSENSUS =
|
||||||
|
std::chrono::seconds{120};
|
||||||
|
|
||||||
/** The minimum amount of time to consider the previous round
|
/** The minimum amount of time to consider the previous round
|
||||||
to have taken.
|
to have taken.
|
||||||
@@ -104,38 +123,80 @@ struct ConsensusParms
|
|||||||
twice the interval between proposals (0.7s) divided by
|
twice the interval between proposals (0.7s) divided by
|
||||||
the interval between mid and late consensus ([85-50]/100).
|
the interval between mid and late consensus ([85-50]/100).
|
||||||
*/
|
*/
|
||||||
std::chrono::milliseconds avMIN_CONSENSUS_TIME = std::chrono::seconds{5};
|
std::chrono::milliseconds const avMIN_CONSENSUS_TIME =
|
||||||
|
std::chrono::seconds{5};
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Avalanche tuning
|
// Avalanche tuning
|
||||||
// As a function of the percent this round's duration is of the prior round,
|
// As a function of the percent this round's duration is of the prior round,
|
||||||
// we increase the threshold for yes votes to add a transaction to our
|
// we increase the threshold for yes votes to add a transaction to our
|
||||||
// position.
|
// position.
|
||||||
|
enum AvalancheState { init, mid, late, stuck };
|
||||||
//! Percentage of nodes on our UNL that must vote yes
|
struct AvalancheCutoff
|
||||||
std::size_t avINIT_CONSENSUS_PCT = 50;
|
{
|
||||||
|
int const consensusTime;
|
||||||
//! Percentage of previous round duration before we advance
|
std::size_t const consensusPct;
|
||||||
std::size_t avMID_CONSENSUS_TIME = 50;
|
AvalancheState const next;
|
||||||
|
};
|
||||||
//! Percentage of nodes that most vote yes after advancing
|
//! Map the consensus requirement avalanche state to the amount of time that
|
||||||
std::size_t avMID_CONSENSUS_PCT = 65;
|
//! must pass before moving to that state, the agreement percentage required
|
||||||
|
//! at that state, and the next state. "stuck" loops back on itself because
|
||||||
//! Percentage of previous round duration before we advance
|
//! once we're stuck, we're stuck.
|
||||||
std::size_t avLATE_CONSENSUS_TIME = 85;
|
//! This structure allows for "looping" of states if needed.
|
||||||
|
std::map<AvalancheState, AvalancheCutoff> const avalancheCutoffs{
|
||||||
//! Percentage of nodes that most vote yes after advancing
|
// {state, {time, percent, nextState}},
|
||||||
std::size_t avLATE_CONSENSUS_PCT = 70;
|
// Initial state: 50% of nodes must vote yes
|
||||||
|
{init, {0, 50, mid}},
|
||||||
//! Percentage of previous round duration before we are stuck
|
// mid-consensus starts after 50% of the previous round time, and
|
||||||
std::size_t avSTUCK_CONSENSUS_TIME = 200;
|
// requires 65% yes
|
||||||
|
{mid, {50, 65, late}},
|
||||||
//! Percentage of nodes that must vote yes after we are stuck
|
// late consensus starts after 85% time, and requires 70% yes
|
||||||
std::size_t avSTUCK_CONSENSUS_PCT = 95;
|
{late, {85, 70, stuck}},
|
||||||
|
// we're stuck after 2x time, requires 95% yes votes
|
||||||
|
{stuck, {200, 95, stuck}},
|
||||||
|
};
|
||||||
|
|
||||||
//! Percentage of nodes required to reach agreement on ledger close time
|
//! Percentage of nodes required to reach agreement on ledger close time
|
||||||
std::size_t avCT_CONSENSUS_PCT = 75;
|
std::size_t const avCT_CONSENSUS_PCT = 75;
|
||||||
|
|
||||||
|
//! Number of rounds before certain actions can happen.
|
||||||
|
// (Moving to the next avalanche level, considering that votes are stalled
|
||||||
|
// without consensus.)
|
||||||
|
std::size_t const avMIN_ROUNDS = 2;
|
||||||
|
|
||||||
|
//! Number of rounds before a stuck vote is considered unlikely to change
|
||||||
|
//! because voting stalled
|
||||||
|
std::size_t const avSTALLED_ROUNDS = 4;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline std::pair<std::size_t, std::optional<ConsensusParms::AvalancheState>>
|
||||||
|
getNeededWeight(
|
||||||
|
ConsensusParms const& p,
|
||||||
|
ConsensusParms::AvalancheState currentState,
|
||||||
|
int percentTime,
|
||||||
|
std::size_t currentRounds,
|
||||||
|
std::size_t minimumRounds)
|
||||||
|
{
|
||||||
|
// at() can throw, but the map is built by hand to ensure all valid
|
||||||
|
// values are available.
|
||||||
|
auto const& currentCutoff = p.avalancheCutoffs.at(currentState);
|
||||||
|
// Should we consider moving to the next state?
|
||||||
|
if (currentCutoff.next != currentState && currentRounds >= minimumRounds)
|
||||||
|
{
|
||||||
|
// at() can throw, but the map is built by hand to ensure all
|
||||||
|
// valid values are available.
|
||||||
|
auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
|
||||||
|
// See if enough time has passed to move on to the next.
|
||||||
|
XRPL_ASSERT(
|
||||||
|
nextCutoff.consensusTime >= currentCutoff.consensusTime,
|
||||||
|
"ripple::getNeededWeight : next state valid");
|
||||||
|
if (percentTime >= nextCutoff.consensusTime)
|
||||||
|
{
|
||||||
|
return {nextCutoff.consensusPct, currentCutoff.next};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {currentCutoff.consensusPct, {}};
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ripple
|
} // namespace ripple
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ struct ConsensusCloseTimes
|
|||||||
enum class ConsensusState {
|
enum class ConsensusState {
|
||||||
No, //!< We do not have consensus
|
No, //!< We do not have consensus
|
||||||
MovedOn, //!< The network has consensus without us
|
MovedOn, //!< The network has consensus without us
|
||||||
|
Expired, //!< Consensus time limit has hard-expired
|
||||||
Yes //!< We have consensus along with the network
|
Yes //!< We have consensus along with the network
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -237,7 +238,7 @@ struct ConsensusResult
|
|||||||
ConsensusTimer roundTime;
|
ConsensusTimer roundTime;
|
||||||
|
|
||||||
// Indicates state in which consensus ended. Once in the accept phase
|
// Indicates state in which consensus ended. Once in the accept phase
|
||||||
// will be either Yes or MovedOn
|
// will be either Yes or MovedOn or Expired
|
||||||
ConsensusState state = ConsensusState::No;
|
ConsensusState state = ConsensusState::No;
|
||||||
|
|
||||||
// The number of peers proposing during the round
|
// The number of peers proposing during the round
|
||||||
|
|||||||
@@ -82,6 +82,51 @@ public:
|
|||||||
return ourVote_;
|
return ourVote_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Are we and our peers "stalled" where we probably won't change
|
||||||
|
//! our vote?
|
||||||
|
bool
|
||||||
|
stalled(ConsensusParms const& p, bool proposing, int peersUnchanged) const
|
||||||
|
{
|
||||||
|
// at() can throw, but the map is built by hand to ensure all valid
|
||||||
|
// values are available.
|
||||||
|
auto const& currentCutoff = p.avalancheCutoffs.at(avalancheState_);
|
||||||
|
auto const& nextCutoff = p.avalancheCutoffs.at(currentCutoff.next);
|
||||||
|
|
||||||
|
// We're have not reached the final avalanche state, or been there long
|
||||||
|
// enough, so there's room for change. Check the times in case the state
|
||||||
|
// machine is altered to allow states to loop.
|
||||||
|
if (nextCutoff.consensusTime > currentCutoff.consensusTime ||
|
||||||
|
avalancheCounter_ < p.avMIN_ROUNDS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// We've haven't had this vote for minimum rounds yet. Things could
|
||||||
|
// change.
|
||||||
|
if (proposing && currentVoteCounter_ < p.avMIN_ROUNDS)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If we or any peers have changed a vote in several rounds, then
|
||||||
|
// things could still change. But if _either_ has not changed in that
|
||||||
|
// long, we're unlikely to change our vote any time soon. (This prevents
|
||||||
|
// a malicious peer from flip-flopping a vote to prevent consensus.)
|
||||||
|
if (peersUnchanged < p.avSTALLED_ROUNDS &&
|
||||||
|
(proposing && currentVoteCounter_ < p.avSTALLED_ROUNDS))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Does this transaction have more than 80% agreement
|
||||||
|
|
||||||
|
// Compute the percentage of nodes voting 'yes' (possibly including us)
|
||||||
|
int const support = (yays_ + (proposing && ourVote_ ? 1 : 0)) * 100;
|
||||||
|
int total = nays_ + yays_ + (proposing ? 1 : 0);
|
||||||
|
if (!total)
|
||||||
|
// There are no votes, so we know nothing
|
||||||
|
return false;
|
||||||
|
int const weight = support / total;
|
||||||
|
// Returns true if the tx has more than minCONSENSUS_PCT (80) percent
|
||||||
|
// agreement. Either voting for _or_ voting against the tx.
|
||||||
|
return weight > p.minCONSENSUS_PCT ||
|
||||||
|
weight < (100 - p.minCONSENSUS_PCT);
|
||||||
|
}
|
||||||
|
|
||||||
//! The disputed transaction.
|
//! The disputed transaction.
|
||||||
Tx_t const&
|
Tx_t const&
|
||||||
tx() const
|
tx() const
|
||||||
@@ -100,8 +145,12 @@ public:
|
|||||||
|
|
||||||
@param peer Identifier of peer.
|
@param peer Identifier of peer.
|
||||||
@param votesYes Whether peer votes to include the disputed transaction.
|
@param votesYes Whether peer votes to include the disputed transaction.
|
||||||
|
|
||||||
|
@return bool Whether the peer changed its vote. (A new vote counts as a
|
||||||
|
change.)
|
||||||
*/
|
*/
|
||||||
void
|
[[nodiscard]]
|
||||||
|
bool
|
||||||
setVote(NodeID_t const& peer, bool votesYes);
|
setVote(NodeID_t const& peer, bool votesYes);
|
||||||
|
|
||||||
/** Remove a peer's vote
|
/** Remove a peer's vote
|
||||||
@@ -135,12 +184,18 @@ private:
|
|||||||
bool ourVote_; //< Our vote (true is yes)
|
bool ourVote_; //< Our vote (true is yes)
|
||||||
Tx_t tx_; //< Transaction under dispute
|
Tx_t tx_; //< Transaction under dispute
|
||||||
Map_t votes_; //< Map from NodeID to vote
|
Map_t votes_; //< Map from NodeID to vote
|
||||||
|
//! The number of rounds we've gone without changing our vote
|
||||||
|
std::size_t currentVoteCounter_ = 0;
|
||||||
|
//! Which minimum acceptance percentage phase we are currently in
|
||||||
|
ConsensusParms::AvalancheState avalancheState_ = ConsensusParms::init;
|
||||||
|
//! How long we have been in the current acceptance phase
|
||||||
|
std::size_t avalancheCounter_ = 0;
|
||||||
beast::Journal const j_;
|
beast::Journal const j_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Track a peer's yes/no vote on a particular disputed tx_
|
// Track a peer's yes/no vote on a particular disputed tx_
|
||||||
template <class Tx_t, class NodeID_t>
|
template <class Tx_t, class NodeID_t>
|
||||||
void
|
bool
|
||||||
DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
|
DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
|
||||||
{
|
{
|
||||||
auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
|
auto const [it, inserted] = votes_.insert(std::make_pair(peer, votesYes));
|
||||||
@@ -158,6 +213,7 @@ DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
|
|||||||
JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
|
JLOG(j_.debug()) << "Peer " << peer << " votes NO on " << tx_.id();
|
||||||
++nays_;
|
++nays_;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// changes vote to yes
|
// changes vote to yes
|
||||||
else if (votesYes && !it->second)
|
else if (votesYes && !it->second)
|
||||||
@@ -166,6 +222,7 @@ DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
|
|||||||
--nays_;
|
--nays_;
|
||||||
++yays_;
|
++yays_;
|
||||||
it->second = true;
|
it->second = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
// changes vote to no
|
// changes vote to no
|
||||||
else if (!votesYes && it->second)
|
else if (!votesYes && it->second)
|
||||||
@@ -174,7 +231,9 @@ DisputedTx<Tx_t, NodeID_t>::setVote(NodeID_t const& peer, bool votesYes)
|
|||||||
++nays_;
|
++nays_;
|
||||||
--yays_;
|
--yays_;
|
||||||
it->second = false;
|
it->second = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a peer's vote on this disputed transaction
|
// Remove a peer's vote on this disputed transaction
|
||||||
@@ -211,21 +270,26 @@ DisputedTx<Tx_t, NodeID_t>::updateVote(
|
|||||||
bool newPosition;
|
bool newPosition;
|
||||||
int weight;
|
int weight;
|
||||||
|
|
||||||
|
// When proposing, to prevent avalanche stalls, we increase the needed
|
||||||
|
// weight slightly over time. We also need to ensure that the consensus has
|
||||||
|
// made a minimum number of attempts at each "state" before moving
|
||||||
|
// to the next.
|
||||||
|
// Proposing or not, we need to keep track of which state we've reached so
|
||||||
|
// we can determine if the vote has stalled.
|
||||||
|
auto const [requiredPct, newState] = getNeededWeight(
|
||||||
|
p, avalancheState_, percentTime, ++avalancheCounter_, p.avMIN_ROUNDS);
|
||||||
|
if (newState)
|
||||||
|
{
|
||||||
|
avalancheState_ = *newState;
|
||||||
|
avalancheCounter_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (proposing) // give ourselves full weight
|
if (proposing) // give ourselves full weight
|
||||||
{
|
{
|
||||||
// This is basically the percentage of nodes voting 'yes' (including us)
|
// This is basically the percentage of nodes voting 'yes' (including us)
|
||||||
weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
|
weight = (yays_ * 100 + (ourVote_ ? 100 : 0)) / (nays_ + yays_ + 1);
|
||||||
|
|
||||||
// To prevent avalanche stalls, we increase the needed weight slightly
|
newPosition = weight > requiredPct;
|
||||||
// over time.
|
|
||||||
if (percentTime < p.avMID_CONSENSUS_TIME)
|
|
||||||
newPosition = weight > p.avINIT_CONSENSUS_PCT;
|
|
||||||
else if (percentTime < p.avLATE_CONSENSUS_TIME)
|
|
||||||
newPosition = weight > p.avMID_CONSENSUS_PCT;
|
|
||||||
else if (percentTime < p.avSTUCK_CONSENSUS_TIME)
|
|
||||||
newPosition = weight > p.avLATE_CONSENSUS_PCT;
|
|
||||||
else
|
|
||||||
newPosition = weight > p.avSTUCK_CONSENSUS_PCT;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -236,13 +300,16 @@ DisputedTx<Tx_t, NodeID_t>::updateVote(
|
|||||||
|
|
||||||
if (newPosition == ourVote_)
|
if (newPosition == ourVote_)
|
||||||
{
|
{
|
||||||
JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO")
|
++currentVoteCounter_;
|
||||||
<< ") : weight " << weight << ", percent "
|
JLOG(j_.info()) << "No change (" << (ourVote_ ? "YES" : "NO") << ") on "
|
||||||
<< percentTime;
|
<< tx_.id() << " : weight " << weight << ", percent "
|
||||||
|
<< percentTime
|
||||||
|
<< ", round(s) with this vote: " << currentVoteCounter_;
|
||||||
JLOG(j_.debug()) << Json::Compact{getJson()};
|
JLOG(j_.debug()) << Json::Compact{getJson()};
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentVoteCounter_ = 0;
|
||||||
ourVote_ = newPosition;
|
ourVote_ = newPosition;
|
||||||
JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
|
JLOG(j_.debug()) << "We now vote " << (ourVote_ ? "YES" : "NO") << " on "
|
||||||
<< tx_.id();
|
<< tx_.id();
|
||||||
|
|||||||
Reference in New Issue
Block a user