mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-24 13:05:53 +00:00
* Creates a version 2 of the UNL file format allowing publishers to pre-publish the next UNL while the current one is still valid. * Version 1 of the UNL file format is still valid and backward compatible. * Also causes rippled to lock down if it has no valid UNLs, similar to being amendment blocked, except reversible. * Resolves #3548 * Resolves #3470
2117 lines
74 KiB
C++
2117 lines
74 KiB
C++
//-----------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2020 Ripple Labs Inc.
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include <ripple/app/consensus/RCLValidations.h>
|
|
#include <ripple/app/ledger/Ledger.h>
|
|
#include <ripple/app/misc/NegativeUNLVote.h>
|
|
#include <ripple/app/misc/ValidatorList.h>
|
|
#include <ripple/app/tx/apply.h>
|
|
#include <ripple/basics/Log.h>
|
|
#include <ripple/beast/unit_test.h>
|
|
#include <ripple/ledger/View.h>
|
|
#include <ripple/rpc/impl/GRPCHelpers.h>
|
|
#include <test/jtx.h>
|
|
|
|
namespace ripple {
|
|
namespace test {
|
|
|
|
/*
|
|
* This file implements the following negative UNL related tests:
|
|
* -- test filling and applying ttUNL_MODIFY Tx and ledger update
|
|
* -- test ttUNL_MODIFY Tx failure without featureNegativeUNL amendment
|
|
* -- test the NegativeUNLVote class. The test cases are split to multiple
|
|
* test classes to allow parallel execution.
|
|
* -- test the negativeUNLFilter function
|
|
*
|
|
* Other negative UNL related tests such as ValidatorList and RPC related ones
|
|
* are put in their existing unit test files.
|
|
*/
|
|
|
|
/**
|
|
* Test the size of the negative UNL in a ledger,
|
|
* also test if the ledger has ToDisalbe and/or ToReEnable
|
|
*
|
|
* @param l the ledger
|
|
* @param size the expected negative UNL size
|
|
* @param hasToDisable if expect ToDisable in ledger
|
|
* @param hasToReEnable if expect ToDisable in ledger
|
|
* @return true if meet all three expectation
|
|
*/
|
|
bool
|
|
negUnlSizeTest(
|
|
std::shared_ptr<Ledger const> const& l,
|
|
size_t size,
|
|
bool hasToDisable,
|
|
bool hasToReEnable);
|
|
|
|
/**
|
|
* Try to apply a ttUNL_MODIFY Tx, and test the apply result
|
|
*
|
|
* @param env the test environment
|
|
* @param view the OpenView of the ledger
|
|
* @param tx the ttUNL_MODIFY Tx
|
|
* @param pass if the Tx should be applied successfully
|
|
* @return true if meet the expectation of apply result
|
|
*/
|
|
bool
|
|
applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass);
|
|
|
|
/**
|
|
* Verify the content of negative UNL entries (public key and ledger sequence)
|
|
* of a ledger
|
|
*
|
|
* @param l the ledger
|
|
* @param nUnlLedgerSeq the expected PublicKeys and ledger Sequences
|
|
* @note nUnlLedgerSeq is copied so that it can be modified.
|
|
* @return true if meet the expectation
|
|
*/
|
|
bool
|
|
VerifyPubKeyAndSeq(
|
|
std::shared_ptr<Ledger const> const& l,
|
|
hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq);
|
|
|
|
/**
|
|
* Count the number of Tx in a TxSet
|
|
*
|
|
* @param txSet the TxSet
|
|
* @return the number of Tx
|
|
*/
|
|
std::size_t
|
|
countTx(std::shared_ptr<SHAMap> const& txSet);
|
|
|
|
/**
|
|
* Create fake public keys
|
|
*
|
|
* @param n the number of public keys
|
|
* @return a vector of public keys created
|
|
*/
|
|
std::vector<PublicKey>
|
|
createPublicKeys(std::size_t n);
|
|
|
|
/**
|
|
* Create ttUNL_MODIFY Tx
|
|
*
|
|
* @param disabling disabling or re-enabling a validator
|
|
* @param seq current ledger seq
|
|
* @param txKey the public key of the validator
|
|
* @return the ttUNL_MODIFY Tx
|
|
*/
|
|
STTx
|
|
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey);
|
|
|
|
class NegativeUNL_test : public beast::unit_test::suite
|
|
{
|
|
/**
|
|
* Test filling and applying ttUNL_MODIFY Tx, as well as ledger update:
|
|
*
|
|
* We will build a long history of ledgers, and try to apply different
|
|
* ttUNL_MODIFY Txes. We will check if the apply results meet expectations
|
|
* and if the ledgers are updated correctly.
|
|
*/
|
|
void
|
|
testNegativeUNL()
|
|
{
|
|
/*
|
|
* test cases:
|
|
*
|
|
* (1) the ledger after genesis
|
|
* -- cannot apply Disable Tx
|
|
* -- cannot apply ReEnable Tx
|
|
* -- nUNL empty
|
|
* -- no ToDisable
|
|
* -- no ToReEnable
|
|
*
|
|
* (2) a flag ledger
|
|
* -- apply an Disable Tx
|
|
* -- cannot apply the second Disable Tx
|
|
* -- cannot apply a ReEnable Tx
|
|
* -- nUNL empty
|
|
* -- has ToDisable with right nodeId
|
|
* -- no ToReEnable
|
|
* ++ extra test: first Disable Tx in ledger TxSet
|
|
*
|
|
* (3) ledgers before the next flag ledger
|
|
* -- nUNL empty
|
|
* -- has ToDisable with right nodeId
|
|
* -- no ToReEnable
|
|
*
|
|
* (4) next flag ledger
|
|
* -- nUNL size == 1, with right nodeId
|
|
* -- no ToDisable
|
|
* -- no ToReEnable
|
|
* -- cannot apply an Disable Tx with nodeId already in nUNL
|
|
* -- apply an Disable Tx with different nodeId
|
|
* -- cannot apply a ReEnable Tx with the same NodeId as Add
|
|
* -- cannot apply a ReEnable Tx with a NodeId not in nUNL
|
|
* -- apply a ReEnable Tx with a nodeId already in nUNL
|
|
* -- has ToDisable with right nodeId
|
|
* -- has ToReEnable with right nodeId
|
|
* -- nUNL size still 1, right nodeId
|
|
*
|
|
* (5) ledgers before the next flag ledger
|
|
* -- nUNL size == 1, right nodeId
|
|
* -- has ToDisable with right nodeId
|
|
* -- has ToReEnable with right nodeId
|
|
*
|
|
* (6) next flag ledger
|
|
* -- nUNL size == 1, different nodeId
|
|
* -- no ToDisable
|
|
* -- no ToReEnable
|
|
* -- apply an Disable Tx with different nodeId
|
|
* -- nUNL size still 1, right nodeId
|
|
* -- has ToDisable with right nodeId
|
|
* -- no ToReEnable
|
|
*
|
|
* (7) ledgers before the next flag ledger
|
|
* -- nUNL size still 1, right nodeId
|
|
* -- has ToDisable with right nodeId
|
|
* -- no ToReEnable
|
|
*
|
|
* (8) next flag ledger
|
|
* -- nUNL size == 2
|
|
* -- apply a ReEnable Tx
|
|
* -- cannot apply second ReEnable Tx, even with right nodeId
|
|
* -- cannot apply an Disable Tx with the same NodeId as Remove
|
|
* -- nUNL size == 2
|
|
* -- no ToDisable
|
|
* -- has ToReEnable with right nodeId
|
|
*
|
|
* (9) ledgers before the next flag ledger
|
|
* -- nUNL size == 2
|
|
* -- no ToDisable
|
|
* -- has ToReEnable with right nodeId
|
|
*
|
|
* (10) next flag ledger
|
|
* -- nUNL size == 1
|
|
* -- apply a ReEnable Tx
|
|
* -- nUNL size == 1
|
|
* -- no ToDisable
|
|
* -- has ToReEnable with right nodeId
|
|
*
|
|
* (11) ledgers before the next flag ledger
|
|
* -- nUNL size == 1
|
|
* -- no ToDisable
|
|
* -- has ToReEnable with right nodeId
|
|
*
|
|
* (12) next flag ledger
|
|
* -- nUNL size == 0
|
|
* -- no ToDisable
|
|
* -- no ToReEnable
|
|
*
|
|
* (13) ledgers before the next flag ledger
|
|
* -- nUNL size == 0
|
|
* -- no ToDisable
|
|
* -- no ToReEnable
|
|
*
|
|
* (14) next flag ledger
|
|
* -- nUNL size == 0
|
|
* -- no ToDisable
|
|
* -- no ToReEnable
|
|
*/
|
|
|
|
testcase("Create UNLModify Tx and apply to ledgers");
|
|
|
|
jtx::Env env(*this, jtx::supported_amendments() | featureNegativeUNL);
|
|
std::vector<PublicKey> publicKeys = createPublicKeys(3);
|
|
// genesis ledger
|
|
auto l = std::make_shared<Ledger>(
|
|
create_genesis,
|
|
env.app().config(),
|
|
std::vector<uint256>{},
|
|
env.app().getNodeFamily());
|
|
BEAST_EXPECT(l->rules().enabled(featureNegativeUNL));
|
|
|
|
// Record the public keys and ledger sequences of expected negative UNL
|
|
// validators when we build the ledger history
|
|
hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq;
|
|
|
|
{
|
|
//(1) the ledger after genesis, not a flag ledger
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
|
|
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
|
|
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
|
|
|
|
OpenView accum(&*l);
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
|
|
accum.apply(*l);
|
|
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
|
|
}
|
|
|
|
{
|
|
//(2) a flag ledger
|
|
// generate more ledgers
|
|
for (auto i = 0; i < 256 - 2; ++i)
|
|
{
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->isFlagLedger());
|
|
l->updateNegativeUNL();
|
|
|
|
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
|
|
auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
|
|
auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
|
|
|
|
// can apply 1 and only 1 ToDisable Tx,
|
|
// cannot apply ToReEnable Tx, since negative UNL is empty
|
|
OpenView accum(&*l);
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, false));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
|
|
accum.apply(*l);
|
|
auto good_size = negUnlSizeTest(l, 0, true, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
|
|
//++ first ToDisable Tx in ledger's TxSet
|
|
uint256 txID = txDisable_0.getTransactionID();
|
|
BEAST_EXPECT(l->txExists(txID));
|
|
}
|
|
}
|
|
|
|
{
|
|
//(3) ledgers before the next flag ledger
|
|
for (auto i = 0; i < 256; ++i)
|
|
{
|
|
auto good_size = negUnlSizeTest(l, 0, true, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->isFlagLedger());
|
|
l->updateNegativeUNL();
|
|
|
|
//(4) next flag ledger
|
|
// test if the ledger updated correctly
|
|
auto good_size = negUnlSizeTest(l, 1, false, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(*(l->negativeUNL().begin()) == publicKeys[0]);
|
|
nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
|
|
}
|
|
|
|
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
|
|
auto txDisable_1 = createTx(true, l->seq(), publicKeys[1]);
|
|
auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
|
|
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
|
|
auto txReEnable_2 = createTx(false, l->seq(), publicKeys[2]);
|
|
|
|
OpenView accum(&*l);
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_1, true));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_2, false));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
|
|
accum.apply(*l);
|
|
good_size = negUnlSizeTest(l, 1, true, true);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
|
|
BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
|
|
BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
|
|
// test sfFirstLedgerSequence
|
|
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
|
|
}
|
|
}
|
|
|
|
{
|
|
//(5) ledgers before the next flag ledger
|
|
for (auto i = 0; i < 256; ++i)
|
|
{
|
|
auto good_size = negUnlSizeTest(l, 1, true, true);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
|
|
BEAST_EXPECT(l->validatorToDisable() == publicKeys[1]);
|
|
BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
|
|
}
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->isFlagLedger());
|
|
l->updateNegativeUNL();
|
|
|
|
//(6) next flag ledger
|
|
// test if the ledger updated correctly
|
|
auto good_size = negUnlSizeTest(l, 1, false, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
}
|
|
|
|
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
|
|
|
|
OpenView accum(&*l);
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, true));
|
|
accum.apply(*l);
|
|
good_size = negUnlSizeTest(l, 1, true, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
|
|
nUnlLedgerSeq.emplace(publicKeys[1], l->seq());
|
|
nUnlLedgerSeq.erase(publicKeys[0]);
|
|
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
|
|
}
|
|
}
|
|
|
|
{
|
|
//(7) ledgers before the next flag ledger
|
|
for (auto i = 0; i < 256; ++i)
|
|
{
|
|
auto good_size = negUnlSizeTest(l, 1, true, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
BEAST_EXPECT(l->validatorToDisable() == publicKeys[0]);
|
|
}
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->isFlagLedger());
|
|
l->updateNegativeUNL();
|
|
|
|
//(8) next flag ledger
|
|
// test if the ledger updated correctly
|
|
auto good_size = negUnlSizeTest(l, 2, false, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
nUnlLedgerSeq.emplace(publicKeys[0], l->seq());
|
|
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
|
|
}
|
|
|
|
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
|
|
auto txReEnable_0 = createTx(false, l->seq(), publicKeys[0]);
|
|
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
|
|
|
|
OpenView accum(&*l);
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_0, true));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, false));
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
|
|
accum.apply(*l);
|
|
good_size = negUnlSizeTest(l, 2, false, true);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
|
|
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
|
|
}
|
|
}
|
|
|
|
{
|
|
//(9) ledgers before the next flag ledger
|
|
for (auto i = 0; i < 256; ++i)
|
|
{
|
|
auto good_size = negUnlSizeTest(l, 2, false, true);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[0]));
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
BEAST_EXPECT(l->validatorToReEnable() == publicKeys[0]);
|
|
}
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->isFlagLedger());
|
|
l->updateNegativeUNL();
|
|
|
|
//(10) next flag ledger
|
|
// test if the ledger updated correctly
|
|
auto good_size = negUnlSizeTest(l, 1, false, false);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
nUnlLedgerSeq.erase(publicKeys[0]);
|
|
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
|
|
}
|
|
|
|
auto txReEnable_1 = createTx(false, l->seq(), publicKeys[1]);
|
|
|
|
OpenView accum(&*l);
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txReEnable_1, true));
|
|
accum.apply(*l);
|
|
good_size = negUnlSizeTest(l, 1, false, true);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
|
|
BEAST_EXPECT(VerifyPubKeyAndSeq(l, nUnlLedgerSeq));
|
|
}
|
|
}
|
|
|
|
{
|
|
//(11) ledgers before the next flag ledger
|
|
for (auto i = 0; i < 256; ++i)
|
|
{
|
|
auto good_size = negUnlSizeTest(l, 1, false, true);
|
|
BEAST_EXPECT(good_size);
|
|
if (good_size)
|
|
{
|
|
BEAST_EXPECT(l->negativeUNL().count(publicKeys[1]));
|
|
BEAST_EXPECT(l->validatorToReEnable() == publicKeys[1]);
|
|
}
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->isFlagLedger());
|
|
l->updateNegativeUNL();
|
|
|
|
//(12) next flag ledger
|
|
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
|
|
}
|
|
|
|
{
|
|
//(13) ledgers before the next flag ledger
|
|
for (auto i = 0; i < 256; ++i)
|
|
{
|
|
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->isFlagLedger());
|
|
l->updateNegativeUNL();
|
|
|
|
//(14) next flag ledger
|
|
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testNegativeUNL();
|
|
}
|
|
};
|
|
|
|
class NegativeUNLNoAmendment_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testNegativeUNLNoAmendment()
|
|
{
|
|
testcase("No negative UNL amendment");
|
|
|
|
jtx::Env env(*this, jtx::supported_amendments() - featureNegativeUNL);
|
|
std::vector<PublicKey> publicKeys = createPublicKeys(1);
|
|
// genesis ledger
|
|
auto l = std::make_shared<Ledger>(
|
|
create_genesis,
|
|
env.app().config(),
|
|
std::vector<uint256>{},
|
|
env.app().getNodeFamily());
|
|
BEAST_EXPECT(!l->rules().enabled(featureNegativeUNL));
|
|
|
|
// generate more ledgers
|
|
for (auto i = 0; i < 256 - 1; ++i)
|
|
{
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
}
|
|
BEAST_EXPECT(l->seq() == 256);
|
|
auto txDisable_0 = createTx(true, l->seq(), publicKeys[0]);
|
|
OpenView accum(&*l);
|
|
BEAST_EXPECT(applyAndTestResult(env, accum, txDisable_0, false));
|
|
accum.apply(*l);
|
|
BEAST_EXPECT(negUnlSizeTest(l, 0, false, false));
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testNegativeUNLNoAmendment();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Utility class for creating validators and ledger history
|
|
*/
|
|
struct NetworkHistory
|
|
{
|
|
using LedgerHistory = std::vector<std::shared_ptr<Ledger>>;
|
|
/**
|
|
*
|
|
* Only reasonable parameters can be honored,
|
|
* e.g cannot hasToReEnable when nUNLSize == 0
|
|
*/
|
|
struct Parameter
|
|
{
|
|
std::uint32_t numNodes; // number of validators
|
|
std::uint32_t negUNLSize; // size of negative UNL in the last ledger
|
|
bool hasToDisable; // if has ToDisable in the last ledger
|
|
bool hasToReEnable; // if has ToReEnable in the last ledger
|
|
/**
|
|
* if not specified, the number of ledgers in the history is calculated
|
|
* from negUNLSize, hasToDisable, and hasToReEnable
|
|
*/
|
|
std::optional<int> numLedgers;
|
|
};
|
|
|
|
NetworkHistory(beast::unit_test::suite& suite, Parameter const& p)
|
|
: env(suite, jtx::supported_amendments() | featureNegativeUNL)
|
|
, param(p)
|
|
, validations(env.app().getValidations())
|
|
{
|
|
createNodes();
|
|
if (!param.numLedgers)
|
|
param.numLedgers = 256 * (param.negUNLSize + 1);
|
|
goodHistory = createLedgerHistory();
|
|
}
|
|
|
|
void
|
|
createNodes()
|
|
{
|
|
assert(param.numNodes <= 256);
|
|
UNLKeys = createPublicKeys(param.numNodes);
|
|
for (int i = 0; i < param.numNodes; ++i)
|
|
{
|
|
UNLKeySet.insert(UNLKeys[i]);
|
|
UNLNodeIDs.push_back(calcNodeID(UNLKeys[i]));
|
|
UNLNodeIDSet.insert(UNLNodeIDs.back());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* create ledger history and apply needed ttUNL_MODIFY tx at flag ledgers
|
|
* @return
|
|
*/
|
|
bool
|
|
createLedgerHistory()
|
|
{
|
|
static uint256 fake_amemdment; // So we have different genesis ledgers
|
|
auto l = std::make_shared<Ledger>(
|
|
create_genesis,
|
|
env.app().config(),
|
|
std::vector<uint256>{fake_amemdment++},
|
|
env.app().getNodeFamily());
|
|
history.push_back(l);
|
|
|
|
// When putting validators into the negative UNL, we start with
|
|
// validator 0, then validator 1 ...
|
|
int nidx = 0;
|
|
while (l->seq() <= param.numLedgers)
|
|
{
|
|
l = std::make_shared<Ledger>(
|
|
*l, env.app().timeKeeper().closeTime());
|
|
history.push_back(l);
|
|
|
|
if (l->isFlagLedger())
|
|
{
|
|
l->updateNegativeUNL();
|
|
OpenView accum(&*l);
|
|
if (l->negativeUNL().size() < param.negUNLSize)
|
|
{
|
|
auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
|
|
if (!applyAndTestResult(env, accum, tx, true))
|
|
break;
|
|
++nidx;
|
|
}
|
|
else if (l->negativeUNL().size() == param.negUNLSize)
|
|
{
|
|
if (param.hasToDisable)
|
|
{
|
|
auto tx = createTx(true, l->seq(), UNLKeys[nidx]);
|
|
if (!applyAndTestResult(env, accum, tx, true))
|
|
break;
|
|
++nidx;
|
|
}
|
|
if (param.hasToReEnable)
|
|
{
|
|
auto tx = createTx(false, l->seq(), UNLKeys[0]);
|
|
if (!applyAndTestResult(env, accum, tx, true))
|
|
break;
|
|
}
|
|
}
|
|
accum.apply(*l);
|
|
}
|
|
l->updateSkipList();
|
|
}
|
|
return negUnlSizeTest(
|
|
l, param.negUNLSize, param.hasToDisable, param.hasToReEnable);
|
|
}
|
|
|
|
/**
|
|
* Create a validation
|
|
* @param ledger the ledger the validation validates
|
|
* @param v the validator
|
|
* @return the validation
|
|
*/
|
|
std::shared_ptr<STValidation>
|
|
createSTVal(std::shared_ptr<Ledger const> const& ledger, NodeID const& v)
|
|
{
|
|
static auto keyPair = randomKeyPair(KeyType::secp256k1);
|
|
return std::make_shared<STValidation>(
|
|
env.app().timeKeeper().now(),
|
|
keyPair.first,
|
|
keyPair.second,
|
|
v,
|
|
[&](STValidation& v) {
|
|
v.setFieldH256(sfLedgerHash, ledger->info().hash);
|
|
v.setFieldU32(sfLedgerSequence, ledger->seq());
|
|
v.setFlag(vfFullValidation);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Walk the ledger history and create validation messages for the ledgers
|
|
*
|
|
* @tparam NeedValidation a function to decided if a validation is needed
|
|
* @param needVal if a validation is needed for this particular combination
|
|
* of ledger and validator
|
|
*/
|
|
template <class NeedValidation>
|
|
void
|
|
walkHistoryAndAddValidations(NeedValidation&& needVal)
|
|
{
|
|
std::uint32_t curr = 0;
|
|
std::size_t need = 256 + 1;
|
|
// only last 256 + 1 ledgers need validations
|
|
if (history.size() > need)
|
|
curr = history.size() - need;
|
|
for (; curr != history.size(); ++curr)
|
|
{
|
|
for (std::size_t i = 0; i < param.numNodes; ++i)
|
|
{
|
|
if (needVal(history[curr], i))
|
|
{
|
|
RCLValidation v(createSTVal(history[curr], UNLNodeIDs[i]));
|
|
v.setTrusted();
|
|
validations.add(UNLNodeIDs[i], v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Ledger const>
|
|
lastLedger() const
|
|
{
|
|
return history.back();
|
|
}
|
|
|
|
jtx::Env env;
|
|
Parameter param;
|
|
RCLValidations& validations;
|
|
std::vector<PublicKey> UNLKeys;
|
|
hash_set<PublicKey> UNLKeySet;
|
|
std::vector<NodeID> UNLNodeIDs;
|
|
hash_set<NodeID> UNLNodeIDSet;
|
|
LedgerHistory history;
|
|
bool goodHistory;
|
|
};
|
|
|
|
auto defaultPreVote = [](NegativeUNLVote& vote) {};
|
|
/**
|
|
* Create a NegativeUNLVote object. It then creates ttUNL_MODIFY Tx as its vote
|
|
* on negative UNL changes.
|
|
*
|
|
* @tparam PreVote a function to be called before vote
|
|
* @param history the ledger history
|
|
* @param myId the voting validator
|
|
* @param expect the number of ttUNL_MODIFY Tx expected
|
|
* @param pre the PreVote function
|
|
* @return true if the number of ttUNL_MODIFY Txes created meet expectation
|
|
*/
|
|
template <typename PreVote = decltype(defaultPreVote)>
|
|
bool
|
|
voteAndCheck(
|
|
NetworkHistory& history,
|
|
NodeID const& myId,
|
|
std::size_t expect,
|
|
PreVote const& pre = defaultPreVote)
|
|
{
|
|
NegativeUNLVote vote(myId, history.env.journal);
|
|
pre(vote);
|
|
auto txSet = std::make_shared<SHAMap>(
|
|
SHAMapType::TRANSACTION, history.env.app().getNodeFamily());
|
|
vote.doVoting(
|
|
history.lastLedger(), history.UNLKeySet, history.validations, txSet);
|
|
return countTx(txSet) == expect;
|
|
}
|
|
|
|
/**
|
|
* Test the private member functions of NegativeUNLVote
|
|
*/
|
|
class NegativeUNLVoteInternal_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testAddTx()
|
|
{
|
|
testcase("Create UNLModify Tx");
|
|
jtx::Env env(*this);
|
|
|
|
NodeID myId(0xA0);
|
|
NegativeUNLVote vote(myId, env.journal);
|
|
|
|
// one add, one remove
|
|
auto txSet = std::make_shared<SHAMap>(
|
|
SHAMapType::TRANSACTION, env.app().getNodeFamily());
|
|
PublicKey toDisableKey;
|
|
PublicKey toReEnableKey;
|
|
LedgerIndex seq(1234);
|
|
BEAST_EXPECT(countTx(txSet) == 0);
|
|
vote.addTx(seq, toDisableKey, NegativeUNLVote::ToDisable, txSet);
|
|
BEAST_EXPECT(countTx(txSet) == 1);
|
|
vote.addTx(seq, toReEnableKey, NegativeUNLVote::ToReEnable, txSet);
|
|
BEAST_EXPECT(countTx(txSet) == 2);
|
|
// content of a tx is implicitly tested after applied to a ledger
|
|
// in later test cases
|
|
}
|
|
|
|
void
|
|
testPickOneCandidate()
|
|
{
|
|
testcase("Pick One Candidate");
|
|
jtx::Env env(*this);
|
|
|
|
NodeID myId(0xA0);
|
|
NegativeUNLVote vote(myId, env.journal);
|
|
|
|
uint256 pad_0(0);
|
|
uint256 pad_f = ~pad_0;
|
|
NodeID n_1(1);
|
|
NodeID n_2(2);
|
|
NodeID n_3(3);
|
|
std::vector<NodeID> candidates({n_1});
|
|
BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
|
|
BEAST_EXPECT(vote.choose(pad_f, candidates) == n_1);
|
|
candidates.emplace_back(2);
|
|
BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
|
|
BEAST_EXPECT(vote.choose(pad_f, candidates) == n_2);
|
|
candidates.emplace_back(3);
|
|
BEAST_EXPECT(vote.choose(pad_0, candidates) == n_1);
|
|
BEAST_EXPECT(vote.choose(pad_f, candidates) == n_3);
|
|
}
|
|
|
|
void
|
|
testBuildScoreTableSpecialCases()
|
|
{
|
|
testcase("Build Score Table");
|
|
/*
|
|
* 1. no skip list
|
|
* 2. short skip list
|
|
* 3. local node not enough history
|
|
* 4. a node double validated some seq
|
|
* 5. local node had enough validations but on a wrong chain
|
|
* 6. a good case, long enough history and perfect scores
|
|
*/
|
|
{
|
|
// 1. no skip list
|
|
NetworkHistory history = {*this, {10, 0, false, false, 1}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
NegativeUNLVote vote(
|
|
history.UNLNodeIDs[3], history.env.journal);
|
|
BEAST_EXPECT(!vote.buildScoreTable(
|
|
history.lastLedger(),
|
|
history.UNLNodeIDSet,
|
|
history.validations));
|
|
}
|
|
}
|
|
|
|
{
|
|
// 2. short skip list
|
|
NetworkHistory history = {*this, {10, 0, false, false, 256 / 2}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
NegativeUNLVote vote(
|
|
history.UNLNodeIDs[3], history.env.journal);
|
|
BEAST_EXPECT(!vote.buildScoreTable(
|
|
history.lastLedger(),
|
|
history.UNLNodeIDSet,
|
|
history.validations));
|
|
}
|
|
}
|
|
|
|
{
|
|
// 3. local node not enough history
|
|
NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
NodeID myId = history.UNLNodeIDs[3];
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool {
|
|
// skip half my validations.
|
|
return !(
|
|
history.UNLNodeIDs[idx] == myId &&
|
|
l->seq() % 2 == 0);
|
|
});
|
|
NegativeUNLVote vote(myId, history.env.journal);
|
|
BEAST_EXPECT(!vote.buildScoreTable(
|
|
history.lastLedger(),
|
|
history.UNLNodeIDSet,
|
|
history.validations));
|
|
}
|
|
}
|
|
|
|
{
|
|
// 4. a node double validated some seq
|
|
// 5. local node had enough validations but on a wrong chain
|
|
NetworkHistory history = {*this, {10, 0, false, false, 256 + 2}};
|
|
// We need two chains for these tests
|
|
bool wrongChainSuccess = history.goodHistory;
|
|
BEAST_EXPECT(wrongChainSuccess);
|
|
NetworkHistory::LedgerHistory wrongChain =
|
|
std::move(history.history);
|
|
// Create a new chain and use it as the one that majority of nodes
|
|
// follow
|
|
history.createLedgerHistory();
|
|
BEAST_EXPECT(history.goodHistory);
|
|
|
|
if (history.goodHistory && wrongChainSuccess)
|
|
{
|
|
NodeID myId = history.UNLNodeIDs[3];
|
|
NodeID badNode = history.UNLNodeIDs[4];
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool {
|
|
// everyone but me
|
|
return !(history.UNLNodeIDs[idx] == myId);
|
|
});
|
|
|
|
// local node validate wrong chain
|
|
// a node double validates
|
|
for (auto& l : wrongChain)
|
|
{
|
|
RCLValidation v1(history.createSTVal(l, myId));
|
|
history.validations.add(myId, v1);
|
|
RCLValidation v2(history.createSTVal(l, badNode));
|
|
history.validations.add(badNode, v2);
|
|
}
|
|
|
|
NegativeUNLVote vote(myId, history.env.journal);
|
|
|
|
// local node still on wrong chain, can build a scoreTable,
|
|
// but all other nodes' scores are zero
|
|
auto scoreTable = vote.buildScoreTable(
|
|
wrongChain.back(),
|
|
history.UNLNodeIDSet,
|
|
history.validations);
|
|
BEAST_EXPECT(scoreTable);
|
|
if (scoreTable)
|
|
{
|
|
for (auto const& [n, score] : *scoreTable)
|
|
{
|
|
if (n == myId)
|
|
BEAST_EXPECT(score == 256);
|
|
else
|
|
BEAST_EXPECT(score == 0);
|
|
}
|
|
}
|
|
|
|
// if local node switched to right history, but cannot build
|
|
// scoreTable because not enough local validations
|
|
BEAST_EXPECT(!vote.buildScoreTable(
|
|
history.lastLedger(),
|
|
history.UNLNodeIDSet,
|
|
history.validations));
|
|
}
|
|
}
|
|
|
|
{
|
|
// 6. a good case
|
|
NetworkHistory history = {*this, {10, 0, false, false, 256 + 1}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return true; });
|
|
NegativeUNLVote vote(
|
|
history.UNLNodeIDs[3], history.env.journal);
|
|
auto scoreTable = vote.buildScoreTable(
|
|
history.lastLedger(),
|
|
history.UNLNodeIDSet,
|
|
history.validations);
|
|
BEAST_EXPECT(scoreTable);
|
|
if (scoreTable)
|
|
{
|
|
for (auto const& [_, score] : *scoreTable)
|
|
{
|
|
(void)_;
|
|
BEAST_EXPECT(score == 256);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find all candidates and check if the number of candidates meets
|
|
* expectation
|
|
*
|
|
* @param vote the NegativeUNLVote object
|
|
* @param unl the validators
|
|
* @param negUnl the negative UNL validators
|
|
* @param scoreTable the score table of validators
|
|
* @param numDisable number of Disable candidates expected
|
|
* @param numReEnable number of ReEnable candidates expected
|
|
* @return true if the number of candidates meets expectation
|
|
*/
|
|
bool
|
|
checkCandidateSizes(
|
|
NegativeUNLVote& vote,
|
|
hash_set<NodeID> const& unl,
|
|
hash_set<NodeID> const& negUnl,
|
|
hash_map<NodeID, std::uint32_t> const& scoreTable,
|
|
std::size_t numDisable,
|
|
std::size_t numReEnable)
|
|
{
|
|
auto [disableCandidates, reEnableCandidates] =
|
|
vote.findAllCandidates(unl, negUnl, scoreTable);
|
|
bool rightDisable = disableCandidates.size() == numDisable;
|
|
bool rightReEnable = reEnableCandidates.size() == numReEnable;
|
|
return rightDisable && rightReEnable;
|
|
};
|
|
|
|
void
|
|
testFindAllCandidates()
|
|
{
|
|
testcase("Find All Candidates");
|
|
/*
|
|
* -- unl size: 35
|
|
* -- negUnl size: 3
|
|
*
|
|
* 0. all good scores
|
|
* 1. all bad scores
|
|
* 2. all between watermarks
|
|
* 3. 2 good scorers in negUnl
|
|
* 4. 2 bad scorers not in negUnl
|
|
* 5. 2 in negUnl but not in unl, have a remove candidate from score
|
|
* table
|
|
* 6. 2 in negUnl but not in unl, no remove candidate from score table
|
|
* 7. 2 new validators have good scores, already in negUnl
|
|
* 8. 2 new validators have bad scores, not in negUnl
|
|
* 9. expired the new validators have bad scores, not in negUnl
|
|
*/
|
|
NetworkHistory history = {*this, {35, 0, false, false, 0}};
|
|
|
|
hash_set<NodeID> negUnl_012;
|
|
for (std::uint32_t i = 0; i < 3; ++i)
|
|
negUnl_012.insert(history.UNLNodeIDs[i]);
|
|
|
|
// build a good scoreTable to use, or copy and modify
|
|
hash_map<NodeID, std::uint32_t> goodScoreTable;
|
|
for (auto const& n : history.UNLNodeIDs)
|
|
goodScoreTable[n] = NegativeUNLVote::negativeUNLHighWaterMark + 1;
|
|
|
|
NegativeUNLVote vote(history.UNLNodeIDs[0], history.env.journal);
|
|
|
|
{
|
|
// all good scores
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, history.UNLNodeIDSet, negUnl_012, goodScoreTable, 0, 3));
|
|
}
|
|
{
|
|
// all bad scores
|
|
hash_map<NodeID, std::uint32_t> scoreTable;
|
|
for (auto& n : history.UNLNodeIDs)
|
|
scoreTable[n] = NegativeUNLVote::negativeUNLLowWaterMark - 1;
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 35 - 3, 0));
|
|
}
|
|
{
|
|
// all between watermarks
|
|
hash_map<NodeID, std::uint32_t> scoreTable;
|
|
for (auto& n : history.UNLNodeIDs)
|
|
scoreTable[n] = NegativeUNLVote::negativeUNLLowWaterMark + 1;
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 0));
|
|
}
|
|
|
|
{
|
|
// 2 good scorers in negUnl
|
|
auto scoreTable = goodScoreTable;
|
|
scoreTable[*negUnl_012.begin()] =
|
|
NegativeUNLVote::negativeUNLLowWaterMark + 1;
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 0, 2));
|
|
}
|
|
|
|
{
|
|
// 2 bad scorers not in negUnl
|
|
auto scoreTable = goodScoreTable;
|
|
scoreTable[history.UNLNodeIDs[11]] =
|
|
NegativeUNLVote::negativeUNLLowWaterMark - 1;
|
|
scoreTable[history.UNLNodeIDs[12]] =
|
|
NegativeUNLVote::negativeUNLLowWaterMark - 1;
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, history.UNLNodeIDSet, negUnl_012, scoreTable, 2, 3));
|
|
}
|
|
|
|
{
|
|
// 2 in negUnl but not in unl, have a remove candidate from score
|
|
// table
|
|
hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
|
|
UNL_temp.erase(history.UNLNodeIDs[0]);
|
|
UNL_temp.erase(history.UNLNodeIDs[1]);
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, UNL_temp, negUnl_012, goodScoreTable, 0, 3));
|
|
}
|
|
|
|
{
|
|
// 2 in negUnl but not in unl, no remove candidate from score table
|
|
auto scoreTable = goodScoreTable;
|
|
scoreTable.erase(history.UNLNodeIDs[0]);
|
|
scoreTable.erase(history.UNLNodeIDs[1]);
|
|
scoreTable[history.UNLNodeIDs[2]] =
|
|
NegativeUNLVote::negativeUNLLowWaterMark + 1;
|
|
hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
|
|
UNL_temp.erase(history.UNLNodeIDs[0]);
|
|
UNL_temp.erase(history.UNLNodeIDs[1]);
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, UNL_temp, negUnl_012, scoreTable, 0, 2));
|
|
}
|
|
|
|
{
|
|
// 2 new validators
|
|
NodeID new_1(0xbead);
|
|
NodeID new_2(0xbeef);
|
|
hash_set<NodeID> nowTrusted = {new_1, new_2};
|
|
hash_set<NodeID> UNL_temp = history.UNLNodeIDSet;
|
|
UNL_temp.insert(new_1);
|
|
UNL_temp.insert(new_2);
|
|
vote.newValidators(256, nowTrusted);
|
|
{
|
|
// 2 new validators have good scores, already in negUnl
|
|
auto scoreTable = goodScoreTable;
|
|
scoreTable[new_1] =
|
|
NegativeUNLVote::negativeUNLHighWaterMark + 1;
|
|
scoreTable[new_2] =
|
|
NegativeUNLVote::negativeUNLHighWaterMark + 1;
|
|
hash_set<NodeID> negUnl_temp = negUnl_012;
|
|
negUnl_temp.insert(new_1);
|
|
negUnl_temp.insert(new_2);
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, UNL_temp, negUnl_temp, scoreTable, 0, 3 + 2));
|
|
}
|
|
{
|
|
// 2 new validators have bad scores, not in negUnl
|
|
auto scoreTable = goodScoreTable;
|
|
scoreTable[new_1] = 0;
|
|
scoreTable[new_2] = 0;
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, UNL_temp, negUnl_012, scoreTable, 0, 3));
|
|
}
|
|
{
|
|
// expired the new validators have bad scores, not in negUnl
|
|
vote.purgeNewValidators(
|
|
256 + NegativeUNLVote::newValidatorDisableSkip + 1);
|
|
auto scoreTable = goodScoreTable;
|
|
scoreTable[new_1] = 0;
|
|
scoreTable[new_2] = 0;
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote, UNL_temp, negUnl_012, scoreTable, 2, 3));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testFindAllCandidatesCombination()
|
|
{
|
|
testcase("Find All Candidates Combination");
|
|
/*
|
|
* == combination 1:
|
|
* -- unl size: 34, 35, 80
|
|
* -- nUnl size: 0, 50%, all
|
|
* -- score pattern: all 0, all negativeUNLLowWaterMark & +1 & -1, all
|
|
* negativeUNLHighWaterMark & +1 & -1, all 100%
|
|
*
|
|
* == combination 2:
|
|
* -- unl size: 34, 35, 80
|
|
* -- negativeUNL size: 0, all
|
|
* -- nUnl size: one on, one off, one on, one off,
|
|
* -- score pattern: 2*(negativeUNLLowWaterMark, +1, -1) &
|
|
* 2*(negativeUNLHighWaterMark, +1, -1) & rest
|
|
* negativeUNLMinLocalValsToVote
|
|
*/
|
|
|
|
jtx::Env env(*this);
|
|
|
|
NodeID myId(0xA0);
|
|
NegativeUNLVote vote(myId, env.journal);
|
|
|
|
std::array<std::uint32_t, 3> unlSizes = {34, 35, 80};
|
|
std::array<std::uint32_t, 3> nUnlPercent = {0, 50, 100};
|
|
std::array<std::uint32_t, 8> scores = {
|
|
0,
|
|
NegativeUNLVote::negativeUNLLowWaterMark - 1,
|
|
NegativeUNLVote::negativeUNLLowWaterMark,
|
|
NegativeUNLVote::negativeUNLLowWaterMark + 1,
|
|
NegativeUNLVote::negativeUNLHighWaterMark - 1,
|
|
NegativeUNLVote::negativeUNLHighWaterMark,
|
|
NegativeUNLVote::negativeUNLHighWaterMark + 1,
|
|
NegativeUNLVote::negativeUNLMinLocalValsToVote};
|
|
|
|
//== combination 1:
|
|
{
|
|
auto fillScoreTable =
|
|
[&](std::uint32_t unl_size,
|
|
std::uint32_t nUnl_size,
|
|
std::uint32_t score,
|
|
hash_set<NodeID>& unl,
|
|
hash_set<NodeID>& negUnl,
|
|
hash_map<NodeID, std::uint32_t>& scoreTable) {
|
|
std::vector<NodeID> nodeIDs;
|
|
std::vector<PublicKey> keys = createPublicKeys(unl_size);
|
|
for (auto const& k : keys)
|
|
{
|
|
nodeIDs.emplace_back(calcNodeID(k));
|
|
unl.emplace(nodeIDs.back());
|
|
scoreTable[nodeIDs.back()] = score;
|
|
}
|
|
for (std::uint32_t i = 0; i < nUnl_size; ++i)
|
|
negUnl.insert(nodeIDs[i]);
|
|
};
|
|
|
|
for (auto us : unlSizes)
|
|
{
|
|
for (auto np : nUnlPercent)
|
|
{
|
|
for (auto score : scores)
|
|
{
|
|
hash_set<NodeID> unl;
|
|
hash_set<NodeID> negUnl;
|
|
hash_map<NodeID, std::uint32_t> scoreTable;
|
|
fillScoreTable(
|
|
us, us * np / 100, score, unl, negUnl, scoreTable);
|
|
BEAST_EXPECT(unl.size() == us);
|
|
BEAST_EXPECT(negUnl.size() == us * np / 100);
|
|
BEAST_EXPECT(scoreTable.size() == us);
|
|
|
|
std::size_t toDisable_expect = 0;
|
|
std::size_t toReEnable_expect = 0;
|
|
if (np == 0)
|
|
{
|
|
if (score <
|
|
NegativeUNLVote::negativeUNLLowWaterMark)
|
|
{
|
|
toDisable_expect = us;
|
|
}
|
|
}
|
|
else if (np == 50)
|
|
{
|
|
if (score >
|
|
NegativeUNLVote::negativeUNLHighWaterMark)
|
|
{
|
|
toReEnable_expect = us * np / 100;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (score >
|
|
NegativeUNLVote::negativeUNLHighWaterMark)
|
|
{
|
|
toReEnable_expect = us;
|
|
}
|
|
}
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote,
|
|
unl,
|
|
negUnl,
|
|
scoreTable,
|
|
toDisable_expect,
|
|
toReEnable_expect));
|
|
}
|
|
}
|
|
}
|
|
|
|
//== combination 2:
|
|
{
|
|
auto fillScoreTable =
|
|
[&](std::uint32_t unl_size,
|
|
std::uint32_t nUnl_percent,
|
|
hash_set<NodeID>& unl,
|
|
hash_set<NodeID>& negUnl,
|
|
hash_map<NodeID, std::uint32_t>& scoreTable) {
|
|
std::vector<NodeID> nodeIDs;
|
|
std::vector<PublicKey> keys =
|
|
createPublicKeys(unl_size);
|
|
for (auto const& k : keys)
|
|
{
|
|
nodeIDs.emplace_back(calcNodeID(k));
|
|
unl.emplace(nodeIDs.back());
|
|
}
|
|
|
|
std::uint32_t nIdx = 0;
|
|
for (auto score : scores)
|
|
{
|
|
scoreTable[nodeIDs[nIdx++]] = score;
|
|
scoreTable[nodeIDs[nIdx++]] = score;
|
|
}
|
|
for (; nIdx < unl_size;)
|
|
{
|
|
scoreTable[nodeIDs[nIdx++]] = scores.back();
|
|
}
|
|
|
|
if (nUnl_percent == 100)
|
|
{
|
|
negUnl = unl;
|
|
}
|
|
else if (nUnl_percent == 50)
|
|
{
|
|
for (std::uint32_t i = 1; i < unl_size; i += 2)
|
|
negUnl.insert(nodeIDs[i]);
|
|
}
|
|
};
|
|
|
|
for (auto us : unlSizes)
|
|
{
|
|
for (auto np : nUnlPercent)
|
|
{
|
|
hash_set<NodeID> unl;
|
|
hash_set<NodeID> negUnl;
|
|
hash_map<NodeID, std::uint32_t> scoreTable;
|
|
|
|
fillScoreTable(us, np, unl, negUnl, scoreTable);
|
|
BEAST_EXPECT(unl.size() == us);
|
|
BEAST_EXPECT(negUnl.size() == us * np / 100);
|
|
BEAST_EXPECT(scoreTable.size() == us);
|
|
|
|
std::size_t toDisable_expect = 0;
|
|
std::size_t toReEnable_expect = 0;
|
|
if (np == 0)
|
|
{
|
|
toDisable_expect = 4;
|
|
}
|
|
else if (np == 50)
|
|
{
|
|
toReEnable_expect = negUnl.size() - 6;
|
|
}
|
|
else
|
|
{
|
|
toReEnable_expect = negUnl.size() - 12;
|
|
}
|
|
BEAST_EXPECT(checkCandidateSizes(
|
|
vote,
|
|
unl,
|
|
negUnl,
|
|
scoreTable,
|
|
toDisable_expect,
|
|
toReEnable_expect));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
testNewValidators()
|
|
{
|
|
testcase("New Validators");
|
|
jtx::Env env(*this);
|
|
|
|
NodeID myId(0xA0);
|
|
NegativeUNLVote vote(myId, env.journal);
|
|
|
|
// test cases:
|
|
// newValidators_ of the NegativeUNLVote empty, add one
|
|
// add a new one and one already added
|
|
// add a new one and some already added
|
|
// purge and see some are expired
|
|
|
|
NodeID n1(0xA1);
|
|
NodeID n2(0xA2);
|
|
NodeID n3(0xA3);
|
|
|
|
vote.newValidators(2, {n1});
|
|
BEAST_EXPECT(vote.newValidators_.size() == 1);
|
|
if (vote.newValidators_.size() == 1)
|
|
{
|
|
BEAST_EXPECT(vote.newValidators_.begin()->first == n1);
|
|
BEAST_EXPECT(vote.newValidators_.begin()->second == 2);
|
|
}
|
|
|
|
vote.newValidators(3, {n1, n2});
|
|
BEAST_EXPECT(vote.newValidators_.size() == 2);
|
|
if (vote.newValidators_.size() == 2)
|
|
{
|
|
BEAST_EXPECT(vote.newValidators_[n1] == 2);
|
|
BEAST_EXPECT(vote.newValidators_[n2] == 3);
|
|
}
|
|
|
|
vote.newValidators(
|
|
NegativeUNLVote::newValidatorDisableSkip, {n1, n2, n3});
|
|
BEAST_EXPECT(vote.newValidators_.size() == 3);
|
|
if (vote.newValidators_.size() == 3)
|
|
{
|
|
BEAST_EXPECT(vote.newValidators_[n1] == 2);
|
|
BEAST_EXPECT(vote.newValidators_[n2] == 3);
|
|
BEAST_EXPECT(
|
|
vote.newValidators_[n3] ==
|
|
NegativeUNLVote::newValidatorDisableSkip);
|
|
}
|
|
|
|
vote.purgeNewValidators(NegativeUNLVote::newValidatorDisableSkip + 2);
|
|
BEAST_EXPECT(vote.newValidators_.size() == 3);
|
|
vote.purgeNewValidators(NegativeUNLVote::newValidatorDisableSkip + 3);
|
|
BEAST_EXPECT(vote.newValidators_.size() == 2);
|
|
vote.purgeNewValidators(NegativeUNLVote::newValidatorDisableSkip + 4);
|
|
BEAST_EXPECT(vote.newValidators_.size() == 1);
|
|
BEAST_EXPECT(vote.newValidators_.begin()->first == n3);
|
|
BEAST_EXPECT(
|
|
vote.newValidators_.begin()->second ==
|
|
NegativeUNLVote::newValidatorDisableSkip);
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testAddTx();
|
|
testPickOneCandidate();
|
|
testBuildScoreTableSpecialCases();
|
|
testFindAllCandidates();
|
|
testFindAllCandidatesCombination();
|
|
testNewValidators();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Rest the build score table function of NegativeUNLVote.
|
|
* This was a part of NegativeUNLVoteInternal. It is redundant and has long
|
|
* runtime. So we separate it out as a manual test.
|
|
*/
|
|
class NegativeUNLVoteScoreTable_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testBuildScoreTableCombination()
|
|
{
|
|
testcase("Build Score Table Combination");
|
|
/*
|
|
* local node good history, correct scores:
|
|
* == combination:
|
|
* -- unl size: 10, 34, 35, 50
|
|
* -- score pattern: all 0, all 50%, all 100%, two 0% two 50% rest 100%
|
|
*/
|
|
std::array<std::uint32_t, 4> unlSizes = {10, 34, 35, 50};
|
|
std::array<std::array<std::uint32_t, 3>, 4> scorePattern = {
|
|
{{{0, 0, 0}}, {{50, 50, 50}}, {{100, 100, 100}}, {{0, 50, 100}}}};
|
|
|
|
for (auto unlSize : unlSizes)
|
|
{
|
|
for (std::uint32_t sp = 0; sp < 4; ++sp)
|
|
{
|
|
NetworkHistory history = {
|
|
*this, {unlSize, 0, false, false, 256 + 2}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
NodeID myId = history.UNLNodeIDs[3];
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool {
|
|
std::size_t k;
|
|
if (idx < 2)
|
|
k = 0;
|
|
else if (idx < 4)
|
|
k = 1;
|
|
else
|
|
k = 2;
|
|
|
|
bool add_50 =
|
|
scorePattern[sp][k] == 50 && l->seq() % 2 == 0;
|
|
bool add_100 = scorePattern[sp][k] == 100;
|
|
bool add_me = history.UNLNodeIDs[idx] == myId;
|
|
return add_50 || add_100 || add_me;
|
|
});
|
|
|
|
NegativeUNLVote vote(myId, history.env.journal);
|
|
auto scoreTable = vote.buildScoreTable(
|
|
history.lastLedger(),
|
|
history.UNLNodeIDSet,
|
|
history.validations);
|
|
BEAST_EXPECT(scoreTable);
|
|
if (scoreTable)
|
|
{
|
|
std::uint32_t i = 0; // looping unl
|
|
auto checkScores = [&](std::uint32_t score,
|
|
std::uint32_t k) -> bool {
|
|
if (history.UNLNodeIDs[i] == myId)
|
|
return score == 256;
|
|
if (scorePattern[sp][k] == 0)
|
|
return score == 0;
|
|
if (scorePattern[sp][k] == 50)
|
|
return score == 256 / 2;
|
|
if (scorePattern[sp][k] == 100)
|
|
return score == 256;
|
|
else
|
|
return false;
|
|
};
|
|
for (; i < 2; ++i)
|
|
{
|
|
BEAST_EXPECT(checkScores(
|
|
(*scoreTable)[history.UNLNodeIDs[i]], 0));
|
|
}
|
|
for (; i < 4; ++i)
|
|
{
|
|
BEAST_EXPECT(checkScores(
|
|
(*scoreTable)[history.UNLNodeIDs[i]], 1));
|
|
}
|
|
for (; i < unlSize; ++i)
|
|
{
|
|
BEAST_EXPECT(checkScores(
|
|
(*scoreTable)[history.UNLNodeIDs[i]], 2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testBuildScoreTableCombination();
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Test the doVoting function of NegativeUNLVote.
|
|
* The test cases are split to 5 classes for parallel execution.
|
|
*
|
|
* Voting tests: (use hasToDisable and hasToReEnable in some of the cases)
|
|
*
|
|
* == all good score, nUnl empty
|
|
* -- txSet.size = 0
|
|
* == all good score, nUnl not empty (use hasToDisable)
|
|
* -- txSet.size = 1
|
|
*
|
|
* == 2 nodes offline, nUnl empty (use hasToReEnable)
|
|
* -- txSet.size = 1
|
|
* == 2 nodes offline, in nUnl
|
|
* -- txSet.size = 0
|
|
*
|
|
* == 2 nodes offline, not in nUnl, but maxListed
|
|
* -- txSet.size = 0
|
|
*
|
|
* == 2 nodes offline including me, not in nUnl
|
|
* -- txSet.size = 0
|
|
* == 2 nodes offline, not in negativeUNL, but I'm not a validator
|
|
* -- txSet.size = 0
|
|
* == 2 in nUnl, but not in unl, no other remove candidates
|
|
* -- txSet.size = 1
|
|
*
|
|
* == 2 new validators have bad scores
|
|
* -- txSet.size = 0
|
|
* == 2 expired new validators have bad scores
|
|
* -- txSet.size = 1
|
|
*/
|
|
|
|
class NegativeUNLVoteGoodScore_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testDoVoting()
|
|
{
|
|
testcase("Do Voting");
|
|
|
|
{
|
|
//== all good score, negativeUNL empty
|
|
//-- txSet.size = 0
|
|
NetworkHistory history = {*this, {51, 0, false, false, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return true; });
|
|
BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
|
|
}
|
|
}
|
|
|
|
{
|
|
// all good score, negativeUNL not empty (use hasToDisable)
|
|
//-- txSet.size = 1
|
|
NetworkHistory history = {*this, {37, 0, true, false, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return true; });
|
|
BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testDoVoting();
|
|
}
|
|
};
|
|
|
|
class NegativeUNLVoteOffline_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testDoVoting()
|
|
{
|
|
testcase("Do Voting");
|
|
|
|
{
|
|
//== 2 nodes offline, negativeUNL empty (use hasToReEnable)
|
|
//-- txSet.size = 1
|
|
NetworkHistory history = {*this, {29, 1, false, true, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool {
|
|
// skip node 0 and node 1
|
|
return idx > 1;
|
|
});
|
|
BEAST_EXPECT(
|
|
voteAndCheck(history, history.UNLNodeIDs.back(), 1));
|
|
}
|
|
}
|
|
|
|
{
|
|
// 2 nodes offline, in negativeUNL
|
|
//-- txSet.size = 0
|
|
NetworkHistory history = {*this, {30, 1, true, false, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
NodeID n1 =
|
|
calcNodeID(*history.lastLedger()->negativeUNL().begin());
|
|
NodeID n2 =
|
|
calcNodeID(*history.lastLedger()->validatorToDisable());
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool {
|
|
// skip node 0 and node 1
|
|
return history.UNLNodeIDs[idx] != n1 &&
|
|
history.UNLNodeIDs[idx] != n2;
|
|
});
|
|
BEAST_EXPECT(
|
|
voteAndCheck(history, history.UNLNodeIDs.back(), 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testDoVoting();
|
|
}
|
|
};
|
|
|
|
class NegativeUNLVoteMaxListed_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testDoVoting()
|
|
{
|
|
testcase("Do Voting");
|
|
|
|
{
|
|
// 2 nodes offline, not in negativeUNL, but maxListed
|
|
//-- txSet.size = 0
|
|
NetworkHistory history = {*this, {32, 8, true, true, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool {
|
|
// skip node 0 ~ 10
|
|
return idx > 10;
|
|
});
|
|
BEAST_EXPECT(
|
|
voteAndCheck(history, history.UNLNodeIDs.back(), 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testDoVoting();
|
|
}
|
|
};
|
|
|
|
class NegativeUNLVoteRetiredValidator_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testDoVoting()
|
|
{
|
|
testcase("Do Voting");
|
|
|
|
{
|
|
//== 2 nodes offline including me, not in negativeUNL
|
|
//-- txSet.size = 0
|
|
NetworkHistory history = {*this, {35, 0, false, false, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return idx > 1; });
|
|
BEAST_EXPECT(voteAndCheck(history, history.UNLNodeIDs[0], 0));
|
|
}
|
|
}
|
|
|
|
{
|
|
// 2 nodes offline, not in negativeUNL, but I'm not a validator
|
|
//-- txSet.size = 0
|
|
NetworkHistory history = {*this, {40, 0, false, false, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return idx > 1; });
|
|
BEAST_EXPECT(voteAndCheck(history, NodeID(0xdeadbeef), 0));
|
|
}
|
|
}
|
|
|
|
{
|
|
//== 2 in negativeUNL, but not in unl, no other remove candidates
|
|
//-- txSet.size = 1
|
|
NetworkHistory history = {*this, {25, 2, false, false, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return idx > 1; });
|
|
BEAST_EXPECT(voteAndCheck(
|
|
history,
|
|
history.UNLNodeIDs.back(),
|
|
1,
|
|
[&](NegativeUNLVote& vote) {
|
|
history.UNLKeySet.erase(history.UNLKeys[0]);
|
|
history.UNLKeySet.erase(history.UNLKeys[1]);
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testDoVoting();
|
|
}
|
|
};
|
|
|
|
class NegativeUNLVoteNewValidator_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testDoVoting()
|
|
{
|
|
testcase("Do Voting");
|
|
|
|
{
|
|
//== 2 new validators have bad scores
|
|
//-- txSet.size = 0
|
|
NetworkHistory history = {*this, {15, 0, false, false, {}}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return true; });
|
|
BEAST_EXPECT(voteAndCheck(
|
|
history,
|
|
history.UNLNodeIDs[0],
|
|
0,
|
|
[&](NegativeUNLVote& vote) {
|
|
auto extra_key_1 =
|
|
randomKeyPair(KeyType::ed25519).first;
|
|
auto extra_key_2 =
|
|
randomKeyPair(KeyType::ed25519).first;
|
|
history.UNLKeySet.insert(extra_key_1);
|
|
history.UNLKeySet.insert(extra_key_2);
|
|
hash_set<NodeID> nowTrusted;
|
|
nowTrusted.insert(calcNodeID(extra_key_1));
|
|
nowTrusted.insert(calcNodeID(extra_key_2));
|
|
vote.newValidators(
|
|
history.lastLedger()->seq(), nowTrusted);
|
|
}));
|
|
}
|
|
}
|
|
|
|
{
|
|
//== 2 expired new validators have bad scores
|
|
//-- txSet.size = 1
|
|
NetworkHistory history = {
|
|
*this,
|
|
{21,
|
|
0,
|
|
false,
|
|
false,
|
|
NegativeUNLVote::newValidatorDisableSkip * 2}};
|
|
BEAST_EXPECT(history.goodHistory);
|
|
if (history.goodHistory)
|
|
{
|
|
history.walkHistoryAndAddValidations(
|
|
[&](std::shared_ptr<Ledger const> const& l,
|
|
std::size_t idx) -> bool { return true; });
|
|
BEAST_EXPECT(voteAndCheck(
|
|
history,
|
|
history.UNLNodeIDs[0],
|
|
1,
|
|
[&](NegativeUNLVote& vote) {
|
|
auto extra_key_1 =
|
|
randomKeyPair(KeyType::ed25519).first;
|
|
auto extra_key_2 =
|
|
randomKeyPair(KeyType::ed25519).first;
|
|
history.UNLKeySet.insert(extra_key_1);
|
|
history.UNLKeySet.insert(extra_key_2);
|
|
hash_set<NodeID> nowTrusted;
|
|
nowTrusted.insert(calcNodeID(extra_key_1));
|
|
nowTrusted.insert(calcNodeID(extra_key_2));
|
|
vote.newValidators(256, nowTrusted);
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testDoVoting();
|
|
}
|
|
};
|
|
|
|
class NegativeUNLVoteFilterValidations_test : public beast::unit_test::suite
|
|
{
|
|
void
|
|
testFilterValidations()
|
|
{
|
|
testcase("Filter Validations");
|
|
jtx::Env env(*this);
|
|
auto l = std::make_shared<Ledger>(
|
|
create_genesis,
|
|
env.app().config(),
|
|
std::vector<uint256>{},
|
|
env.app().getNodeFamily());
|
|
|
|
auto createSTVal = [&](std::pair<PublicKey, SecretKey> const& keys) {
|
|
return std::make_shared<STValidation>(
|
|
env.app().timeKeeper().now(),
|
|
keys.first,
|
|
keys.second,
|
|
calcNodeID(keys.first),
|
|
[&](STValidation& v) {
|
|
v.setFieldH256(sfLedgerHash, l->info().hash);
|
|
v.setFieldU32(sfLedgerSequence, l->seq());
|
|
v.setFlag(vfFullValidation);
|
|
});
|
|
};
|
|
|
|
// create keys and validations
|
|
std::uint32_t numNodes = 10;
|
|
std::uint32_t negUnlSize = 3;
|
|
std::vector<std::string> cfgKeys;
|
|
hash_set<NodeID> activeValidators;
|
|
hash_set<PublicKey> nUnlKeys;
|
|
std::vector<std::shared_ptr<STValidation>> vals;
|
|
for (int i = 0; i < numNodes; ++i)
|
|
{
|
|
auto keyPair = randomKeyPair(KeyType::secp256k1);
|
|
vals.emplace_back(createSTVal(keyPair));
|
|
cfgKeys.push_back(toBase58(TokenType::NodePublic, keyPair.first));
|
|
activeValidators.emplace(calcNodeID(keyPair.first));
|
|
if (i < negUnlSize)
|
|
{
|
|
nUnlKeys.insert(keyPair.first);
|
|
}
|
|
}
|
|
|
|
// setup the ValidatorList
|
|
auto& validators = env.app().validators();
|
|
auto& local = *nUnlKeys.begin();
|
|
std::vector<std::string> cfgPublishers;
|
|
validators.load(local, cfgKeys, cfgPublishers);
|
|
validators.updateTrusted(
|
|
activeValidators,
|
|
env.timeKeeper().now(),
|
|
env.app().getOPs(),
|
|
env.app().overlay(),
|
|
env.app().getHashRouter());
|
|
BEAST_EXPECT(validators.getTrustedMasterKeys().size() == numNodes);
|
|
validators.setNegativeUNL(nUnlKeys);
|
|
BEAST_EXPECT(validators.getNegativeUNL().size() == negUnlSize);
|
|
|
|
// test the filter
|
|
BEAST_EXPECT(vals.size() == numNodes);
|
|
vals = validators.negativeUNLFilter(std::move(vals));
|
|
BEAST_EXPECT(vals.size() == numNodes - negUnlSize);
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testFilterValidations();
|
|
}
|
|
};
|
|
|
|
class NegativeUNLgRPC_test : public beast::unit_test::suite
|
|
{
|
|
template <class T>
|
|
std::string
|
|
toByteString(T const& data)
|
|
{
|
|
const char* bytes = reinterpret_cast<const char*>(data.data());
|
|
return {bytes, data.size()};
|
|
}
|
|
|
|
void
|
|
testGRPC()
|
|
{
|
|
testcase("gRPC test");
|
|
|
|
auto gRpcTest = [this](
|
|
std::uint32_t negUnlSize,
|
|
bool hasToDisable,
|
|
bool hasToReEnable) -> bool {
|
|
NetworkHistory history = {
|
|
*this, {20, negUnlSize, hasToDisable, hasToReEnable, {}}};
|
|
if (!history.goodHistory)
|
|
return false;
|
|
|
|
auto const& negUnlObject =
|
|
history.lastLedger()->read(keylet::negativeUNL());
|
|
if (!negUnlSize && !hasToDisable && !hasToReEnable && !negUnlObject)
|
|
return true;
|
|
if (!negUnlObject)
|
|
return false;
|
|
|
|
org::xrpl::rpc::v1::NegativeUNL to;
|
|
ripple::RPC::convert(to, *negUnlObject);
|
|
if (!to.has_flags() ||
|
|
to.flags().value() != negUnlObject->getFlags())
|
|
return false;
|
|
|
|
bool goodSize = to.disabled_validators_size() == negUnlSize &&
|
|
to.has_validator_to_disable() == hasToDisable &&
|
|
to.has_validator_to_re_enable() == hasToReEnable;
|
|
if (!goodSize)
|
|
return false;
|
|
|
|
if (negUnlSize)
|
|
{
|
|
if (!negUnlObject->isFieldPresent(sfDisabledValidators))
|
|
return false;
|
|
auto const& nUnlData =
|
|
negUnlObject->getFieldArray(sfDisabledValidators);
|
|
if (nUnlData.size() != negUnlSize)
|
|
return false;
|
|
int idx = 0;
|
|
for (auto const& n : nUnlData)
|
|
{
|
|
if (!n.isFieldPresent(sfPublicKey) ||
|
|
!n.isFieldPresent(sfFirstLedgerSequence))
|
|
return false;
|
|
|
|
if (!to.disabled_validators(idx).has_ledger_sequence() ||
|
|
!to.disabled_validators(idx).has_public_key())
|
|
return false;
|
|
|
|
if (to.disabled_validators(idx).public_key().value() !=
|
|
toByteString(n.getFieldVL(sfPublicKey)))
|
|
return false;
|
|
|
|
if (to.disabled_validators(idx).ledger_sequence().value() !=
|
|
n.getFieldU32(sfFirstLedgerSequence))
|
|
return false;
|
|
|
|
++idx;
|
|
}
|
|
}
|
|
|
|
if (hasToDisable)
|
|
{
|
|
if (!negUnlObject->isFieldPresent(sfValidatorToDisable))
|
|
return false;
|
|
if (to.validator_to_disable().value() !=
|
|
toByteString(
|
|
negUnlObject->getFieldVL(sfValidatorToDisable)))
|
|
return false;
|
|
}
|
|
|
|
if (hasToReEnable)
|
|
{
|
|
if (!negUnlObject->isFieldPresent(sfValidatorToReEnable))
|
|
return false;
|
|
if (to.validator_to_re_enable().value() !=
|
|
toByteString(
|
|
negUnlObject->getFieldVL(sfValidatorToReEnable)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
BEAST_EXPECT(gRpcTest(0, false, false));
|
|
BEAST_EXPECT(gRpcTest(2, true, true));
|
|
}
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testGRPC();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(NegativeUNL, ledger, ripple);
|
|
BEAST_DEFINE_TESTSUITE(NegativeUNLNoAmendment, ledger, ripple);
|
|
|
|
BEAST_DEFINE_TESTSUITE(NegativeUNLVoteInternal, consensus, ripple);
|
|
BEAST_DEFINE_TESTSUITE_MANUAL(NegativeUNLVoteScoreTable, consensus, ripple);
|
|
BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteGoodScore, consensus, ripple, 1);
|
|
BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteOffline, consensus, ripple, 1);
|
|
BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteMaxListed, consensus, ripple, 1);
|
|
BEAST_DEFINE_TESTSUITE_PRIO(
|
|
NegativeUNLVoteRetiredValidator,
|
|
consensus,
|
|
ripple,
|
|
1);
|
|
BEAST_DEFINE_TESTSUITE_PRIO(NegativeUNLVoteNewValidator, consensus, ripple, 1);
|
|
BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, ripple);
|
|
BEAST_DEFINE_TESTSUITE(NegativeUNLgRPC, ledger, ripple);
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////////////////////////////////
|
|
bool
|
|
negUnlSizeTest(
|
|
std::shared_ptr<Ledger const> const& l,
|
|
size_t size,
|
|
bool hasToDisable,
|
|
bool hasToReEnable)
|
|
{
|
|
bool sameSize = l->negativeUNL().size() == size;
|
|
bool sameToDisable =
|
|
(l->validatorToDisable() != boost::none) == hasToDisable;
|
|
bool sameToReEnable =
|
|
(l->validatorToReEnable() != boost::none) == hasToReEnable;
|
|
|
|
return sameSize && sameToDisable && sameToReEnable;
|
|
}
|
|
|
|
bool
|
|
applyAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass)
|
|
{
|
|
auto res = apply(env.app(), view, tx, ApplyFlags::tapNONE, env.journal);
|
|
if (pass)
|
|
return res.first == tesSUCCESS;
|
|
else
|
|
return res.first == tefFAILURE || res.first == temDISABLED;
|
|
}
|
|
|
|
bool
|
|
VerifyPubKeyAndSeq(
|
|
std::shared_ptr<Ledger const> const& l,
|
|
hash_map<PublicKey, std::uint32_t> nUnlLedgerSeq)
|
|
{
|
|
auto sle = l->read(keylet::negativeUNL());
|
|
if (!sle)
|
|
return false;
|
|
if (!sle->isFieldPresent(sfDisabledValidators))
|
|
return false;
|
|
|
|
auto const& nUnlData = sle->getFieldArray(sfDisabledValidators);
|
|
if (nUnlData.size() != nUnlLedgerSeq.size())
|
|
return false;
|
|
|
|
for (auto const& n : nUnlData)
|
|
{
|
|
if (!n.isFieldPresent(sfFirstLedgerSequence) ||
|
|
!n.isFieldPresent(sfPublicKey))
|
|
return false;
|
|
|
|
auto seq = n.getFieldU32(sfFirstLedgerSequence);
|
|
auto d = n.getFieldVL(sfPublicKey);
|
|
auto s = makeSlice(d);
|
|
if (!publicKeyType(s))
|
|
return false;
|
|
PublicKey pk(s);
|
|
auto it = nUnlLedgerSeq.find(pk);
|
|
if (it == nUnlLedgerSeq.end())
|
|
return false;
|
|
if (it->second != seq)
|
|
return false;
|
|
nUnlLedgerSeq.erase(it);
|
|
}
|
|
return nUnlLedgerSeq.size() == 0;
|
|
}
|
|
|
|
std::size_t
|
|
countTx(std::shared_ptr<SHAMap> const& txSet)
|
|
{
|
|
std::size_t count = 0;
|
|
for (auto i = txSet->begin(); i != txSet->end(); ++i)
|
|
{
|
|
++count;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
std::vector<PublicKey>
|
|
createPublicKeys(std::size_t n)
|
|
{
|
|
std::vector<PublicKey> keys;
|
|
std::size_t ss = 33;
|
|
std::vector<uint8_t> data(ss, 0);
|
|
data[0] = 0xED;
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
data[1]++;
|
|
Slice s(data.data(), ss);
|
|
keys.emplace_back(s);
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
STTx
|
|
createTx(bool disabling, LedgerIndex seq, PublicKey const& txKey)
|
|
{
|
|
auto fill = [&](auto& obj) {
|
|
obj.setFieldU8(sfUNLModifyDisabling, disabling ? 1 : 0);
|
|
obj.setFieldU32(sfLedgerSequence, seq);
|
|
obj.setFieldVL(sfUNLModifyValidator, txKey);
|
|
};
|
|
return STTx(ttUNL_MODIFY, fill);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace ripple
|