#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl::tests { #ifndef __INTELLISENSE__ static_assert(std::is_nothrow_destructible{}); static_assert(!std::is_default_constructible{}); static_assert(!std::is_copy_constructible{}); static_assert(!std::is_copy_assignable{}); static_assert(!std::is_move_constructible{}); static_assert(!std::is_move_assignable{}); static_assert(std::is_nothrow_destructible{}); static_assert(std::is_copy_constructible{}); static_assert(std::is_copy_assignable{}); static_assert(std::is_move_constructible{}); static_assert(std::is_move_assignable{}); static_assert(std::is_nothrow_destructible{}); static_assert(!std::is_default_constructible{}); static_assert(!std::is_copy_constructible{}); static_assert(std::is_nothrow_destructible{}); static_assert(std::is_default_constructible{}); static_assert(std::is_copy_constructible{}); static_assert(std::is_copy_assignable{}); static_assert(std::is_move_constructible{}); static_assert(std::is_move_assignable{}); static_assert(std::is_nothrow_destructible{}); static_assert(std::is_default_constructible{}); static_assert(std::is_copy_constructible{}); static_assert(std::is_copy_assignable{}); static_assert(std::is_move_constructible{}); static_assert(std::is_move_assignable{}); static_assert(std::is_nothrow_destructible{}); static_assert(!std::is_default_constructible{}); static_assert(!std::is_copy_constructible{}); static_assert(!std::is_copy_assignable{}); static_assert(!std::is_move_constructible{}); static_assert(!std::is_move_assignable{}); static_assert(std::is_nothrow_destructible{}); static_assert(!std::is_default_constructible{}); static_assert(!std::is_copy_constructible{}); static_assert(!std::is_copy_assignable{}); static_assert(!std::is_move_constructible{}); static_assert(!std::is_move_assignable{}); static_assert(std::is_nothrow_destructible{}); static_assert(!std::is_default_constructible{}); static_assert(!std::is_copy_constructible{}); static_assert(!std::is_copy_assignable{}); static_assert(!std::is_move_constructible{}); static_assert(!std::is_move_assignable{}); #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 const& info) { return std::string{info.param.testName}; } class SHAMapTest : public ::testing::TestWithParam { 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 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(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 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 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