test: Migrate resource, shamap Beast tests to GTest (#7133)

Co-authored-by: Alex Kremer <akremer@ripple.com>
This commit is contained in:
Marek Foss
2026-07-06 19:26:19 +02:00
committed by GitHub
parent f59d5c0894
commit 7a153a2bce
10 changed files with 792 additions and 913 deletions

View File

@@ -162,9 +162,6 @@ test.protocol > test.unit_test
test.protocol > xrpl.basics
test.protocol > xrpl.json
test.protocol > xrpl.protocol
test.resource > test.unit_test
test.resource > xrpl.basics
test.resource > xrpl.resource
test.rpc > test.jtx
test.rpc > xrpl.basics
test.rpc > xrpl.config
@@ -188,12 +185,6 @@ test.server > xrpld.core
test.server > xrpl.json
test.server > xrpl.protocol
test.server > xrpl.server
test.shamap > test.unit_test
test.shamap > xrpl.basics
test.shamap > xrpl.config
test.shamap > xrpl.nodestore
test.shamap > xrpl.protocol
test.shamap > xrpl.shamap
test.unit_test > xrpl.basics
test.unit_test > xrpl.protocol
tests.libxrpl > xrpl.basics
@@ -205,6 +196,7 @@ tests.libxrpl > xrpl.net
tests.libxrpl > xrpl.nodestore
tests.libxrpl > xrpl.protocol
tests.libxrpl > xrpl.protocol_autogen
tests.libxrpl > xrpl.resource
tests.libxrpl > xrpl.server
tests.libxrpl > xrpl.shamap
tests.libxrpl > xrpl.tx

View File

@@ -1,296 +0,0 @@
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/insight/NullCollector.h>
#include <xrpl/beast/net/IPAddressV4.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/resource/Charge.h>
#include <xrpl/resource/Consumer.h>
#include <xrpl/resource/Disposition.h>
#include <xrpl/resource/Gossip.h>
#include <xrpl/resource/detail/Logic.h>
#include <xrpl/resource/detail/Tuning.h>
#include <boost/utility/base_from_member.hpp>
#include <chrono>
#include <cstdint>
#include <functional>
#include <string>
namespace xrpl::Resource {
class ResourceManager_test : public beast::unit_test::Suite
{
public:
class TestLogic : private boost::base_from_member<TestStopwatch>, public Logic
{
private:
using clock_type = boost::base_from_member<TestStopwatch>;
public:
explicit TestLogic(beast::Journal journal)
: Logic(beast::insight::NullCollector::make(), member, journal)
{
}
void
advance()
{
++member;
}
TestStopwatch&
clock()
{
return member;
}
};
//--------------------------------------------------------------------------
static void
createGossip(Gossip& gossip)
{
std::uint8_t const v(10 + randInt(9));
std::uint8_t const n(10 + randInt(9));
gossip.items.reserve(n);
for (std::uint8_t i = 0; i < n; ++i)
{
Gossip::Item item;
item.balance = 100 + randInt(499);
beast::IP::AddressV4::bytes_type const d = {
{192, 0, 2, static_cast<std::uint8_t>(v + i)}};
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
gossip.items.push_back(item);
}
}
//--------------------------------------------------------------------------
void
testDrop(beast::Journal j, bool limited)
{
if (limited)
{
testcase("Limited warn/drop");
}
else
{
testcase("Unlimited warn/drop");
}
TestLogic logic(j);
Charge const fee(kDropThreshold + 1);
beast::IP::Endpoint const addr(beast::IP::Endpoint::fromString("192.0.2.2"));
std::function<Consumer(beast::IP::Endpoint)> const ep =
[&logic, limited](beast::IP::Endpoint const& address) {
return limited ? logic.newInboundEndpoint(address)
: logic.newUnlimitedEndpoint(address);
};
{
Consumer c(ep(addr));
// Create load until we get a warning
int n = 10000;
while (--n >= 0)
{
if (n == 0)
{
if (limited)
{
fail("Loop count exceeded without warning");
}
else
{
pass();
}
return;
}
if (c.charge(fee) == Disposition::Warn)
{
if (limited)
{
pass();
}
else
{
fail("Should loop forever with no warning");
}
break;
}
++logic.clock();
}
// Create load until we get dropped
while (--n >= 0)
{
if (n == 0)
{
if (limited)
{
fail("Loop count exceeded without dropping");
}
else
{
pass();
}
return;
}
if (c.charge(fee) == Disposition::Drop)
{
// Disconnect abusive Consumer
BEAST_EXPECT(c.disconnect(j) == limited);
break;
}
++logic.clock();
}
}
// Make sure the consumer is on the blacklist for a while.
{
Consumer const c(logic.newInboundEndpoint(addr));
logic.periodicActivity();
if (c.disposition() != Disposition::Drop)
{
if (limited)
{
fail("Dropped consumer not put on blacklist");
}
else
{
pass();
}
return;
}
}
// Makes sure the Consumer is eventually removed from blacklist
bool readmitted = false;
{
using namespace std::chrono_literals;
// Give Consumer time to become readmitted. Should never
// exceed expiration time.
auto n = kSecondsUntilExpiration + 1s;
while (--n > 0s)
{
++logic.clock();
logic.periodicActivity();
Consumer const c(logic.newInboundEndpoint(addr));
if (c.disposition() != Disposition::Drop)
{
readmitted = true;
break;
}
}
}
if (!readmitted)
{
fail("Dropped Consumer left on blacklist too long");
return;
}
pass();
}
void
testImports(beast::Journal j)
{
testcase("Imports");
TestLogic logic(j);
Gossip g[5];
for (auto& gossip : g)
createGossip(gossip);
for (int i = 0; i < 5; ++i)
logic.importConsumers(std::to_string(i), g[i]);
pass();
}
void
testImport(beast::Journal j)
{
testcase("Import");
TestLogic logic(j);
Gossip g;
Gossip::Item item;
item.balance = 100;
beast::IP::AddressV4::bytes_type const d = {{192, 0, 2, 1}};
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
g.items.push_back(item);
logic.importConsumers("g", g);
pass();
}
void
testCharges(beast::Journal j)
{
testcase("Charge");
TestLogic logic(j);
{
beast::IP::Endpoint const address(beast::IP::Endpoint::fromString("192.0.2.1"));
Consumer c(logic.newInboundEndpoint(address));
Charge const fee(1000);
JLOG(j.info()) << "Charging " << c.toString() << " " << fee << " per second";
c.charge(fee);
for (int i = 0; i < 128; ++i)
{
JLOG(j.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
<< ", Balance = " << c.balance();
logic.advance();
}
}
{
beast::IP::Endpoint const address(beast::IP::Endpoint::fromString("192.0.2.2"));
Consumer c(logic.newInboundEndpoint(address));
Charge const fee(1000);
JLOG(j.info()) << "Charging " << c.toString() << " " << fee << " per second";
for (int i = 0; i < 128; ++i)
{
c.charge(fee);
JLOG(j.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
<< ", Balance = " << c.balance();
logic.advance();
}
}
pass();
}
void
run() override
{
using beast::Severity;
test::SuiteJournal journal("ResourceManager_test", *this);
testDrop(journal, true);
testDrop(journal, false);
testCharges(journal);
testImports(journal);
testImport(journal);
}
};
BEAST_DEFINE_TESTSUITE(ResourceManager, resource, xrpl);
} // namespace xrpl::Resource

View File

@@ -1,177 +0,0 @@
#include <test/shamap/common.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/shamap/SHAMap.h>
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <chrono>
#include <cstdint>
#include <list>
#include <ostream>
#include <utility>
#include <vector>
namespace xrpl::tests {
class SHAMapSync_test : public beast::unit_test::Suite
{
public:
beast::xor_shift_engine eng;
boost::intrusive_ptr<SHAMapItem>
makeRandomAS()
{
Serializer s;
for (int d = 0; d < 3; ++d)
s.add32(randInt<std::uint32_t>(eng));
return makeShamapitem(s.getSHA512Half(), s.slice());
}
bool
confuseMap(SHAMap& map, int count)
{
// add a bunch of random states to a map, then remove them
// map should be the same
SHAMapHash const beforeHash = map.getHash();
std::list<uint256> items;
for (int i = 0; i < count; ++i)
{
auto item = makeRandomAS();
items.push_back(item->key());
if (!map.addItem(SHAMapNodeType::TnAccountState, item))
{
log << "Unable to add item to map\n";
return false;
}
}
for (auto const& item : items)
{
if (!map.delItem(item))
{
log << "Unable to remove item from map\n";
return false;
}
}
if (beforeHash != map.getHash())
{
log << "Hashes do not match " << beforeHash << " " << map.getHash() << std::endl;
return false;
}
return true;
}
void
run() override
{
using beast::Severity;
test::SuiteJournal journal("SHAMapSync_test", *this);
TestNodeFamily f(journal), f2(journal);
SHAMap source(SHAMapType::FREE, f);
SHAMap destination(SHAMapType::FREE, f2);
int const items = 10000;
for (int i = 0; i < items; ++i)
{
source.addItem(SHAMapNodeType::TnAccountState, makeRandomAS());
if (i % 100 == 0)
source.invariants();
}
source.invariants();
BEAST_EXPECT(confuseMap(source, 500));
source.invariants();
source.setImmutable();
int count = 0;
source.visitLeaves([&count](auto const& item) { ++count; });
BEAST_EXPECT(count == items);
std::vector<SHAMapMissingNode> missingNodes;
source.walkMap(missingNodes, 2048);
BEAST_EXPECT(missingNodes.empty());
destination.setSynching();
{
std::vector<std::pair<SHAMapNodeID, Blob>> a;
BEAST_EXPECT(source.getNodeFat(SHAMapNodeID(), a, randBool(eng), randInt(eng, 2)));
unexpected(a.empty(), "NodeSize");
BEAST_EXPECT(destination.addRootNode(source.getHash(), makeSlice(a[0].second), nullptr)
.isGood());
}
do
{
f.clock().advance(std::chrono::seconds(1));
// get the list of nodes we know we need
auto nodesMissing = destination.getMissingNodes(2048, nullptr);
if (nodesMissing.empty())
break;
// get as many nodes as possible based on this information
std::vector<std::pair<SHAMapNodeID, Blob>> b;
for (auto& it : nodesMissing)
{
// Don't use BEAST_EXPECT here b/c it will be called a
// non-deterministic number of times and the number of tests run
// should be deterministic
if (!source.getNodeFat(it.first, b, randBool(eng), randInt(eng, 2)))
fail("", __FILE__, __LINE__);
}
// Don't use BEAST_EXPECT here b/c it will be called a
// non-deterministic number of times and the number of tests run
// should be deterministic
if (b.empty())
fail("", __FILE__, __LINE__);
for (auto& node : b)
{
// Don't use BEAST_EXPECT here b/c it will be called a
// non-deterministic number of times and the number of tests run
// should be deterministic
if (!destination.addKnownNode(node.first, makeSlice(node.second), nullptr)
.isUseful())
fail("", __FILE__, __LINE__);
}
} while (true);
destination.clearSynching();
BEAST_EXPECT(source.deepCompare(destination));
destination.invariants();
}
};
BEAST_DEFINE_TESTSUITE(SHAMapSync, shamap, xrpl);
} // namespace xrpl::tests

View File

@@ -1,427 +0,0 @@
#include <test/shamap/common.h>
#include <test/unit_test/SuiteJournal.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/unit_test/suite.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/shamap/SHAMap.h>
#include <xrpl/shamap/SHAMapInnerNode.h>
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapLeafNode.h>
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
namespace xrpl::tests {
#ifndef __INTELLISENSE__
static_assert(std::is_nothrow_destructible<SHAMap>{});
static_assert(!std::is_default_constructible<SHAMap>{});
static_assert(!std::is_copy_constructible<SHAMap>{});
static_assert(!std::is_copy_assignable<SHAMap>{});
static_assert(!std::is_move_constructible<SHAMap>{});
static_assert(!std::is_move_assignable<SHAMap>{});
static_assert(std::is_nothrow_destructible<SHAMap::ConstIterator>{});
static_assert(std::is_copy_constructible<SHAMap::ConstIterator>{});
static_assert(std::is_copy_assignable<SHAMap::ConstIterator>{});
static_assert(std::is_move_constructible<SHAMap::ConstIterator>{});
static_assert(std::is_move_assignable<SHAMap::ConstIterator>{});
static_assert(std::is_nothrow_destructible<SHAMapItem>{});
static_assert(!std::is_default_constructible<SHAMapItem>{});
static_assert(!std::is_copy_constructible<SHAMapItem>{});
static_assert(std::is_nothrow_destructible<SHAMapNodeID>{});
static_assert(std::is_default_constructible<SHAMapNodeID>{});
static_assert(std::is_copy_constructible<SHAMapNodeID>{});
static_assert(std::is_copy_assignable<SHAMapNodeID>{});
static_assert(std::is_move_constructible<SHAMapNodeID>{});
static_assert(std::is_move_assignable<SHAMapNodeID>{});
static_assert(std::is_nothrow_destructible<SHAMapHash>{});
static_assert(std::is_default_constructible<SHAMapHash>{});
static_assert(std::is_copy_constructible<SHAMapHash>{});
static_assert(std::is_copy_assignable<SHAMapHash>{});
static_assert(std::is_move_constructible<SHAMapHash>{});
static_assert(std::is_move_assignable<SHAMapHash>{});
static_assert(std::is_nothrow_destructible<SHAMapTreeNode>{});
static_assert(!std::is_default_constructible<SHAMapTreeNode>{});
static_assert(!std::is_copy_constructible<SHAMapTreeNode>{});
static_assert(!std::is_copy_assignable<SHAMapTreeNode>{});
static_assert(!std::is_move_constructible<SHAMapTreeNode>{});
static_assert(!std::is_move_assignable<SHAMapTreeNode>{});
static_assert(std::is_nothrow_destructible<SHAMapInnerNode>{});
static_assert(!std::is_default_constructible<SHAMapInnerNode>{});
static_assert(!std::is_copy_constructible<SHAMapInnerNode>{});
static_assert(!std::is_copy_assignable<SHAMapInnerNode>{});
static_assert(!std::is_move_constructible<SHAMapInnerNode>{});
static_assert(!std::is_move_assignable<SHAMapInnerNode>{});
static_assert(std::is_nothrow_destructible<SHAMapLeafNode>{});
static_assert(!std::is_default_constructible<SHAMapLeafNode>{});
static_assert(!std::is_copy_constructible<SHAMapLeafNode>{});
static_assert(!std::is_copy_assignable<SHAMapLeafNode>{});
static_assert(!std::is_move_constructible<SHAMapLeafNode>{});
static_assert(!std::is_move_assignable<SHAMapLeafNode>{});
#endif
inline bool
operator==(SHAMapItem const& a, SHAMapItem const& b)
{
return a.key() == b.key();
}
inline bool
operator!=(SHAMapItem const& a, SHAMapItem const& b)
{
return a.key() != b.key();
}
inline bool
operator==(SHAMapItem const& a, uint256 const& b)
{
return a.key() == b;
}
inline bool
operator!=(SHAMapItem const& a, uint256 const& b)
{
return a.key() != b;
}
class SHAMap_test : public beast::unit_test::Suite
{
public:
static Buffer
intToVuc(int v)
{
Buffer vuc(32);
std::fill_n(vuc.data(), vuc.size(), static_cast<std::uint8_t>(v));
return vuc;
}
void
run() override
{
using beast::Severity;
test::SuiteJournal journal("SHAMap_test", *this);
run(true, journal);
run(false, journal);
}
void
run(bool backed, beast::Journal const& journal)
{
if (backed)
{
testcase("add/traverse backed");
}
else
{
testcase("add/traverse unbacked");
}
tests::TestNodeFamily f(journal);
// h3 and h4 differ only in the leaf, same terminal node (level 19)
constexpr uint256 kH1("092891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
constexpr uint256 kH2("436ccbac3347baa1f1e53baeef1f43334da88f1f6d70d963b833afd6dfa289fe");
constexpr uint256 kH3("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8");
constexpr uint256 kH4("b92891fe4ef6cee585fdc6fda2e09eb4d386363158ec3321b8123e5a772c6ca8");
constexpr uint256 kH5("a92891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
SHAMap sMap(SHAMapType::FREE, f);
sMap.invariants();
if (!backed)
sMap.setUnbacked();
auto i1 = makeShamapitem(kH1, intToVuc(1));
auto i2 = makeShamapitem(kH2, intToVuc(2));
auto i3 = makeShamapitem(kH3, intToVuc(3));
auto i4 = makeShamapitem(kH4, intToVuc(4));
auto i5 = makeShamapitem(kH5, intToVuc(5));
unexpected(!sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i2)), "no add");
sMap.invariants();
unexpected(!sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i1)), "no add");
sMap.invariants();
auto i = sMap.begin();
auto e = sMap.end();
unexpected(i == e || (*i != *i1), "bad traverse");
++i;
unexpected(i == e || (*i != *i2), "bad traverse");
++i;
unexpected(i != e, "bad traverse");
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i4));
sMap.invariants();
sMap.delItem(i2->key());
sMap.invariants();
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i3));
sMap.invariants();
i = sMap.begin();
e = sMap.end();
unexpected(i == e || (*i != *i1), "bad traverse");
++i;
unexpected(i == e || (*i != *i3), "bad traverse");
++i;
unexpected(i == e || (*i != *i4), "bad traverse");
++i;
unexpected(i != e, "bad traverse");
if (backed)
{
testcase("snapshot backed");
}
else
{
testcase("snapshot unbacked");
}
SHAMapHash const mapHash = sMap.getHash();
std::shared_ptr<SHAMap> const map2 = sMap.snapShot(false);
map2->invariants();
unexpected(sMap.getHash() != mapHash, "bad snapshot");
unexpected(map2->getHash() != mapHash, "bad snapshot");
SHAMap::Delta delta;
BEAST_EXPECT(sMap.compare(*map2, delta, 100));
BEAST_EXPECT(delta.empty());
unexpected(!sMap.delItem(sMap.begin()->key()), "bad mod");
sMap.invariants();
unexpected(sMap.getHash() == mapHash, "bad snapshot");
unexpected(map2->getHash() != mapHash, "bad snapshot");
BEAST_EXPECT(sMap.compare(*map2, delta, 100));
BEAST_EXPECT(delta.size() == 1);
BEAST_EXPECT(delta.begin()->first == kH1);
BEAST_EXPECT(delta.begin()->second.first == nullptr);
BEAST_EXPECT(delta.begin()->second.second->key() == kH1);
sMap.dump();
if (backed)
{
testcase("build/tear backed");
}
else
{
testcase("build/tear unbacked");
}
{
static constexpr std::array keys{
uint256(
"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8")};
static constexpr std::array kHashes{
uint256(
"B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C"
"64B6281F7F"),
uint256(
"FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A883184546"
"7FB2ECE266"),
uint256(
"4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51A"
"E756795B75"),
uint256(
"7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC"
"6C74F93E07"),
uint256(
"395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596"
"462B0E3A3E"),
uint256(
"D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D9"
"8A84D9DDE4"),
uint256(
"76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF9"
"4361143615"),
uint256(
"DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA0"
"2BBE7230E5")};
SHAMap map(SHAMapType::FREE, f);
if (!backed)
map.setUnbacked();
BEAST_EXPECT(map.getHash() == beast::kZero);
for (int k = 0; k < keys.size(); ++k)
{
BEAST_EXPECT(map.addItem(
SHAMapNodeType::TnTransactionNm, makeShamapitem(keys[k], intToVuc(k))));
BEAST_EXPECT(map.getHash().asUInt256() == kHashes[k]);
map.invariants();
}
for (int k = keys.size() - 1; k >= 0; --k)
{
BEAST_EXPECT(map.getHash().asUInt256() == kHashes[k]);
BEAST_EXPECT(map.delItem(keys[k]));
map.invariants();
}
BEAST_EXPECT(map.getHash() == beast::kZero);
}
if (backed)
{
testcase("iterate backed");
}
else
{
testcase("iterate unbacked");
}
{
static constexpr std::array keys{
uint256(
"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8"),
uint256(
"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e"
"5a772c6ca8")};
tests::TestNodeFamily tf{journal};
SHAMap map{SHAMapType::FREE, tf};
if (!backed)
map.setUnbacked();
for (auto const& k : keys)
{
map.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(k, intToVuc(0)));
map.invariants();
}
int h = 7;
for (auto const& k : map)
{
BEAST_EXPECT(k.key() == keys[h]);
--h;
}
}
}
};
class SHAMapPathProof_test : public beast::unit_test::Suite
{
void
run() override
{
test::SuiteJournal journal("SHAMapPathProof_test", *this);
tests::TestNodeFamily tf{journal};
SHAMap map{SHAMapType::FREE, tf};
map.setUnbacked();
uint256 key;
uint256 rootHash;
std::vector<Blob> goodPath;
for (unsigned char c = 1; c < 100; ++c)
{
uint256 k(c);
map.addItem(
SHAMapNodeType::TnAccountState, makeShamapitem(k, Slice{k.data(), k.size()}));
map.invariants();
auto root = map.getHash().asUInt256();
auto path = map.getProofPath(k);
BEAST_EXPECT(path);
if (!path)
break;
BEAST_EXPECT(map.verifyProofPath(root, k, *path));
if (c == 1)
{
// extra node
path->insert(path->begin(), path->front());
BEAST_EXPECT(!map.verifyProofPath(root, k, *path));
// wrong key
uint256 const wrongKey(c + 1);
BEAST_EXPECT(!map.getProofPath(wrongKey));
}
if (c == 99)
{
key = k;
rootHash = root;
goodPath = std::move(*path);
}
}
// still good
BEAST_EXPECT(map.verifyProofPath(rootHash, key, goodPath));
// empty path
std::vector<Blob> badPath;
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
// too long
badPath = goodPath;
badPath.push_back(goodPath.back());
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
// bad node
badPath.clear();
badPath.emplace_back(100, 100);
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
// bad node type
badPath.clear();
badPath.push_back(goodPath.front());
badPath.front().back()--; // change node type
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
// all inner
badPath.clear();
badPath = goodPath;
badPath.erase(badPath.begin());
BEAST_EXPECT(!map.verifyProofPath(rootHash, key, badPath));
}
};
BEAST_DEFINE_TESTSUITE(SHAMap, shamap, xrpl);
BEAST_DEFINE_TESTSUITE(SHAMapPathProof, shamap, xrpl);
} // namespace xrpl::tests

