diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 5924ceea0..aededf9a4 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -748,7 +748,6 @@ if (tests) src/test/app/Transaction_ordering_test.cpp src/test/app/TrustAndBalance_test.cpp src/test/app/TxQ_test.cpp - src/test/app/UNLReport_test.cpp src/test/app/URIToken_test.cpp src/test/app/ValidatorKeys_test.cpp src/test/app/ValidatorList_test.cpp @@ -814,6 +813,7 @@ if (tests) src/test/consensus/LedgerTrie_test.cpp src/test/consensus/NegativeUNL_test.cpp src/test/consensus/ScaleFreeSim_test.cpp + src/test/consensus/UNLReport_test.cpp src/test/consensus/Validations_test.cpp #[===============================[ test sources: diff --git a/src/ripple/app/tx/impl/DeleteAccount.cpp b/src/ripple/app/tx/impl/DeleteAccount.cpp index 6447c510b..9b7b57169 100644 --- a/src/ripple/app/tx/impl/DeleteAccount.cpp +++ b/src/ripple/app/tx/impl/DeleteAccount.cpp @@ -201,10 +201,6 @@ DeleteAccount::preclaim(PreclaimContext const& ctx) if (!sleDst) return tecNO_DST; - // accounts created via Import are blocked from deletion - if (sleDst->isFieldPresent(sfImportSequence)) - return tecHAS_OBLIGATIONS; - if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag]) return tecDST_TAG_NEEDED; @@ -221,6 +217,13 @@ DeleteAccount::preclaim(PreclaimContext const& ctx) if (!sleAccount) return terNO_ACCOUNT; + // accounts created via Import are blocked from deletion + if (ctx.view.rules().enabled(featureImport)) + { + if (sleAccount->isFieldPresent(sfImportSequence)) + return tecHAS_OBLIGATIONS; + } + if (ctx.view.rules().enabled(featureNonFungibleTokensV1)) { // If an issuer has any issued NFTs resident in the ledger then it @@ -253,8 +256,11 @@ DeleteAccount::preclaim(PreclaimContext const& ctx) // do not allow the account to be removed if there are hooks installed or one or more hook states // when these fields are completely empty the field is made absent so this test is sufficient // these fields cannot be populated unless hooks is enabled so the rules do not need to be checked - if (sleAccount->isFieldPresent(sfHookNamespaces) || sleAccount->isFieldPresent(sfHooks)) - return tecHAS_OBLIGATIONS; + if (ctx.view.rules().enabled(featureHooks)) + { + if (sleAccount->isFieldPresent(sfHookNamespaces) || sleAccount->isFieldPresent(sfHooks)) + return tecHAS_OBLIGATIONS; + } // When fixNFTokenRemint is enabled, we don't allow an account to be // deleted if is within 256 of the diff --git a/src/test/app/Import_test.cpp b/src/test/app/Import_test.cpp index d7a69df4d..c706bc054 100644 --- a/src/test/app/Import_test.cpp +++ b/src/test/app/Import_test.cpp @@ -225,6 +225,23 @@ class Import_test : public beast::unit_test::suite return slep != nullptr; } + void + incLgrSeqForAccDel( + jtx::Env& env, + jtx::Account const& acc, + std::uint32_t margin = 0) + { + int const delta = [&]() -> int { + if (env.seq(acc) + 255 > env.current()->seq()) + return env.seq(acc) - env.current()->seq() + 255 - margin; + return 0; + }(); + BEAST_EXPECT(margin == 0 || delta >= 0); + for (int i = 0; i < delta; ++i) + env.close(); + BEAST_EXPECT(env.current()->seq() == env.seq(acc) + 255 - margin); + } + void testComputeStartingBalance(FeatureBitset features) { @@ -4348,13 +4365,14 @@ class Import_test : public beast::unit_test::suite } void - testAccountCount(FeatureBitset features) + testAccountIndex(FeatureBitset features) { - testcase("account count"); + testcase("account index"); using namespace test::jtx; using namespace std::literals; + // Account Index from Import { test::jtx::Env env{*this, makeNetworkVLConfig(21337, keys)}; auto const feeDrops = env.current()->fees().base; @@ -4390,6 +4408,42 @@ class Import_test : public beast::unit_test::suite auto const [fee, feeSle] = feesKeyAndSle(*env.current()); BEAST_EXPECT((*feeSle)[sfAccountCount] == 1); } + + // Account Index from Payment + { + test::jtx::Env env{*this, makeNetworkVLConfig(21337, keys)}; + auto const feeDrops = env.current()->fees().base; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + env.fund(XRP(1000), alice, bob, carol); + env.close(); + + struct TestAccountData + { + Account acct; + std::uint64_t index; + }; + + std::array acctTests = {{ + {alice, 0}, + {bob, 1}, + {carol, 2}, + }}; + + for (auto const& t : acctTests) + { + // confirm index was set + auto const [acct, acctSle] = + accountKeyAndSle(*env.current(), t.acct); + BEAST_EXPECT((*acctSle)[sfAccountIndex] == t.index); + } + + // confirm count was updated + auto const [fee, feeSle] = feesKeyAndSle(*env.current()); + BEAST_EXPECT((*feeSle)[sfAccountCount] == 3); + } } void @@ -4484,6 +4538,44 @@ class Import_test : public beast::unit_test::suite } } + void + testAccountDelete(FeatureBitset features) + { + testcase("account delete"); + + using namespace test::jtx; + using namespace std::literals; + + { + test::jtx::Env env{*this, makeNetworkVLConfig(21337, keys)}; + + auto const feeDrops = env.current()->fees().base; + + auto const alice = Account("alice"); + auto const bob = Account("bob"); + env.fund(XRP(1000), alice, bob); + env.close(); + + // burn 10'000 xrp + auto const master = Account("masterpassphrase"); + env(noop(master), fee(10'000'000'000), ter(tesSUCCESS)); + env.close(); + + Json::Value const xpop = loadXpop(ImportTCAccountSet::w_seed); + Json::Value const tx = import(alice, xpop); + env(tx, fee(feeDrops * 10), ter(tesSUCCESS)); + + // Close enough ledgers to be able to delete alices's account. + incLgrSeqForAccDel(env, alice); + + // alice cannot delete account after import + auto const acctDelFee{drops(env.current()->fees().increment)}; + env(acctdelete(alice, bob), + fee(acctDelFee), + ter(tecHAS_OBLIGATIONS)); + } + } + void testImportSequence(FeatureBitset features) { @@ -5398,9 +5490,10 @@ public: testSetRegularKey(features); testSetRegularKeyFlags(features); testSignersListSet(features); - testAccountCount(features); + testAccountIndex(features); testHookIssuer(features); testImportSequence(features); + testAccountDelete(features); testMaxSupply(features); testMinMax(features); testHalving(features - featureOwnerPaysFee); diff --git a/src/test/app/UNLReport_test.cpp b/src/test/app/UNLReport_test.cpp deleted file mode 100644 index b4511139e..000000000 --- a/src/test/app/UNLReport_test.cpp +++ /dev/null @@ -1,411 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2017 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 -#include -#include -#include -#include -#include -#include - -namespace ripple { -namespace test { - -struct UNLReport_test : public beast::unit_test::suite -{ - std::vector - createPublicKeys(std::size_t n) - { - std::vector keys; - std::size_t ss = 33; - std::vector 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(LedgerIndex seq, PublicKey const& importKey, PublicKey const& valKey) - { - auto fill = [&](auto& obj) { - obj.setFieldU32(sfLedgerSequence, seq); - obj.set(([&]() { - auto inner = std::make_unique(sfActiveValidator); - inner->setFieldVL(sfPublicKey, valKey); - return inner; - })()); - obj.set(([&]() { - auto inner = std::make_unique(sfImportVLKey); - inner->setFieldVL(sfPublicKey, importKey); - return inner; - })()); - }; - return STTx(ttUNL_REPORT, fill); - } - - bool - hasUNLReport(jtx::Env const& env) - { - auto const slep = env.le(keylet::UNLReport()); - return slep != nullptr; - } - - void - debugVLKeys(jtx::Env const& env) - { - auto const slep = env.le(keylet::UNLReport()); - auto const& vlKeys = slep->getFieldArray(sfImportVLKeys); - std::cout << "VL KEYS ARRAY"<< "\n"; - for (auto const& k: vlKeys) - std::cout << "vlKey: " << PublicKey(k[sfPublicKey]) << "\n"; - } - - bool - validateImportVL(jtx::Env const& env, PublicKey const& pk) - { - auto const slep = env.le(keylet::UNLReport()); - auto const& vlKeys = slep->getFieldArray(sfImportVLKeys); - for (auto const& k: vlKeys) - if (PublicKey(k[sfPublicKey]) == pk) - return true; - return false; - } - - bool - validateActiveValidator(jtx::Env const& env, PublicKey const& pk) - { - auto const slep = env.le(keylet::UNLReport()); - auto const& activeVLs = slep->getFieldArray(sfActiveValidators); - for (auto const& k: activeVLs) - if (PublicKey(k[sfPublicKey]) == pk) - return true; - return false; - } - - std::unique_ptr - makeNetworkConfig(uint32_t networkID) - { - using namespace jtx; - std::vector const keys = { - "ED74D4036C6591A4BDF9C54CEFA39B996A" - "5DCE5F86D11FDA1874481CE9D5A1CDC1"}; - return envconfig([&](std::unique_ptr cfg) { - cfg->NETWORK_ID = networkID; - Section config; - config.append( - {"reference_fee = 10", - "account_reserve = 1000000", - "owner_reserve = 200000"}); - auto setup = setup_FeeVote(config); - cfg->FEES = setup; - - for (auto const& strPk : keys) - { - auto pkHex = strUnHex(strPk); - if (!pkHex) - Throw( - "Import VL Key '" + strPk + "' was not valid hex."); - - auto const pkType = publicKeyType(makeSlice(*pkHex)); - if (!pkType) - Throw( - "Import VL Key '" + strPk + - "' was not a valid key type."); - - cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex)); - } - return cfg; - }); - } - - void - testDisabled(FeatureBitset features) - { - testcase("Test disabled"); - using namespace jtx; - Env env{ - *this, - envconfig(), - features - featureXahauGenesis, - nullptr}; - - std::vector publicKeys = createPublicKeys(3); - - auto l = std::make_shared( - create_genesis, - env.app().config(), - std::vector{}, - env.app().getNodeFamily()); - - l = std::make_shared(*l, env.app().timeKeeper().closeTime()); - - // insert a ttUNL_REPORT pseudo into the open ledger - env.app().openLedger().modify( - [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createTx(l->seq(), publicKeys[0], publicKeys[0]); - uint256 txID = tx.getTransactionID(); - auto s = std::make_shared(); - tx.add(*s); - env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); - view.rawTxInsert(txID, std::move(s), nullptr); - return true; - }); - - // close the ledger - env.close(); - - BEAST_EXPECT(hasUNLReport(env) == false); - } - - void - testNoImportVL(FeatureBitset features) - { - testcase("Test no import vl"); - using namespace jtx; - - std::vector const _ivlKeys = { - "ED74D4036C6591A4BDF9C54CEFA39B996A" - "5DCE5F86D11FDA1874481CE9D5A1CDC1"}; - - std::vector ivlKeys; - for (auto const& strPk : _ivlKeys) - { - auto pkHex = strUnHex(strPk); - ivlKeys.emplace_back(makeSlice(*pkHex)); - } - - std::vector const _vlKeys = { - "ED8E43A943A174190BA2FAE91F44AC6E2D1D8202EFDCC2EA3DBB39814576D690F7", - "ED45D1840EE724BE327ABE9146503D5848EFD5F38B6D5FEDE71E80ACCE5E6E738B", - }; - - std::vector vlKeys; - for (auto const& strPk : _vlKeys) - { - auto pkHex = strUnHex(strPk); - vlKeys.emplace_back(makeSlice(*pkHex)); - } - - // Create UNLReport - { - Env env{ - *this, - envconfig(), - features, - nullptr}; - - auto l = std::make_shared( - create_genesis, - env.app().config(), - std::vector{}, - env.app().getNodeFamily()); - - l = std::make_shared(*l, env.app().timeKeeper().closeTime()); - - // insert a ttUNL_REPORT pseudo into the open ledger - env.app().openLedger().modify( - [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createTx(l->seq(), ivlKeys[0], vlKeys[0]); - uint256 txID = tx.getTransactionID(); - auto s = std::make_shared(); - tx.add(*s); - env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); - view.rawTxInsert(txID, std::move(s), nullptr); - return true; - }); - - // close the ledger - env.close(); - - BEAST_EXPECT(validateImportVL(env, ivlKeys[0]) == true); - BEAST_EXPECT(validateActiveValidator(env, vlKeys[0]) == true); - } - - // Update UNLReport - { - Env env{ - *this, - envconfig(), - features, - nullptr}; - - auto l = std::make_shared( - create_genesis, - env.app().config(), - std::vector{}, - env.app().getNodeFamily()); - - l = std::make_shared(*l, env.app().timeKeeper().closeTime()); - - // insert a ttUNL_REPORT pseudo into the open ledger - env.app().openLedger().modify( - [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createTx(l->seq(), ivlKeys[0], vlKeys[0]); - uint256 txID = tx.getTransactionID(); - auto s = std::make_shared(); - tx.add(*s); - env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); - view.rawTxInsert(txID, std::move(s), nullptr); - return true; - }); - - // close the ledger - env.close(); - - BEAST_EXPECT(validateImportVL(env, ivlKeys[0]) == true); - BEAST_EXPECT(validateActiveValidator(env, vlKeys[0]) == true); - - l = std::make_shared(*l, env.app().timeKeeper().closeTime()); - - // insert a ttUNL_REPORT pseudo into the open ledger - env.app().openLedger().modify( - [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createTx(l->seq(), ivlKeys[0], vlKeys[1]); - uint256 txID = tx.getTransactionID(); - auto s = std::make_shared(); - tx.add(*s); - env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); - view.rawTxInsert(txID, std::move(s), nullptr); - return true; - }); - - // close the ledger - env.close(); - - BEAST_EXPECT(validateImportVL(env, ivlKeys[0]) == true); - BEAST_EXPECT(validateActiveValidator(env, vlKeys[1]) == true); - } - } - - void - testImportVL(FeatureBitset features) - { - testcase("Test import vl"); - using namespace jtx; - - std::vector const _ivlKeys = { - "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC1", - "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC2", - }; - - std::vector ivlKeys; - for (auto const& strPk : _ivlKeys) - { - auto pkHex = strUnHex(strPk); - ivlKeys.emplace_back(makeSlice(*pkHex)); - } - - std::vector const _vlKeys = { - "ED8E43A943A174190BA2FAE91F44AC6E2D1D8202EFDCC2EA3DBB39814576D690F7", - "ED45D1840EE724BE327ABE9146503D5848EFD5F38B6D5FEDE71E80ACCE5E6E738B" - }; - - std::vector vlKeys; - for (auto const& strPk : _vlKeys) - { - auto pkHex = strUnHex(strPk); - vlKeys.emplace_back(makeSlice(*pkHex)); - } - - // telIMPORT_VL_KEY_NOT_RECOGNISED - { - test::jtx::Env env{*this, makeNetworkConfig(21337), features, nullptr}; - - auto l = std::make_shared( - create_genesis, - env.app().config(), - std::vector{}, - env.app().getNodeFamily()); - - l = std::make_shared(*l, env.app().timeKeeper().closeTime()); - - // insert a ttUNL_REPORT pseudo into the open ledger - env.app().openLedger().modify( - [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createTx(l->seq(), ivlKeys[1], vlKeys[0]); - uint256 txID = tx.getTransactionID(); - auto s = std::make_shared(); - tx.add(*s); - env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); - view.rawTxInsert(txID, std::move(s), nullptr); - return true; - }); - - BEAST_EXPECT(hasUNLReport(env) == false); - } - - // SUCCESS - { - test::jtx::Env env{*this, makeNetworkConfig(21337), features, nullptr}; - - auto l = std::make_shared( - create_genesis, - env.app().config(), - std::vector{}, - env.app().getNodeFamily()); - - l = std::make_shared(*l, env.app().timeKeeper().closeTime()); - - // insert a ttUNL_REPORT pseudo into the open ledger - env.app().openLedger().modify( - [&](OpenView& view, beast::Journal j) -> bool { - STTx tx = createTx(l->seq(), ivlKeys[0], vlKeys[0]); - uint256 txID = tx.getTransactionID(); - auto s = std::make_shared(); - tx.add(*s); - env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); - view.rawTxInsert(txID, std::move(s), nullptr); - return true; - }); - - // close the ledger - env.close(); - - BEAST_EXPECT(validateImportVL(env, ivlKeys[0]) == true); - BEAST_EXPECT(validateImportVL(env, ivlKeys[1]) == false); - BEAST_EXPECT(validateActiveValidator(env, vlKeys[0]) == true); - } - } - - void - testWithFeats(FeatureBitset features) - { - testDisabled(features); - testNoImportVL(features); - testImportVL(features); - } - -public: - void - run() override - { - using namespace test::jtx; - auto const sa = supported_amendments(); - testWithFeats(sa); - } -}; - -BEAST_DEFINE_TESTSUITE(UNLReport, app, ripple); - -} // namespace test -} // namespace ripple diff --git a/src/test/consensus/UNLReport_test.cpp b/src/test/consensus/UNLReport_test.cpp new file mode 100644 index 000000000..e60fdd254 --- /dev/null +++ b/src/test/consensus/UNLReport_test.cpp @@ -0,0 +1,1215 @@ +//----------------------------------------------------------------------------- +/* + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 +applyAndTestUNLRResult(jtx::Env& env, OpenView& view, STTx const& tx, bool pass); + +/** + * Verify the content of UNL Report 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 +VerifyUNLRPubKeyAndSeq( + std::shared_ptr const& l, + hash_map nUnlLedgerSeq); + +/** + * Count the number of Tx in a TxSet + * + * @param txSet the TxSet + * @return the number of Tx + */ +std::size_t +countUNLRTx(std::shared_ptr const& txSet); + +std::vector const keys = {"ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC1"}; + +std::unique_ptr +makeNetworkVLConfig(uint32_t networkID, std::vector keys); + +/** + * Verify if the UNL report exists + * + * @param env the environment + * @return true if it exists false if it does not + */ +bool +hasUNLReport(jtx::Env const& env); + +/** + * Verify if the Import VL Key exists + * + * @param env the environment + * @param pk the import public key + * @return true if it exists false if it does not + */ +bool +isImportVL(jtx::Env const& env, PublicKey const& pk); + +/** + * Verify if the Validator exists + * + * @param env the environment + * @param pk the public key of the validator + * @return true if it exists false if it does not + */ +bool +isActiveValidator(jtx::Env const& env, PublicKey const& pk); + +/** + * Create fake public keys + * + * @param n the number of public keys + * @return a vector of public keys created + */ +std::vector +createUNLRPublicKeys(std::size_t n); + +/** + * Create ttUNL_REPORT Tx + * + * @param seq current ledger seq + * @param importKey the import public key of the validator + * @param valKey the public key of the validator + * @return the ttUNL_REPORT Tx + */ +STTx +createUNLRTx( + LedgerIndex seq, + PublicKey const& importKey, + PublicKey const& valKey); + +class UNLReport_test : public beast::unit_test::suite +{ + // Import VL Keys + std::vector const _ivlKeys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC1", + "ED74D4036C6591A4BDF9C54CEFA39B996A5DCE5F86D11FDA1874481CE9D5A1CDC2", + }; + + std::vector ivlKeys; + + // VL Keys + std::vector const _vlKeys = { + "0388935426E0D08083314842EDFBB2D517BD47699F9A4527318A8E10468C97C052", + "02691AC5AE1C4C333AE5DF8A93BDC495F0EEBFC6DB0DA7EB6EF808F3AFC006E3FE", + "028949021029D5CC87E78BCF053AFEC0CAFD15108EC119EAAFEC466F5C095407BF", + "027BAEF0CB02EA8B95F50DF4BC16C740B17B50C85F3757AA06A5DB6ADE0ED92106", + "0318E0D644F3D2911D7B7E1B0B17684E7E625A6C36AECCE851BD16A4AD628B2136" + }; + std::vector vlKeys; + + void + testNoImportVL(FeatureBitset features) + { + testcase("Test no import vl"); + using namespace jtx; + + std::vector ivlKeys; + for (auto const& strPk : _ivlKeys) + { + auto pkHex = strUnHex(strPk); + ivlKeys.emplace_back(makeSlice(*pkHex)); + } + + std::vector vlKeys; + for (auto const& strPk : _vlKeys) + { + auto pkHex = strUnHex(strPk); + vlKeys.emplace_back(makeSlice(*pkHex)); + } + + // Create UNLReport + { + Env env{*this, envconfig(), features, nullptr}; + + auto l = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + l = std::make_shared(*l, env.app().timeKeeper().closeTime()); + + // insert a ttUNL_REPORT pseudo into the open ledger + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = createUNLRTx(l->seq(), ivlKeys[0], vlKeys[0]); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + // close the ledger + env.close(); + + BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); + BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true); + } + + // Update UNLReport + { + Env env{*this, envconfig(), features, nullptr}; + + auto l = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + l = std::make_shared(*l, env.app().timeKeeper().closeTime()); + + // insert a ttUNL_REPORT pseudo into the open ledger + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = createUNLRTx(l->seq(), ivlKeys[0], vlKeys[0]); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + // close the ledger + env.close(); + + BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); + BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true); + + l = std::make_shared(*l, env.app().timeKeeper().closeTime()); + + // insert a ttUNL_REPORT pseudo into the open ledger + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = createUNLRTx(l->seq(), ivlKeys[0], vlKeys[1]); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + // close the ledger + env.close(); + + BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); + BEAST_EXPECT(isActiveValidator(env, vlKeys[1]) == true); + } + } + + void + testImportVL(FeatureBitset features) + { + testcase("Test import vl"); + using namespace jtx; + + std::vector ivlKeys; + for (auto const& strPk : _ivlKeys) + { + auto pkHex = strUnHex(strPk); + ivlKeys.emplace_back(makeSlice(*pkHex)); + } + + std::vector vlKeys; + for (auto const& strPk : _vlKeys) + { + auto pkHex = strUnHex(strPk); + vlKeys.emplace_back(makeSlice(*pkHex)); + } + + // telIMPORT_VL_KEY_NOT_RECOGNISED + { + test::jtx::Env env{*this, makeNetworkVLConfig(21337, keys), features, nullptr}; + + auto l = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + l = std::make_shared(*l, env.app().timeKeeper().closeTime()); + + // insert a ttUNL_REPORT pseudo into the open ledger + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = createUNLRTx(l->seq(), ivlKeys[1], vlKeys[0]); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + BEAST_EXPECT(hasUNLReport(env) == false); + } + + // SUCCESS + { + test::jtx::Env env{*this, makeNetworkVLConfig(21337, keys), features, nullptr}; + + auto l = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + l = std::make_shared(*l, env.app().timeKeeper().closeTime()); + + // insert a ttUNL_REPORT pseudo into the open ledger + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = createUNLRTx(l->seq(), ivlKeys[0], vlKeys[0]); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + + // close the ledger + env.close(); + + BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); + BEAST_EXPECT(isImportVL(env, ivlKeys[1]) == false); + BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true); + } + } + + void + testValidMulitple(FeatureBitset features) + { + testcase("Test several validators all get on the list"); + using namespace jtx; + + test::jtx::Env env{*this, makeNetworkVLConfig(21337, keys), features, nullptr}; + + std::vector ivlKeys; + for (auto const& strPk : _ivlKeys) + { + auto pkHex = strUnHex(strPk); + ivlKeys.emplace_back(makeSlice(*pkHex)); + } + + std::vector vlKeys; + for (auto const& strPk : _vlKeys) + { + auto pkHex = strUnHex(strPk); + vlKeys.emplace_back(makeSlice(*pkHex)); + } + + auto l = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + + l = std::make_shared(*l, env.app().timeKeeper().closeTime()); + + for (auto const& vlKey : vlKeys) { + env.app().openLedger().modify( + [&](OpenView& view, beast::Journal j) -> bool { + STTx tx = createUNLRTx( + env.current()->seq() + 1, ivlKeys[0], vlKey); + uint256 txID = tx.getTransactionID(); + auto s = std::make_shared(); + tx.add(*s); + env.app().getHashRouter().setFlags(txID, SF_PRIVATE2); + view.rawTxInsert(txID, std::move(s), nullptr); + return true; + }); + } + + // close the ledger + env.close(); + + BEAST_EXPECT(isImportVL(env, ivlKeys[0]) == true); + BEAST_EXPECT(isImportVL(env, ivlKeys[1]) == false); + BEAST_EXPECT(isActiveValidator(env, vlKeys[0]) == true); + BEAST_EXPECT(isActiveValidator(env, vlKeys[1]) == true); + BEAST_EXPECT(isActiveValidator(env, vlKeys[2]) == true); + BEAST_EXPECT(isActiveValidator(env, vlKeys[3]) == true); + BEAST_EXPECT(isActiveValidator(env, vlKeys[4]) == true); + } + + void + testWithFeats(FeatureBitset features) + { + testNoImportVL(features); + testImportVL(features); + testValidMulitple(features); + } + + void + run() override + { + using namespace test::jtx; + auto const sa = supported_amendments(); + testWithFeats(sa); + } +}; + + +class UNLReportNoAmendment_test : public beast::unit_test::suite +{ + void + testUNLReportNoAmendment() + { + testcase("No UNL report amendment"); + + jtx::Env env(*this, jtx::supported_amendments() - featureXahauGenesis); + std::vector publicKeys = createUNLRPublicKeys(1); + // genesis ledger + auto l = std::make_shared( + create_genesis, + env.app().config(), + std::vector{}, + env.app().getNodeFamily()); + BEAST_EXPECT(!l->rules().enabled(featureXahauGenesis)); + + // generate more ledgers + for (auto i = 0; i < 256 - 1; ++i) + { + l = std::make_shared( + *l, env.app().timeKeeper().closeTime()); + } + BEAST_EXPECT(l->seq() == 256); + STTx tx = createUNLRTx(l->seq(), publicKeys[0], publicKeys[0]); + OpenView accum(&*l); + BEAST_EXPECT(applyAndTestUNLRResult(env, accum, tx, false)); + accum.apply(*l); + BEAST_EXPECT(!hasUNLReport(env)); + } + + void + run() override + { + testUNLReportNoAmendment(); + } +}; + +/** + * Utility class for creating validators and ledger history + */ +struct URNetworkHistory +{ + using LedgerHistory = std::vector>; + /** + * + * 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 + bool withVL = false; // test with VL Import + /** + * if not specified, the number of ledgers in the history is calculated + * from negUNLSize, hasToDisable, and hasToReEnable + */ + std::optional numLedgers; + }; + + URNetworkHistory(beast::unit_test::suite& suite, Parameter const& p) + : env(suite, + p.withVL ? makeNetworkVLConfig(21337, keys) : jtx::envconfig(), + 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 = createUNLRPublicKeys(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( + create_genesis, + env.app().config(), + std::vector{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( + *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 + createSTVal(std::shared_ptr const& ledger, NodeID const& v) + { + static auto keyPair = randomKeyPair(KeyType::secp256k1); + return std::make_shared( + 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 + 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 + lastLedger() const + { + return history.back(); + } + + jtx::Env env; + Parameter param; + RCLValidations& validations; + std::vector UNLKeys; + hash_set UNLKeySet; + std::vector UNLNodeIDs; + hash_set UNLNodeIDSet; + LedgerHistory history; + bool goodHistory; +}; + +auto defaultUNLRPreVote = [](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 +bool +voteAndCheckUNLR( + URNetworkHistory& history, + NodeID const& myId, + std::size_t expectModify, + std::size_t expectReport, + PreVote const& pre = defaultUNLRPreVote) +{ + NegativeUNLVote vote(myId, history.env.journal, history.env.app()); + pre(vote); + auto txSet = std::make_shared( + SHAMapType::TRANSACTION, history.env.app().getNodeFamily()); + vote.doVoting( + history.lastLedger(), history.UNLKeySet, history.validations, txSet); + + return countUNLRTx(txSet) == expectReport && countTx(txSet) >= expectModify; +} + +/* + * Test the doVoting function of UNLReport. + * 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 UNLReportVoteGoodScore_test : public beast::unit_test::suite +{ + void + testDoVoting() + { + testcase("Do Voting"); + + for (bool const withVLImport : {true, false}) + { + { + //== all good score, negativeUNL empty + //-- txSet.size = 0 + auto const numNodes = 51; + auto const unlrCount = withVLImport ? 52 : 51; + URNetworkHistory history = {*this, {numNodes, 0, false, false, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { return true; }); + BEAST_EXPECT(voteAndCheckUNLR(history, history.UNLNodeIDs[0], 0, unlrCount)); + } + } + + { + // all good score, negativeUNL not empty (use hasToDisable) + //-- txSet.size = 1 + auto const numNodes = 37; + auto const unlrCount = withVLImport ? 38 : 37; + URNetworkHistory history = {*this, {numNodes, 0, true, false, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { return true; }); + BEAST_EXPECT(voteAndCheckUNLR(history, history.UNLNodeIDs[0], 1, unlrCount)); + } + } + } + } + + void + run() override + { + testDoVoting(); + } +}; + +class UNLReportVoteOffline_test : public beast::unit_test::suite +{ + void + testDoVoting() + { + testcase("Do Voting"); + + for (bool const withVLImport : {true, false}) + { + { + //== 2 nodes offline, negativeUNL empty (use hasToReEnable) + //-- txSet.size = 1 + auto const numNodes = 29; + auto const unlrCount = withVLImport ? 28 : 27; + URNetworkHistory history = {*this, {numNodes, 1, false, true, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { + // skip node 0 and node 1 + return idx > 1; + }); + BEAST_EXPECT( + voteAndCheckUNLR(history, history.UNLNodeIDs.back(), 1, unlrCount)); + } + } + + { + // 2 nodes offline, in negativeUNL + //-- txSet.size = 0 + auto const numNodes = 30; + auto const unlrCount = withVLImport ? 29 : 28; + URNetworkHistory history = {*this, {numNodes, 1, true, false, withVLImport, {}}}; + 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 const& l, + std::size_t idx) -> bool { + // skip node 0 and node 1 + return history.UNLNodeIDs[idx] != n1 && + history.UNLNodeIDs[idx] != n2; + }); + BEAST_EXPECT( + voteAndCheckUNLR(history, history.UNLNodeIDs.back(), 0, unlrCount)); + } + } + } + } + + void + run() override + { + testDoVoting(); + } +}; + +class UNLReportVoteMaxListed_test : public beast::unit_test::suite +{ + void + testDoVoting() + { + testcase("Do Voting"); + + for (bool const withVLImport : {true, false}) + { + { + // 2 nodes offline, not in negativeUNL, but maxListed + //-- txSet.size = 0 + auto const numNodes = 32; + auto const unlrCount = withVLImport ? 22 : 21; + URNetworkHistory history = {*this, {numNodes, 8, true, true, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { + // skip node 0 ~ 10 + return idx > 10; + }); + BEAST_EXPECT( + voteAndCheckUNLR(history, history.UNLNodeIDs.back(), 0, unlrCount)); + } + } + } + } + + void + run() override + { + testDoVoting(); + } +}; + +class UNLReportVoteRetiredValidator_test : public beast::unit_test::suite +{ + void + testDoVoting() + { + testcase("Do Voting"); + + for (bool const withVLImport : {true, false}) + { + + { + //== 2 nodes offline including me, not in negativeUNL + //-- txSet.size = 0 + auto const numNodes = 35; + auto const unlrCount = withVLImport ? 0 : 0; + URNetworkHistory history = {*this, {numNodes, 0, false, false, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { return idx > 1; }); + BEAST_EXPECT(voteAndCheckUNLR(history, history.UNLNodeIDs[0], 0, unlrCount)); + } + } + + { + // 2 nodes offline, not in negativeUNL, but I'm not a validator + //-- txSet.size = 0 + auto const numNodes = 40; + auto const unlrCount = withVLImport ? 0 : 0; + URNetworkHistory history = {*this, {numNodes, 0, false, false, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { return idx > 1; }); + BEAST_EXPECT(voteAndCheckUNLR(history, NodeID(0xdeadbeef), 0, unlrCount)); + } + } + + { + //== 2 in negativeUNL, but not in unl, no other remove candidates + //-- txSet.size = 1 + auto const numNodes = 25; + auto const unlrCount = withVLImport ? 24 : 23; + URNetworkHistory history = {*this, {numNodes, 2, false, false, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { return idx > 1; }); + BEAST_EXPECT(voteAndCheckUNLR( + history, + history.UNLNodeIDs.back(), + 1, + unlrCount, + [&](NegativeUNLVote& vote) { + history.UNLKeySet.erase(history.UNLKeys[0]); + history.UNLKeySet.erase(history.UNLKeys[1]); + })); + } + } + } + } + + void + run() override + { + testDoVoting(); + } +}; + +class UNLReportVoteNewValidator_test : public beast::unit_test::suite +{ + void + testDoVoting() + { + testcase("Do Voting"); + + for (bool const withVLImport : {true, false}) + { + + { + //== 2 new validators have bad scores + //-- txSet.size = 0 + auto const numNodes = 15; + auto const unlrCount = withVLImport ? 16 : 15; + URNetworkHistory history = {*this, {numNodes, 0, false, false, withVLImport, {}}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { return true; }); + BEAST_EXPECT(voteAndCheckUNLR( + history, + history.UNLNodeIDs[0], + 0, + unlrCount, + [&](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 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 + auto const numNodes = 21; + auto const unlrCount = withVLImport ? 22 : 21; + URNetworkHistory history = { + *this, + {numNodes, + 0, + false, + false, + withVLImport, + NegativeUNLVote::newValidatorDisableSkip * 2}}; + BEAST_EXPECT(history.goodHistory); + if (history.goodHistory) + { + history.walkHistoryAndAddValidations( + [&](std::shared_ptr const& l, + std::size_t idx) -> bool { return true; }); + BEAST_EXPECT(voteAndCheckUNLR( + history, + history.UNLNodeIDs[0], + 1, + unlrCount, + [&](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 nowTrusted; + nowTrusted.insert(calcNodeID(extra_key_1)); + nowTrusted.insert(calcNodeID(extra_key_2)); + vote.newValidators(256, nowTrusted); + })); + } + } + } + } + + void + run() override + { + testDoVoting(); + } +}; + +BEAST_DEFINE_TESTSUITE(UNLReport, ledger, ripple); +BEAST_DEFINE_TESTSUITE(UNLReportNoAmendment, ledger, ripple); +BEAST_DEFINE_TESTSUITE_PRIO(UNLReportVoteGoodScore, consensus, ripple, 1); +BEAST_DEFINE_TESTSUITE(UNLReportVoteOffline, consensus, ripple); +BEAST_DEFINE_TESTSUITE(UNLReportVoteMaxListed, consensus, ripple); +BEAST_DEFINE_TESTSUITE_PRIO( + UNLReportVoteRetiredValidator, + consensus, + ripple, + 1); +BEAST_DEFINE_TESTSUITE(UNLReportVoteNewValidator, consensus, ripple); + +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////// +bool +applyAndTestUNLRResult(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 || + res.first == temMALFORMED || + res.first == telIMPORT_VL_KEY_NOT_RECOGNISED; +} + +bool +VerifyUNLRPubKeyAndSeq( + std::shared_ptr const& l, + hash_map 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 +countUNLRTx(std::shared_ptr const& txSet) +{ + std::size_t count = 0; + for (auto i = txSet->begin(); i != txSet->end(); ++i) + { + auto raw = i->slice(); + if (raw[0] == 0x12U && raw[1] == 0 && raw[2] == 0x68U) + count++; + } + return count; +}; + +std::unique_ptr +makeNetworkVLConfig(uint32_t networkID, std::vector keys) +{ + using namespace jtx; + return envconfig([&](std::unique_ptr cfg) { + cfg->NETWORK_ID = networkID; + Section config; + config.append( + {"reference_fee = 10", + "account_reserve = 1000000", + "owner_reserve = 200000"}); + auto setup = setup_FeeVote(config); + cfg->FEES = setup; + + for (auto const& strPk : keys) + { + auto pkHex = strUnHex(strPk); + if (!pkHex) + Throw( + "Import VL Key '" + strPk + "' was not valid hex."); + + auto const pkType = publicKeyType(makeSlice(*pkHex)); + if (!pkType) + Throw( + "Import VL Key '" + strPk + + "' was not a valid key type."); + + cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex)); + } + return cfg; + }); +} + +bool +hasUNLReport(jtx::Env const& env) +{ + auto const slep = env.le(keylet::UNLReport()); + return slep != nullptr; +} + +bool +isImportVL(jtx::Env const& env, PublicKey const& pk) +{ + auto const slep = env.le(keylet::UNLReport()); + auto const& vlKeys = slep->getFieldArray(sfImportVLKeys); + for (auto const& k: vlKeys) + if (PublicKey(k[sfPublicKey]) == pk) + return true; + return false; +} + +bool +isActiveValidator(jtx::Env const& env, PublicKey const& pk) +{ + auto const slep = env.le(keylet::UNLReport()); + auto const& activeVLs = slep->getFieldArray(sfActiveValidators); + for (auto const& k: activeVLs) + if (PublicKey(k[sfPublicKey]) == pk) + return true; + return false; +} + +std::vector +createUNLRPublicKeys(std::size_t n) +{ + std::vector keys; + std::size_t ss = 33; + std::vector 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 +createUNLRTx( + LedgerIndex seq, + PublicKey const& importKey, + PublicKey const& valKey) +{ + auto fill = [&](auto& obj) { + obj.setFieldU32(sfLedgerSequence, seq); + obj.set(([&]() { + auto inner = std::make_unique(sfActiveValidator); + inner->setFieldVL(sfPublicKey, valKey); + return inner; + })()); + obj.set(([&]() { + auto inner = std::make_unique(sfImportVLKey); + inner->setFieldVL(sfPublicKey, importKey); + return inner; + })()); + }; + return STTx(ttUNL_REPORT, fill); +} + +} // namespace test +} // namespace ripple