View File

@@ -29,6 +29,8 @@ set(test_modules
basics
crypto
json
resource
shamap
tx
protocol_autogen
)

View File

@@ -0,0 +1,241 @@
#include <xrpl/resource/detail/Logic.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/insight/NullCollector.h>
#include <xrpl/beast/net/IPAddressV4.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/resource/Charge.h>
#include <xrpl/resource/Consumer.h>
#include <xrpl/resource/Disposition.h>
#include <xrpl/resource/Gossip.h>
#include <xrpl/resource/detail/Tuning.h>
#include <boost/utility/base_from_member.hpp>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <chrono>
#include <cstdint>
#include <string>
namespace xrpl::Resource {
class ResourceManagerTest : public ::testing::Test
{
protected:
beast::Journal const j_{TestSink::instance()};
class TestLogic : private boost::base_from_member<TestStopwatch>, public Logic
{
private:
using clock_type = boost::base_from_member<TestStopwatch>;
public:
explicit TestLogic(beast::Journal journal)
: Logic(beast::insight::NullCollector::make(), member, journal)
{
}
void
advance()
{
++member;
}
TestStopwatch&
clock()
{
return member;
}
};
//--------------------------------------------------------------------------
static void
populateGossip(Gossip& gossip)
{
std::uint8_t const v(10 + randInt(9));
std::uint8_t const n(10 + randInt(9));
gossip.items.reserve(n);
for (std::uint8_t i = 0; i < n; ++i)
{
Gossip::Item item;
item.balance = 100 + randInt(499);
beast::IP::AddressV4::bytes_type const d = {{
192,
0,
2,
static_cast<std::uint8_t>(v + i),
}};
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
gossip.items.push_back(item);
}
}
};
TEST_F(ResourceManagerTest, limited_warn_drop)
{
TestLogic logic{j_};
Charge const fee{kDropThreshold + 1};
beast::IP::Endpoint const addr{beast::IP::Endpoint::fromString("192.0.2.2")};
{
Consumer c{logic.newInboundEndpoint(addr)};
// Create load until we get a warning
int n = 10000;
bool warned = false;
while (--n >= 0)
{
if (c.charge(fee) == Disposition::Warn)
{
warned = true;
break;
}
++logic.clock();
}
ASSERT_TRUE(warned) << "Loop count exceeded without warning";
// Create load until we get dropped
bool dropped = false;
while (--n >= 0)
{
if (c.charge(fee) == Disposition::Drop)
{
dropped = true;
// Disconnect abusive Consumer
EXPECT_TRUE(c.disconnect(j_));
break;
}
++logic.clock();
}
ASSERT_TRUE(dropped) << "Loop count exceeded without dropping";
}
// Make sure the consumer is on the blacklist for a while.
{
Consumer const c{logic.newInboundEndpoint(addr)};
logic.periodicActivity();
EXPECT_EQ(c.disposition(), Disposition::Drop) << "Dropped consumer not put on blacklist";
}
// Makes sure the Consumer is eventually removed from blacklist
bool readmitted = false;
{
using namespace std::chrono_literals;
// Give Consumer time to become readmitted. Should never
// exceed expiration time.
auto n = kSecondsUntilExpiration + 1s;
while (--n > 0s)
{
++logic.clock();
logic.periodicActivity();
Consumer const c{logic.newInboundEndpoint(addr)};
if (c.disposition() != Disposition::Drop)
{
readmitted = true;
break;
}
}
}
EXPECT_TRUE(readmitted) << "Dropped Consumer left on blacklist too long";
}
TEST_F(ResourceManagerTest, unlimited_warn_drop)
{
TestLogic logic{j_};
Charge const fee{kDropThreshold + 1};
beast::IP::Endpoint const addr{beast::IP::Endpoint::fromString("192.0.2.2")};
Consumer c{logic.newUnlimitedEndpoint(addr)};
// Create load until we get a warning
int n = 10000;
bool warned = false;
while (--n >= 0)
{
if (c.charge(fee) == Disposition::Warn)
{
warned = true;
break;
}
++logic.clock();
}
EXPECT_FALSE(warned) << "Should loop forever with no warning";
}
TEST_F(ResourceManagerTest, charges)
{
TestLogic logic{j_};
{
beast::IP::Endpoint const address{beast::IP::Endpoint::fromString("192.0.2.1")};
Consumer c{logic.newInboundEndpoint(address)};
Charge const fee{1000};
JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second";
c.charge(fee);
for (int i = 0; i < 128; ++i)
{
JLOG(j_.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
<< ", Balance = " << c.balance();
logic.advance();
}
}
{
beast::IP::Endpoint const address{beast::IP::Endpoint::fromString("192.0.2.2")};
Consumer c{logic.newInboundEndpoint(address)};
Charge const fee{1000};
JLOG(j_.info()) << "Charging " << c.toString() << " " << fee << " per second";
for (int i = 0; i < 128; ++i)
{
c.charge(fee);
JLOG(j_.info()) << "Time= " << logic.clock().now().time_since_epoch().count()
<< ", Balance = " << c.balance();
logic.advance();
}
}
}
TEST_F(ResourceManagerTest, imports)
{
TestLogic logic{j_};
Gossip g[5];
for (auto& i : g)
populateGossip(i);
for (int i = 0; i < 5; ++i)
logic.importConsumers(std::to_string(i), g[i]);
}
TEST_F(ResourceManagerTest, import)
{
TestLogic logic{j_};
Gossip g;
Gossip::Item item;
item.balance = 100;
beast::IP::AddressV4::bytes_type const d = {{
192,
0,
2,
1,
}};
item.address = beast::IP::Endpoint{beast::IP::AddressV4{d}};
g.items.push_back(item);
logic.importConsumers("g", g);
}
} // namespace xrpl::Resource

View File

@@ -0,0 +1,22 @@
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/shamap/SHAMap.h>
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <shamap/common.h>
#include <memory>
namespace xrpl::tests {
TEST(FetchPackTest, construct_table)
{
beast::Journal const j{TestSink::instance()};
TestNodeFamily f{j};
std::shared_ptr<SHAMap> const t1{std::make_shared<SHAMap>(SHAMapType::FREE, f)};
EXPECT_NE(t1, nullptr);
}
} // namespace xrpl::tests

View File

@@ -0,0 +1,349 @@
#include <xrpl/shamap/SHAMap.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Buffer.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/shamap/SHAMapInnerNode.h>
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapLeafNode.h>
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <shamap/common.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
namespace xrpl::tests {
#ifndef __INTELLISENSE__
static_assert(std::is_nothrow_destructible<SHAMap>{});
static_assert(!std::is_default_constructible<SHAMap>{});
static_assert(!std::is_copy_constructible<SHAMap>{});
static_assert(!std::is_copy_assignable<SHAMap>{});
static_assert(!std::is_move_constructible<SHAMap>{});
static_assert(!std::is_move_assignable<SHAMap>{});
static_assert(std::is_nothrow_destructible<SHAMap::ConstIterator>{});
static_assert(std::is_copy_constructible<SHAMap::ConstIterator>{});
static_assert(std::is_copy_assignable<SHAMap::ConstIterator>{});
static_assert(std::is_move_constructible<SHAMap::ConstIterator>{});
static_assert(std::is_move_assignable<SHAMap::ConstIterator>{});
static_assert(std::is_nothrow_destructible<SHAMapItem>{});
static_assert(!std::is_default_constructible<SHAMapItem>{});
static_assert(!std::is_copy_constructible<SHAMapItem>{});
static_assert(std::is_nothrow_destructible<SHAMapNodeID>{});
static_assert(std::is_default_constructible<SHAMapNodeID>{});
static_assert(std::is_copy_constructible<SHAMapNodeID>{});
static_assert(std::is_copy_assignable<SHAMapNodeID>{});
static_assert(std::is_move_constructible<SHAMapNodeID>{});
static_assert(std::is_move_assignable<SHAMapNodeID>{});
static_assert(std::is_nothrow_destructible<SHAMapHash>{});
static_assert(std::is_default_constructible<SHAMapHash>{});
static_assert(std::is_copy_constructible<SHAMapHash>{});
static_assert(std::is_copy_assignable<SHAMapHash>{});
static_assert(std::is_move_constructible<SHAMapHash>{});
static_assert(std::is_move_assignable<SHAMapHash>{});
static_assert(std::is_nothrow_destructible<SHAMapTreeNode>{});
static_assert(!std::is_default_constructible<SHAMapTreeNode>{});
static_assert(!std::is_copy_constructible<SHAMapTreeNode>{});
static_assert(!std::is_copy_assignable<SHAMapTreeNode>{});
static_assert(!std::is_move_constructible<SHAMapTreeNode>{});
static_assert(!std::is_move_assignable<SHAMapTreeNode>{});
static_assert(std::is_nothrow_destructible<SHAMapInnerNode>{});
static_assert(!std::is_default_constructible<SHAMapInnerNode>{});
static_assert(!std::is_copy_constructible<SHAMapInnerNode>{});
static_assert(!std::is_copy_assignable<SHAMapInnerNode>{});
static_assert(!std::is_move_constructible<SHAMapInnerNode>{});
static_assert(!std::is_move_assignable<SHAMapInnerNode>{});
static_assert(std::is_nothrow_destructible<SHAMapLeafNode>{});
static_assert(!std::is_default_constructible<SHAMapLeafNode>{});
static_assert(!std::is_copy_constructible<SHAMapLeafNode>{});
static_assert(!std::is_copy_assignable<SHAMapLeafNode>{});
static_assert(!std::is_move_constructible<SHAMapLeafNode>{});
static_assert(!std::is_move_assignable<SHAMapLeafNode>{});
#endif
inline bool
operator!=(SHAMapItem const& a, SHAMapItem const& b)
{
return a.key() != b.key();
}
struct SHAMapBackingMode
{
bool backed;
std::string_view testName;
};
constexpr SHAMapBackingMode kBackedMode{.backed = true, .testName = "backed"};
constexpr SHAMapBackingMode kUnbackedMode{.backed = false, .testName = "unbacked"};
std::string
shamapBackingModeName(::testing::TestParamInfo<SHAMapBackingMode> const& info)
{
return std::string{info.param.testName};
}
class SHAMapTest : public ::testing::TestWithParam<SHAMapBackingMode>
{
protected:
beast::Journal const j_{TestSink::instance()};
static Buffer
intToVuc(std::uint8_t v)
{
Buffer vuc{32};
std::fill_n(vuc.data(), vuc.size(), v);
return vuc;
}
};
TEST_P(SHAMapTest, add_traverse_snapshot_build_tear_and_iterate)
{
auto const testMode = GetParam();
tests::TestNodeFamily f{j_};
// kH3 and kH4 differ only in the leaf, same terminal node (level 19)
constexpr uint256 kH1("092891fe4ef6cee585fdc6fda0e09eb4d386363158ec3321b8123e5a772c6ca7");
constexpr uint256 kH2("436ccbac3347baa1f1e53baeef1f43334da88f1f6d70d963b833afd6dfa289fe");
constexpr uint256 kH3("b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8");
constexpr uint256 kH4("b92891fe4ef6cee585fdc6fda2e09eb4d386363158ec3321b8123e5a772c6ca8");
SHAMap sMap{SHAMapType::FREE, f};
sMap.invariants();
if (!testMode.backed)
sMap.setUnbacked();
auto i1 = makeShamapitem(kH1, intToVuc(1));
auto i2 = makeShamapitem(kH2, intToVuc(2));
auto i3 = makeShamapitem(kH3, intToVuc(3));
auto i4 = makeShamapitem(kH4, intToVuc(4));
EXPECT_TRUE(sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i2))) << "no add";
sMap.invariants();
EXPECT_TRUE(sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i1))) << "no add";
sMap.invariants();
auto i = sMap.begin();
auto e = sMap.end();
EXPECT_FALSE(i == e || (*i != *i1)) << "bad traverse";
++i;
EXPECT_FALSE(i == e || (*i != *i2)) << "bad traverse";
++i;
EXPECT_EQ(i, e) << "bad traverse";
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i4));
sMap.invariants();
sMap.delItem(i2->key());
sMap.invariants();
sMap.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(*i3));
sMap.invariants();
i = sMap.begin();
e = sMap.end();
EXPECT_FALSE(i == e || (*i != *i1)) << "bad traverse";
++i;
EXPECT_FALSE(i == e || (*i != *i3)) << "bad traverse";
++i;
EXPECT_FALSE(i == e || (*i != *i4)) << "bad traverse";
++i;
EXPECT_EQ(i, e) << "bad traverse";
SHAMapHash const mapHash = sMap.getHash();
std::shared_ptr<SHAMap> const map2 = sMap.snapShot(false);
map2->invariants();
EXPECT_EQ(sMap.getHash(), mapHash) << "bad snapshot";
EXPECT_EQ(map2->getHash(), mapHash) << "bad snapshot";
SHAMap::Delta delta;
ASSERT_TRUE(sMap.compare(*map2, delta, 100));
EXPECT_TRUE(delta.empty());
EXPECT_TRUE(sMap.delItem(sMap.begin()->key())) << "bad mod";
sMap.invariants();
EXPECT_NE(sMap.getHash(), mapHash) << "bad snapshot";
EXPECT_EQ(map2->getHash(), mapHash) << "bad snapshot";
ASSERT_TRUE(sMap.compare(*map2, delta, 100));
ASSERT_EQ(delta.size(), 1);
EXPECT_EQ(delta.begin()->first, kH1);
EXPECT_EQ(delta.begin()->second.first, nullptr);
ASSERT_NE(delta.begin()->second.second, nullptr);
EXPECT_EQ(delta.begin()->second.second->key(), kH1);
sMap.dump();
{
constexpr std::array kKeys{
uint256{"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
};
constexpr std::array kHashes{
uint256{"B7387CFEA0465759ADC718E8C42B52D2309D179B326E239EB5075C64B6281F7F"},
uint256{"FBC195A9592A54AB44010274163CB6BA95F497EC5BA0A8831845467FB2ECE266"},
uint256{"4E7D2684B65DFD48937FFB775E20175C43AF0C94066F7D5679F51AE756795B75"},
uint256{"7A2F312EB203695FFD164E038E281839EEF06A1B99BFC263F3CECC6C74F93E07"},
uint256{"395A6691A372387A703FB0F2C6D2C405DAF307D0817F8F0E207596462B0E3A3E"},
uint256{"D044C0A696DE3169CC70AE216A1564D69DE96582865796142CE7D98A84D9DDE4"},
uint256{"76DCC77C4027309B5A91AD164083264D70B77B5E43E08AEDA5EBF94361143615"},
uint256{"DF4220E93ADC6F5569063A01B4DC79F8DB9553B6A3222ADE23DEA02BBE7230E5"},
};
SHAMap map{SHAMapType::FREE, f};
if (!testMode.backed)
map.setUnbacked();
EXPECT_EQ(map.getHash(), beast::kZero);
for (std::size_t k = 0; k < kKeys.size(); ++k)
{
EXPECT_TRUE(map.addItem(
SHAMapNodeType::TnTransactionNm,
makeShamapitem(kKeys[k], intToVuc(static_cast<std::uint8_t>(k)))));
EXPECT_EQ(map.getHash().asUInt256(), kHashes[k]);
map.invariants();
}
for (std::size_t k = kKeys.size(); k-- > 0;)
{
EXPECT_EQ(map.getHash().asUInt256(), kHashes[k]);
EXPECT_TRUE(map.delItem(kKeys[k]));
map.invariants();
}
EXPECT_EQ(map.getHash(), beast::kZero);
}
{
constexpr std::array kKeys{
uint256{"f22891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b99891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b92891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b92881fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b92791fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b92691fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"b91891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
uint256{"292891fe4ef6cee585fdc6fda1e09eb4d386363158ec3321b8123e5a772c6ca8"},
};
tests::TestNodeFamily tf{j_};
SHAMap map{SHAMapType::FREE, tf};
if (!testMode.backed)
map.setUnbacked();
for (auto const& k : kKeys)
{
map.addItem(SHAMapNodeType::TnTransactionNm, makeShamapitem(k, intToVuc(0)));
map.invariants();
}
int h = 7;
for (auto const& k : map)
{
EXPECT_EQ(k.key(), kKeys[h]);
--h;
}
}
}
INSTANTIATE_TEST_SUITE_P(
BackingMode,
SHAMapTest,
::testing::Values(kBackedMode, kUnbackedMode),
shamapBackingModeName);
class SHAMapPathProof : public ::testing::Test
{
protected:
beast::Journal const j_{TestSink::instance()};
};
TEST_F(SHAMapPathProof, verify_proof_path)
{
tests::TestNodeFamily tf{j_};
SHAMap map{SHAMapType::FREE, tf};
map.setUnbacked();
uint256 key;
uint256 rootHash;
std::vector<Blob> goodPath;
for (unsigned char c = 1; c < 100; ++c)
{
uint256 k(c);
map.addItem(SHAMapNodeType::TnAccountState, makeShamapitem(k, Slice{k.data(), k.size()}));
map.invariants();
auto root = map.getHash().asUInt256();
auto path = map.getProofPath(k);
if (!path)
{
ADD_FAILURE() << "Missing proof path";
return;
}
auto& proofPath = *path;
EXPECT_TRUE(map.verifyProofPath(root, k, proofPath));
if (c == 1)
{
// extra node
proofPath.insert(proofPath.begin(), proofPath.front());
EXPECT_FALSE(map.verifyProofPath(root, k, proofPath));
// wrong key
uint256 const wrongKey(c + 1);
EXPECT_FALSE(map.getProofPath(wrongKey));
}
if (c == 99)
{
key = k;
rootHash = root;
goodPath = std::move(proofPath);
}
}
// still good
EXPECT_TRUE(map.verifyProofPath(rootHash, key, goodPath));
// empty path
std::vector<Blob> badPath;
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
// too long
badPath = goodPath;
badPath.push_back(goodPath.back());
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
// bad node
badPath.clear();
badPath.emplace_back(100, 100);
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
// bad node type
badPath.clear();
badPath.push_back(goodPath.front());
badPath.front().back()--; // change node type
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
// all inner
badPath.clear();
badPath = goodPath;
badPath.erase(badPath.begin());
EXPECT_FALSE(map.verifyProofPath(rootHash, key, badPath));
}
} // namespace xrpl::tests

View File

@@ -0,0 +1,170 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/SHAMapHash.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/random.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/beast/xor_shift_engine.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/shamap/SHAMap.h>
#include <xrpl/shamap/SHAMapItem.h>
#include <xrpl/shamap/SHAMapMissingNode.h>
#include <xrpl/shamap/SHAMapTreeNode.h>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <gtest/gtest.h>
#include <helpers/TestSink.h>
#include <shamap/common.h>
#include <chrono>
#include <cstdint>
#include <list>
#include <utility>
#include <vector>
namespace xrpl::tests {
class SHAMapSyncTest : public ::testing::Test
{
protected:
beast::Journal const j_{TestSink::instance()};
beast::xor_shift_engine eng_;
boost::intrusive_ptr<SHAMapItem>
makeRandomAS()
{
Serializer s;
for (int d = 0; d < 3; ++d)
s.add32(randInt<std::uint32_t>(eng_));
return makeShamapitem(s.getSHA512Half(), s.slice());
}
bool
confuseMap(SHAMap& map, int count)
{
// add a bunch of random states to a map, then remove them
// map should be the same
SHAMapHash const beforeHash = map.getHash();
std::list<uint256> items;
for (int i = 0; i < count; ++i)
{
auto item = makeRandomAS();
items.push_back(item->key());
if (!map.addItem(SHAMapNodeType::TnAccountState, item))
{
ADD_FAILURE() << "Unable to add item to map";
return false;
}
}
for (auto const& item : items)
{
if (!map.delItem(item))
{
ADD_FAILURE() << "Unable to remove item from map";
return false;
}
}
if (beforeHash != map.getHash())
{
ADD_FAILURE() << "Hashes do not match " << beforeHash << " " << map.getHash();
return false;
}
return true;
}
};
TEST_F(SHAMapSyncTest, sync)
{
TestNodeFamily f{j_}, f2{j_};
SHAMap source{SHAMapType::FREE, f};
SHAMap destination{SHAMapType::FREE, f2};
int const items = 10000;
for (int i = 0; i < items; ++i)
{
source.addItem(SHAMapNodeType::TnAccountState, makeRandomAS());
if (i % 100 == 0)
source.invariants();
}
source.invariants();
ASSERT_TRUE(confuseMap(source, 500));
source.invariants();
source.setImmutable();
int count = 0;
source.visitLeaves([&count]([[maybe_unused]] auto const& item) { ++count; });
EXPECT_EQ(count, items);
std::vector<SHAMapMissingNode> missingNodes;
source.walkMap(missingNodes, 2048);
EXPECT_TRUE(missingNodes.empty());
destination.setSynching();
{
std::vector<std::pair<SHAMapNodeID, Blob>> a;
ASSERT_TRUE(source.getNodeFat(SHAMapNodeID(), a, randBool(eng_), randInt(eng_, 2)));
ASSERT_FALSE(a.empty()) << "NodeSize";
ASSERT_TRUE(
destination.addRootNode(source.getHash(), makeSlice(a[0].second), nullptr).isGood());
}
do
{
f.clock().advance(std::chrono::seconds(1));
// get the list of nodes we know we need
auto nodesMissing = destination.getMissingNodes(2048, nullptr);
if (nodesMissing.empty())
break;
// get as many nodes as possible based on this information
std::vector<std::pair<SHAMapNodeID, Blob>> b;
for (auto& it : nodesMissing)
{
// Keep failures fatal here because this loop is data-dependent.
// non-deterministic number of times and the number of tests run
// should be deterministic
if (!source.getNodeFat(it.first, b, randBool(eng_), randInt(eng_, 2)))
FAIL() << "Unable to fetch node";
}
// Keep failures fatal here because this loop is data-dependent.
// non-deterministic number of times and the number of tests run
// should be deterministic
if (b.empty())
FAIL() << "No nodes returned";
for (auto const& i : b)
{
// Keep failures fatal here because this loop is data-dependent.
// non-deterministic number of times and the number of tests run
// should be deterministic
if (!destination.addKnownNode(i.first, makeSlice(i.second), nullptr).isUseful())
FAIL() << "Known node was not useful";
}
} while (true);
destination.clearSynching();
EXPECT_TRUE(source.deepCompare(destination));
destination.invariants();
}
} // namespace xrpl::tests

View File

@@ -4,7 +4,6 @@
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/contract.h>
#include <xrpl/beast/clock/manual_clock.h>
#include <xrpl/beast/utility/Journal.h>
#include <xrpl/config/BasicConfig.h>
#include <xrpl/config/Constants.h>
@@ -92,13 +91,17 @@ public:
}
void
missingNodeAcquireBySeq(std::uint32_t refNum, uint256 const& nodeHash) override
missingNodeAcquireBySeq(
[[maybe_unused]] std::uint32_t refNum,
[[maybe_unused]] uint256 const& nodeHash) override
{
Throw<std::runtime_error>("missing node");
}
void
missingNodeAcquireByHash(uint256 const& refHash, std::uint32_t refNum) override
missingNodeAcquireByHash(
[[maybe_unused]] uint256 const& refHash,
[[maybe_unused]] std::uint32_t refNum) override
{
Throw<std::runtime_error>("missing node");
}
@@ -110,7 +113,7 @@ public:
(*tnCache_).reset();
}
beast::ManualClock<std::chrono::steady_clock>
TestStopwatch&
clock()
{
return clock_;