#include #include #include #include #include #include #include #include #include #include TEST(BackendTest, Basic) { boost::asio::io_context ioc; std::optional work; work.emplace(ioc); std::atomic_bool done = false; boost::asio::spawn( ioc, [&done, &work, &ioc](boost::asio::yield_context yield) { boost::log::core::get()->set_filter( boost::log::trivial::severity >= boost::log::trivial::warning); std::string keyspace = "clio_test_" + std::to_string(std::chrono::system_clock::now() .time_since_epoch() .count()); boost::json::object cassandraConfig{ {"database", {{"type", "cassandra"}, {"cassandra", {{"contact_points", "127.0.0.1"}, {"port", 9042}, {"keyspace", keyspace.c_str()}, {"replication_factor", 1}, {"table_prefix", ""}, {"max_requests_outstanding", 1000}, {"indexer_key_shift", 2}, {"threads", 8}}}}}}; boost::json::object postgresConfig{ {"database", {{"type", "postgres"}, {"experimental", true}, {"postgres", {{"contact_point", "127.0.0.1"}, {"username", "postgres"}, {"database", keyspace.c_str()}, {"password", "postgres"}, {"indexer_key_shift", 2}, {"max_connections", 100}, {"threads", 8}}}}}}; std::vector configs = { cassandraConfig, postgresConfig}; for (auto& config : configs) { std::cout << keyspace << std::endl; auto backend = Backend::make_Backend(ioc, config); std::string rawHeader = "03C3141A01633CD656F91B4EBB5EB89B791BD34DBC8A04BB6F407C5335" "BC54351E" "DD73" "3898497E809E04074D14D271E4832D7888754F9230800761563A292FA2" "315A6DB6" "FE30" "CC5909B285080FCD6773CC883F9FE0EE4D439340AC592AADB973ED3CF5" "3E2232B3" "3EF5" "7CECAC2816E3122816E31A0A00F8377CD95DFA484CFAE282656A58CE5A" "A29652EF" "FD80" "AC59CD91416E4E13DBBE"; auto hexStringToBinaryString = [](auto const& hex) { auto blob = ripple::strUnHex(hex); std::string strBlob; for (auto c : *blob) { strBlob += c; } return strBlob; }; auto binaryStringToUint256 = [](auto const& bin) -> ripple::uint256 { ripple::uint256 uint; return uint.fromVoid((void const*)bin.data()); }; auto ledgerInfoToBinaryString = [](auto const& info) { auto blob = RPC::ledgerInfoToBlob(info, true); std::string strBlob; for (auto c : blob) { strBlob += c; } return strBlob; }; std::string rawHeaderBlob = hexStringToBinaryString(rawHeader); ripple::LedgerInfo lgrInfo = deserializeHeader(ripple::makeSlice(rawHeaderBlob)); backend->startWrites(); backend->writeLedger(lgrInfo, std::move(rawHeaderBlob)); backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfo.seq, uint256ToString(Backend::lastKey)); ASSERT_TRUE(backend->finishWrites(lgrInfo.seq)); { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng.has_value()); EXPECT_EQ(rng->minSequence, rng->maxSequence); EXPECT_EQ(rng->maxSequence, lgrInfo.seq); } { auto seq = backend->fetchLatestLedgerSequence(yield); EXPECT_TRUE(seq.has_value()); EXPECT_EQ(*seq, lgrInfo.seq); } { std::cout << "fetching ledger by sequence" << std::endl; auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield); std::cout << "fetched ledger by sequence" << std::endl; ASSERT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfo.seq); EXPECT_EQ( RPC::ledgerInfoToBlob(lgrInfo), RPC::ledgerInfoToBlob(*retLgr)); } EXPECT_FALSE( backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield) .has_value()); auto lgrInfoOld = lgrInfo; auto lgrInfoNext = lgrInfo; lgrInfoNext.seq = lgrInfo.seq + 1; lgrInfoNext.parentHash = lgrInfo.hash; lgrInfoNext.hash++; lgrInfoNext.accountHash = ~lgrInfo.accountHash; { std::string rawHeaderBlob = ledgerInfoToBinaryString(lgrInfoNext); backend->startWrites(); backend->writeLedger(lgrInfoNext, std::move(rawHeaderBlob)); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng.has_value()); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); } { auto seq = backend->fetchLatestLedgerSequence(yield); EXPECT_EQ(seq, lgrInfoNext.seq); } { std::cout << "fetching ledger by sequence" << std::endl; auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); std::cout << "fetched ledger by sequence" << std::endl; EXPECT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); EXPECT_NE( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); retLgr = backend->fetchLedgerBySequence( lgrInfoNext.seq - 1, yield); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); EXPECT_NE( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); retLgr = backend->fetchLedgerBySequence( lgrInfoNext.seq - 2, yield); EXPECT_FALSE( backend ->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield) .has_value()); auto txns = backend->fetchAllTransactionsInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); auto hashes = backend->fetchAllTransactionHashesInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(hashes.size(), 0); } // the below dummy data is not expected to be consistent. The // metadata string does represent valid metadata. Don't assume // though that the transaction or its hash correspond to the // metadata, or anything like that. These tests are purely // binary tests to make sure the same data that goes in, comes // back out std::string metaHex = "201C0000001AF8E411006F560A3E08122A05AC91DEFA87052B0554E4A2" "9B46" "3A27642EBB060B6052196592EEE72200000000240480FDB52503CE1A86" "3300" "000000000000003400000000000000005529983CBAED30F54747145292" "1C3C" "6B9F9685F292F6291000EED0A44413AF18C250101AC09600F4B502C8F7" "F830" "F80B616DCB6F3970CB79AB70975A05ED5B66860B9564400000001FE217" "CB65" "D54B640B31521B05000000000000000000000000434E59000000000003" "60E3" "E0751BD9A566CD03FA6CAFC78118B82BA081142252F328CF9126341776" "2570" "D67220CCB33B1370E1E1E3110064561AC09600F4B502C8F7F830F80B61" "6DCB" "6F3970CB79AB70975A05ED33DF783681E8365A05ED33DF783681581AC0" "9600" "F4B502C8F7F830F80B616DCB6F3970CB79AB70975A05ED33DF78368103" "1100" "0000000000000000000000434E59000000000004110360E3E0751BD9A5" "66CD" "03FA6CAFC78118B82BA0E1E1E4110064561AC09600F4B502C8F7F830F8" "0B61" "6DCB6F3970CB79AB70975A05ED5B66860B95E72200000000365A05ED5B" "6686" "0B95581AC09600F4B502C8F7F830F80B616DCB6F3970CB79AB70975A05" "ED5B" "66860B9501110000000000000000000000000000000000000000021100" "0000" "0000000000000000000000000000000000031100000000000000000000" "0000" "434E59000000000004110360E3E0751BD9A566CD03FA6CAFC78118B82B" "A0E1" "E1E311006F5647B05E66DE9F3DF2689E8F4CE6126D3136B6C5E79587F9" "D24B" "D71A952B0852BAE8240480FDB950101AC09600F4B502C8F7F830F80B61" "6DCB" "6F3970CB79AB70975A05ED33DF78368164400000033C83A95F65D59D9A" "6291" "9C2D18000000000000000000000000434E5900000000000360E3E0751B" "D9A5" "66CD03FA6CAFC78118B82BA081142252F328CF91263417762570D67220" "CCB3" "3B1370E1E1E511006456AEA3074F10FE15DAC592F8A0405C61FB7D4C98" "F588" "C2D55C84718FAFBBD2604AE72200000000310000000000000000320000" "0000" "0000000058AEA3074F10FE15DAC592F8A0405C61FB7D4C98F588C2D55C" "8471" "8FAFBBD2604A82142252F328CF91263417762570D67220CCB33B1370E1" "E1E5" "1100612503CE1A8755CE935137F8C6C8DEF26B5CD93BE18105CA83F65E" "1E90" "CEC546F562D25957DC0856E0311EB450B6177F969B94DBDDA83E99B7A0" "576A" "CD9079573876F16C0C004F06E6240480FDB9624000000005FF0E2BE1E7" "2200" "000000240480FDBA2D00000005624000000005FF0E1F81142252F328CF" "9126" "3417762570D67220CCB33B1370E1E1F1031000"; std::string txnHex = "1200072200000000240480FDB920190480FDB5201B03CE1A8964400000" "033C" "83A95F65D59D9A62919C2D18000000000000000000000000434E590000" "0000" "000360E3E0751BD9A566CD03FA6CAFC78118B82BA06840000000000000" "0C73" "21022D40673B44C82DEE1DDB8B9BB53DCCE4F97B27404DB850F068DD91" "D685" "E337EA7446304402202EA6B702B48B39F2197112382838F92D4C02948E" "9911" "FE6B2DEBCF9183A426BC022005DAC06CD4517E86C2548A80996019F3AC" "60A0" "9EED153BF60C992930D68F09F981142252F328CF91263417762570D672" "20CC" "B33B1370"; std::string hashHex = "0A81FB3D6324C2DCF73131505C6E4DC67981D7FC39F5E9574CEC4B1F22" "D28BF7"; // this account is not related to the above transaction and // metadata std::string accountHex = "1100612200000000240480FDBC2503CE1A872D0000000555516931B2AD" "018EFFBE" "17C5" "C9DCCF872F36837C2C6136ACF80F2A24079CF81FD0624000000005FF0E" "07811422" "52F3" "28CF91263417762570D67220CCB33B1370"; std::string accountIndexHex = "E0311EB450B6177F969B94DBDDA83E99B7A0576ACD9079573876F16C0C" "004F06"; // An NFTokenMint tx std::string nftTxnHex = "1200192200000008240011CC9B201B001F71D6202A0000000168400000" "000000000C7321ED475D1452031E8F9641AF1631519A58F7B8681E172E" "4838AA0E59408ADA1727DD74406960041F34F10E0CBB39444B4D4E577F" "C0B7E8D843D091C2917E96E7EE0E08B30C91413EC551A2B8A1D405E8BA" "34FE185D8B10C53B40928611F2DE3B746F0303751868747470733A2F2F" "677265677765697362726F642E636F6D81146203F49C21D5D6E022CB16" "DE3538F248662FC73C"; std::string nftTxnMeta = "201C00000001F8E511005025001F71B3556ED9C9459001E4F4A9121F4E" "07AB6D14898A5BBEF13D85C25D743540DB59F3CF566203F49C21D5D6E0" "22CB16DE3538F248662FC73CFFFFFFFFFFFFFFFFFFFFFFFFE6FAEC5A00" "0800006203F49C21D5D6E022CB16DE3538F248662FC73C8962EFA00000" "0006751868747470733A2F2F677265677765697362726F642E636F6DE1" "EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73C93E8B1" "C200000028751868747470733A2F2F677265677765697362726F642E63" "6F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73C" "9808B6B90000001D751868747470733A2F2F677265677765697362726F" "642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F24866" "2FC73C9C28BBAC00000012751868747470733A2F2F6772656777656973" "62726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538" "F248662FC73CA048C0A300000007751868747470733A2F2F6772656777" "65697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16" "DE3538F248662FC73CAACE82C500000029751868747470733A2F2F6772" "65677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E0" "22CB16DE3538F248662FC73CAEEE87B80000001E751868747470733A2F" "2F677265677765697362726F642E636F6DE1EC5A000800006203F49C21" "D5D6E022CB16DE3538F248662FC73CB30E8CAF00000013751868747470" "733A2F2F677265677765697362726F642E636F6DE1EC5A000800006203" "F49C21D5D6E022CB16DE3538F248662FC73CB72E91A200000008751868" "747470733A2F2F677265677765697362726F642E636F6DE1EC5A000800" "006203F49C21D5D6E022CB16DE3538F248662FC73CC1B453C40000002A" "751868747470733A2F2F677265677765697362726F642E636F6DE1EC5A" "000800006203F49C21D5D6E022CB16DE3538F248662FC73CC5D458BB00" "00001F751868747470733A2F2F677265677765697362726F642E636F6D" "E1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CC9F4" "5DAE00000014751868747470733A2F2F677265677765697362726F642E" "636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC7" "3CCE1462A500000009751868747470733A2F2F67726567776569736272" "6F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248" "662FC73CD89A24C70000002B751868747470733A2F2F67726567776569" "7362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE35" "38F248662FC73CDCBA29BA00000020751868747470733A2F2F67726567" "7765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB" "16DE3538F248662FC73CE0DA2EB100000015751868747470733A2F2F67" "7265677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6" "E022CB16DE3538F248662FC73CE4FA33A40000000A751868747470733A" "2F2F677265677765697362726F642E636F6DE1EC5A000800006203F49C" "21D5D6E022CB16DE3538F248662FC73CF39FFABD000000217518687474" "70733A2F2F677265677765697362726F642E636F6DE1EC5A0008000062" "03F49C21D5D6E022CB16DE3538F248662FC73CF7BFFFB0000000167518" "68747470733A2F2F677265677765697362726F642E636F6DE1EC5A0008" "00006203F49C21D5D6E022CB16DE3538F248662FC73CFBE004A7000000" "0B751868747470733A2F2F677265677765697362726F642E636F6DE1F1" "E1E72200000000501A6203F49C21D5D6E022CB16DE3538F248662FC73C" "662FC73C8962EFA000000006FAEC5A000800006203F49C21D5D6E022CB" "16DE3538F248662FC73C8962EFA000000006751868747470733A2F2F67" "7265677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6" "E022CB16DE3538F248662FC73C93E8B1C200000028751868747470733A" "2F2F677265677765697362726F642E636F6DE1EC5A000800006203F49C" "21D5D6E022CB16DE3538F248662FC73C9808B6B90000001D7518687474" "70733A2F2F677265677765697362726F642E636F6DE1EC5A0008000062" "03F49C21D5D6E022CB16DE3538F248662FC73C9C28BBAC000000127518" "68747470733A2F2F677265677765697362726F642E636F6DE1EC5A0008" "00006203F49C21D5D6E022CB16DE3538F248662FC73CA048C0A3000000" "07751868747470733A2F2F677265677765697362726F642E636F6DE1EC" "5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CAACE82C5" "00000029751868747470733A2F2F677265677765697362726F642E636F" "6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CAE" "EE87B80000001E751868747470733A2F2F677265677765697362726F64" "2E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662F" "C73CB30E8CAF00000013751868747470733A2F2F677265677765697362" "726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F2" "48662FC73CB72E91A200000008751868747470733A2F2F677265677765" "697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE" "3538F248662FC73CC1B453C40000002A751868747470733A2F2F677265" "677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022" "CB16DE3538F248662FC73CC5D458BB0000001F751868747470733A2F2F" "677265677765697362726F642E636F6DE1EC5A000800006203F49C21D5" "D6E022CB16DE3538F248662FC73CC9F45DAE0000001475186874747073" "3A2F2F677265677765697362726F642E636F6DE1EC5A000800006203F4" "9C21D5D6E022CB16DE3538F248662FC73CCE1462A50000000975186874" "7470733A2F2F677265677765697362726F642E636F6DE1EC5A00080000" "6203F49C21D5D6E022CB16DE3538F248662FC73CD89A24C70000002B75" "1868747470733A2F2F677265677765697362726F642E636F6DE1EC5A00" "0800006203F49C21D5D6E022CB16DE3538F248662FC73CDCBA29BA0000" "0020751868747470733A2F2F677265677765697362726F642E636F6DE1" "EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73CE0DA2E" "B100000015751868747470733A2F2F677265677765697362726F642E63" "6F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F248662FC73C" "E4FA33A40000000A751868747470733A2F2F677265677765697362726F" "642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538F24866" "2FC73CEF7FF5C60000002C751868747470733A2F2F6772656777656973" "62726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16DE3538" "F248662FC73CF39FFABD00000021751868747470733A2F2F6772656777" "65697362726F642E636F6DE1EC5A000800006203F49C21D5D6E022CB16" "DE3538F248662FC73CF7BFFFB000000016751868747470733A2F2F6772" "65677765697362726F642E636F6DE1EC5A000800006203F49C21D5D6E0" "22CB16DE3538F248662FC73CFBE004A70000000B751868747470733A2F" "2F677265677765697362726F642E636F6DE1F1E1E1E511006125001F71" "B3556ED9C9459001E4F4A9121F4E07AB6D14898A5BBEF13D85C25D7435" "40DB59F3CF56BE121B82D5812149D633F605EB07265A80B762A365CE94" "883089FEEE4B955701E6240011CC9B202B0000002C6240000002540BE3" "ECE1E72200000000240011CC9C2D0000000A202B0000002D202C000000" "066240000002540BE3E081146203F49C21D5D6E022CB16DE3538F24866" "2FC73CE1E1F1031000"; std::string nftTxnHashHex = "6C7F69A6D25A13AC4A2E9145999F45D4674F939900017A96885FDC2757" "E9284E"; ripple::uint256 nftID; EXPECT_TRUE( nftID.parseHex("000800006203F49C21D5D6E022CB16DE3538F248662" "FC73CEF7FF5C60000002C")); std::string metaBlob = hexStringToBinaryString(metaHex); std::string txnBlob = hexStringToBinaryString(txnHex); std::string hashBlob = hexStringToBinaryString(hashHex); std::string accountBlob = hexStringToBinaryString(accountHex); std::string accountIndexBlob = hexStringToBinaryString(accountIndexHex); std::vector affectedAccounts; std::string nftTxnBlob = hexStringToBinaryString(nftTxnHex); std::string nftTxnMetaBlob = hexStringToBinaryString(nftTxnMeta); { backend->startWrites(); lgrInfoNext.seq = lgrInfoNext.seq + 1; lgrInfoNext.txHash = ~lgrInfo.txHash; lgrInfoNext.accountHash = lgrInfoNext.accountHash ^ lgrInfoNext.txHash; lgrInfoNext.parentHash = lgrInfoNext.hash; lgrInfoNext.hash++; ripple::uint256 hash256; EXPECT_TRUE(hash256.parseHex(hashHex)); ripple::TxMeta txMeta{hash256, lgrInfoNext.seq, metaBlob}; auto journal = ripple::debugLog(); auto accountsSet = txMeta.getAffectedAccounts(); for (auto& a : accountsSet) { affectedAccounts.push_back(a); } std::vector accountTxData; accountTxData.emplace_back(txMeta, hash256, journal); ripple::uint256 nftHash256; EXPECT_TRUE(nftHash256.parseHex(nftTxnHashHex)); ripple::TxMeta nftTxMeta{ nftHash256, lgrInfoNext.seq, nftTxnMetaBlob}; ripple::SerialIter it{nftTxnBlob.data(), nftTxnBlob.size()}; ripple::STTx sttx{it}; auto const [parsedNFTTxsRef, parsedNFT] = getNFTData(nftTxMeta, sttx); // need to copy the nft txns so we can std::move later std::vector parsedNFTTxs; parsedNFTTxs.insert( parsedNFTTxs.end(), parsedNFTTxsRef.begin(), parsedNFTTxsRef.end()); EXPECT_EQ(parsedNFTTxs.size(), 1); EXPECT_TRUE(parsedNFT.has_value()); EXPECT_EQ(parsedNFT->tokenID, nftID); std::vector nftData; nftData.push_back(*parsedNFT); backend->writeLedger( lgrInfoNext, std::move(ledgerInfoToBinaryString(lgrInfoNext))); backend->writeTransaction( std::move(std::string{hashBlob}), lgrInfoNext.seq, lgrInfoNext.closeTime.time_since_epoch().count(), std::move(std::string{txnBlob}), std::move(std::string{metaBlob})); backend->writeAccountTransactions(std::move(accountTxData)); // NFT writing not yet implemented for pg if (config == cassandraConfig) { backend->writeNFTs(std::move(nftData)); backend->writeNFTTransactions(std::move(parsedNFTTxs)); } else { EXPECT_THROW( { backend->writeNFTs(std::move(nftData)); }, std::runtime_error); EXPECT_THROW( { backend->writeNFTTransactions( std::move(parsedNFTTxs)); }, std::runtime_error); } backend->writeLedgerObject( std::move(std::string{accountIndexBlob}), lgrInfoNext.seq, std::move(std::string{accountBlob})); backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfoNext.seq, std::string{accountIndexBlob}); backend->writeSuccessor( std::string{accountIndexBlob}, lgrInfoNext.seq, uint256ToString(Backend::lastKey)); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 1); EXPECT_STREQ( (const char*)txns[0].transaction.data(), (const char*)txnBlob.data()); EXPECT_STREQ( (const char*)txns[0].metadata.data(), (const char*)metaBlob.data()); auto hashes = backend->fetchAllTransactionHashesInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(hashes.size(), 1); EXPECT_EQ(ripple::strHex(hashes[0]), hashHex); for (auto& a : affectedAccounts) { auto [txns, cursor] = backend->fetchAccountTransactions( a, 100, true, {}, yield); EXPECT_EQ(txns.size(), 1); EXPECT_EQ(txns[0], txns[0]); EXPECT_FALSE(cursor); } // NFT fetching not yet implemented for pg if (config == cassandraConfig) { auto nft = backend->fetchNFT(nftID, lgrInfoNext.seq, yield); EXPECT_TRUE(nft.has_value()); auto [nftTxns, cursor] = backend->fetchNFTTransactions( nftID, 100, true, {}, yield); EXPECT_EQ(nftTxns.size(), 1); EXPECT_EQ(nftTxns[0], nftTxns[0]); EXPECT_FALSE(cursor); } else { EXPECT_THROW( { backend->fetchNFT( nftID, lgrInfoNext.seq, yield); }, std::runtime_error); EXPECT_THROW( { backend->fetchNFTTransactions( nftID, 100, true, {}, yield); }, std::runtime_error); } ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq + 1, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoOld.seq - 1, yield); EXPECT_FALSE(obj); } // obtain a time-based seed: unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); std::string accountBlobOld = accountBlob; { backend->startWrites(); lgrInfoNext.seq = lgrInfoNext.seq + 1; lgrInfoNext.parentHash = lgrInfoNext.hash; lgrInfoNext.hash++; lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash; lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash); backend->writeLedger( lgrInfoNext, std::move(ledgerInfoToBinaryString(lgrInfoNext))); std::shuffle( accountBlob.begin(), accountBlob.end(), std::default_random_engine(seed)); backend->writeLedgerObject( std::move(std::string{accountIndexBlob}), lgrInfoNext.seq, std::move(std::string{accountBlob})); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq + 1, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq - 1, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlobOld.data()); obj = backend->fetchLedgerObject( key256, lgrInfoOld.seq - 1, yield); EXPECT_FALSE(obj); } { backend->startWrites(); lgrInfoNext.seq = lgrInfoNext.seq + 1; lgrInfoNext.parentHash = lgrInfoNext.hash; lgrInfoNext.hash++; lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash; lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash); backend->writeLedger( lgrInfoNext, std::move(ledgerInfoToBinaryString(lgrInfoNext))); backend->writeLedgerObject( std::move(std::string{accountIndexBlob}), lgrInfoNext.seq, std::move(std::string{})); backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfoNext.seq, uint256ToString(Backend::lastKey)); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); auto txns = backend->fetchAllTransactionsInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq, yield); EXPECT_FALSE(obj); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq + 1, yield); EXPECT_FALSE(obj); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq - 2, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlobOld.data()); obj = backend->fetchLedgerObject( key256, lgrInfoOld.seq - 1, yield); EXPECT_FALSE(obj); } auto generateObjects = [seed]( size_t numObjects, uint32_t ledgerSequence) { std::vector> res{ numObjects}; ripple::uint256 key; key = ledgerSequence * 100000; for (auto& blob : res) { ++key; std::string keyStr{(const char*)key.data(), key.size()}; blob.first = keyStr; blob.second = std::to_string(ledgerSequence) + keyStr; } return res; }; auto updateObjects = [](uint32_t ledgerSequence, auto objs) { for (auto& [key, obj] : objs) { obj = std::to_string(ledgerSequence) + obj; } return objs; }; auto generateTxns = [seed](size_t numTxns, uint32_t ledgerSequence) { std::vector< std::tuple> res{numTxns}; ripple::uint256 base; base = ledgerSequence * 100000; for (auto& blob : res) { ++base; std::string hashStr{ (const char*)base.data(), base.size()}; std::string txnStr = "tx" + std::to_string(ledgerSequence) + hashStr; std::string metaStr = "meta" + std::to_string(ledgerSequence) + hashStr; blob = std::make_tuple(hashStr, txnStr, metaStr); } return res; }; auto generateAccounts = [](uint32_t ledgerSequence, uint32_t numAccounts) { std::vector accounts; ripple::AccountID base; base = ledgerSequence * 998765; for (size_t i = 0; i < numAccounts; ++i) { ++base; accounts.push_back(base); } return accounts; }; auto generateAccountTx = [&](uint32_t ledgerSequence, auto txns) { std::vector ret; auto accounts = generateAccounts(ledgerSequence, 10); std::srand(std::time(nullptr)); uint32_t idx = 0; for (auto& [hash, txn, meta] : txns) { AccountTransactionsData data; data.ledgerSequence = ledgerSequence; data.transactionIndex = idx; data.txHash = hash; for (size_t i = 0; i < 3; ++i) { data.accounts.insert( accounts[std::rand() % accounts.size()]); } ++idx; ret.push_back(data); } return ret; }; auto generateNextLedger = [seed](auto lgrInfo) { ++lgrInfo.seq; lgrInfo.parentHash = lgrInfo.hash; std::srand(std::time(nullptr)); std::shuffle( lgrInfo.txHash.begin(), lgrInfo.txHash.end(), std::default_random_engine(seed)); std::shuffle( lgrInfo.accountHash.begin(), lgrInfo.accountHash.end(), std::default_random_engine(seed)); std::shuffle( lgrInfo.hash.begin(), lgrInfo.hash.end(), std::default_random_engine(seed)); return lgrInfo; }; auto writeLedger = [&](auto lgrInfo, auto txns, auto objs, auto accountTx, auto state) { std::cout << "writing ledger = " << std::to_string(lgrInfo.seq) << std::endl; backend->startWrites(); backend->writeLedger( lgrInfo, std::move(ledgerInfoToBinaryString(lgrInfo))); for (auto [hash, txn, meta] : txns) { backend->writeTransaction( std::move(hash), lgrInfo.seq, lgrInfo.closeTime.time_since_epoch().count(), std::move(txn), std::move(meta)); } for (auto [key, obj] : objs) { backend->writeLedgerObject( std::string{key}, lgrInfo.seq, std::string{obj}); } if (state.count(lgrInfo.seq - 1) == 0 || std::find_if( state[lgrInfo.seq - 1].begin(), state[lgrInfo.seq - 1].end(), [&](auto obj) { return obj.first == objs[0].first; }) == state[lgrInfo.seq - 1].end()) { for (size_t i = 0; i < objs.size(); ++i) { if (i + 1 < objs.size()) backend->writeSuccessor( std::string{objs[i].first}, lgrInfo.seq, std::string{objs[i + 1].first}); else backend->writeSuccessor( std::string{objs[i].first}, lgrInfo.seq, uint256ToString(Backend::lastKey)); } if (state.count(lgrInfo.seq - 1)) backend->writeSuccessor( std::string{ state[lgrInfo.seq - 1].back().first}, lgrInfo.seq, std::string{objs[0].first}); else backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfo.seq, std::string{objs[0].first}); } backend->writeAccountTransactions(std::move(accountTx)); ASSERT_TRUE(backend->finishWrites(lgrInfo.seq)); }; auto checkLedger = [&](auto lgrInfo, auto txns, auto objs, auto accountTx) { auto rng = backend->fetchLedgerRange(); auto seq = lgrInfo.seq; EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_GE(rng->maxSequence, seq); auto retLgr = backend->fetchLedgerBySequence(seq, yield); EXPECT_TRUE(retLgr); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)); // retLgr = backend->fetchLedgerByHash(lgrInfo.hash); // EXPECT_TRUE(retLgr); // EXPECT_EQ(RPC::ledgerInfoToBlob(*retLgr), // RPC::ledgerInfoToBlob(lgrInfo)); auto retTxns = backend->fetchAllTransactionsInLedger(seq, yield); for (auto [hash, txn, meta] : txns) { bool found = false; for (auto [retTxn, retMeta, retSeq, retDate] : retTxns) { if (std::strncmp( (const char*)retTxn.data(), (const char*)txn.data(), txn.size()) == 0 && std::strncmp( (const char*)retMeta.data(), (const char*)meta.data(), meta.size()) == 0) found = true; } ASSERT_TRUE(found); } for (auto [account, data] : accountTx) { std::vector retData; std::optional cursor; do { uint32_t limit = 10; auto [txns, retCursor] = backend->fetchAccountTransactions( account, limit, false, cursor, yield); if (retCursor) EXPECT_EQ(txns.size(), limit); retData.insert( retData.end(), txns.begin(), txns.end()); cursor = retCursor; } while (cursor); EXPECT_EQ(retData.size(), data.size()); for (size_t i = 0; i < retData.size(); ++i) { auto [txn, meta, seq, date] = retData[i]; auto [hash, expTxn, expMeta] = data[i]; EXPECT_STREQ( (const char*)txn.data(), (const char*)expTxn.data()); EXPECT_STREQ( (const char*)meta.data(), (const char*)expMeta.data()); } } std::vector keys; for (auto [key, obj] : objs) { auto retObj = backend->fetchLedgerObject( binaryStringToUint256(key), seq, yield); if (obj.size()) { ASSERT_TRUE(retObj.has_value()); EXPECT_STREQ( (const char*)obj.data(), (const char*)retObj->data()); } else { ASSERT_FALSE(retObj.has_value()); } keys.push_back(binaryStringToUint256(key)); } { auto retObjs = backend->fetchLedgerObjects(keys, seq, yield); ASSERT_EQ(retObjs.size(), objs.size()); for (size_t i = 0; i < keys.size(); ++i) { auto [key, obj] = objs[i]; auto retObj = retObjs[i]; if (obj.size()) { ASSERT_TRUE(retObj.size()); EXPECT_STREQ( (const char*)obj.data(), (const char*)retObj.data()); } else { ASSERT_FALSE(retObj.size()); } } } Backend::LedgerPage page; std::vector retObjs; size_t numLoops = 0; do { uint32_t limit = 10; page = backend->fetchLedgerPage( page.cursor, seq, limit, false, yield); std::cout << "fetched a page " << page.objects.size() << std::endl; if (page.cursor) std::cout << ripple::strHex(*page.cursor) << std::endl; // if (page.cursor) // EXPECT_EQ(page.objects.size(), limit); retObjs.insert( retObjs.end(), page.objects.begin(), page.objects.end()); ++numLoops; } while (page.cursor); for (auto obj : objs) { bool found = false; for (auto retObj : retObjs) { if (ripple::strHex(obj.first) == ripple::strHex(retObj.key)) { found = true; ASSERT_EQ( ripple::strHex(obj.second), ripple::strHex(retObj.blob)); } } if (found != (obj.second.size() != 0)) std::cout << ripple::strHex(obj.first) << std::endl; ASSERT_EQ(found, obj.second.size() != 0); } }; std::map< uint32_t, std::vector>> state; std::map< uint32_t, std::vector< std::tuple>> allTxns; std::unordered_map< std::string, std::pair> allTxnsMap; std::map< uint32_t, std::map>> allAccountTx; std::map lgrInfos; for (size_t i = 0; i < 10; ++i) { lgrInfoNext = generateNextLedger(lgrInfoNext); auto objs = generateObjects(25, lgrInfoNext.seq); auto txns = generateTxns(10, lgrInfoNext.seq); auto accountTx = generateAccountTx(lgrInfoNext.seq, txns); for (auto rec : accountTx) { for (auto account : rec.accounts) { allAccountTx[lgrInfoNext.seq][account].push_back( std::string{ (const char*)rec.txHash.data(), rec.txHash.size()}); } } EXPECT_EQ(objs.size(), 25); EXPECT_NE(objs[0], objs[1]); EXPECT_EQ(txns.size(), 10); EXPECT_NE(txns[0], txns[1]); std::sort(objs.begin(), objs.end()); state[lgrInfoNext.seq] = objs; writeLedger(lgrInfoNext, txns, objs, accountTx, state); allTxns[lgrInfoNext.seq] = txns; lgrInfos[lgrInfoNext.seq] = lgrInfoNext; for (auto& [hash, txn, meta] : txns) { allTxnsMap[hash] = std::make_pair(txn, meta); } } std::vector> objs; for (size_t i = 0; i < 10; ++i) { lgrInfoNext = generateNextLedger(lgrInfoNext); if (!objs.size()) objs = generateObjects(25, lgrInfoNext.seq); else objs = updateObjects(lgrInfoNext.seq, objs); auto txns = generateTxns(10, lgrInfoNext.seq); auto accountTx = generateAccountTx(lgrInfoNext.seq, txns); for (auto rec : accountTx) { for (auto account : rec.accounts) { allAccountTx[lgrInfoNext.seq][account].push_back( std::string{ (const char*)rec.txHash.data(), rec.txHash.size()}); } } EXPECT_EQ(objs.size(), 25); EXPECT_NE(objs[0], objs[1]); EXPECT_EQ(txns.size(), 10); EXPECT_NE(txns[0], txns[1]); std::sort(objs.begin(), objs.end()); state[lgrInfoNext.seq] = objs; writeLedger(lgrInfoNext, txns, objs, accountTx, state); allTxns[lgrInfoNext.seq] = txns; lgrInfos[lgrInfoNext.seq] = lgrInfoNext; for (auto& [hash, txn, meta] : txns) { allTxnsMap[hash] = std::make_pair(txn, meta); } } auto flatten = [&](uint32_t max) { std::vector> flat; std::map objs; for (auto [seq, diff] : state) { for (auto [k, v] : diff) { if (seq > max) { if (objs.count(k) == 0) objs[k] = ""; } else { objs[k] = v; } } } for (auto [key, value] : objs) { flat.push_back(std::make_pair(key, value)); } return flat; }; auto flattenAccountTx = [&](uint32_t max) { std::unordered_map< ripple::AccountID, std::vector< std::tuple>> accountTx; for (auto [seq, map] : allAccountTx) { if (seq > max) break; for (auto& [account, hashes] : map) { for (auto& hash : hashes) { auto& [txn, meta] = allTxnsMap[hash]; accountTx[account].push_back( std::make_tuple(hash, txn, meta)); } } } for (auto& [account, data] : accountTx) std::reverse(data.begin(), data.end()); return accountTx; }; for (auto [seq, diff] : state) { std::cout << "flatteneing" << std::endl; auto flat = flatten(seq); std::cout << "flattened" << std::endl; checkLedger( lgrInfos[seq], allTxns[seq], flat, flattenAccountTx(seq)); std::cout << "checked" << std::endl; } } done = true; work.reset(); }); ioc.run(); EXPECT_EQ(done, true); } TEST(Backend, cache) { using namespace Backend; boost::log::core::get()->set_filter( boost::log::trivial::severity >= boost::log::trivial::warning); SimpleCache cache; ASSERT_FALSE(cache.isFull()); cache.setFull(); // Nothing in cache { ASSERT_TRUE(cache.isFull()); ASSERT_EQ(cache.size(), 0); ASSERT_FALSE(cache.get(ripple::uint256{12}, 0)); ASSERT_FALSE(cache.getSuccessor(firstKey, 0)); ASSERT_FALSE(cache.getPredecessor(lastKey, 0)); } // insert uint32_t curSeq = 1; std::vector objs; objs.push_back({}); objs[0] = {ripple::uint256{42}, {0xCC}}; cache.update(objs, curSeq); { auto& obj = objs[0]; ASSERT_TRUE(cache.isFull()); ASSERT_EQ(cache.size(), 1); auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, curSeq + 1)); ASSERT_FALSE(cache.get(obj.key, curSeq - 1)); ASSERT_FALSE(cache.getSuccessor(obj.key, curSeq)); ASSERT_FALSE(cache.getPredecessor(obj.key, curSeq)); auto succ = cache.getSuccessor(firstKey, curSeq); ASSERT_TRUE(succ); ASSERT_EQ(*succ, obj); auto pred = cache.getPredecessor(lastKey, curSeq); ASSERT_TRUE(pred); ASSERT_EQ(pred, obj); } // update curSeq++; objs[0].blob = {0x01}; cache.update(objs, curSeq); { auto& obj = objs[0]; ASSERT_EQ(cache.size(), 1); auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, curSeq + 1)); ASSERT_FALSE(cache.get(obj.key, curSeq - 1)); ASSERT_TRUE(cache.isFull()); ASSERT_FALSE(cache.getSuccessor(obj.key, curSeq)); ASSERT_FALSE(cache.getPredecessor(obj.key, curSeq)); auto succ = cache.getSuccessor(firstKey, curSeq); ASSERT_TRUE(succ); ASSERT_EQ(*succ, obj); auto pred = cache.getPredecessor(lastKey, curSeq); ASSERT_TRUE(pred); ASSERT_EQ(*pred, obj); } // empty update curSeq++; cache.update({}, curSeq); { auto& obj = objs[0]; ASSERT_EQ(cache.size(), 1); auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_TRUE(cache.get(obj.key, curSeq - 1)); ASSERT_FALSE(cache.get(obj.key, curSeq - 2)); ASSERT_EQ(*cache.get(obj.key, curSeq - 1), obj.blob); ASSERT_FALSE(cache.getSuccessor(obj.key, curSeq)); ASSERT_FALSE(cache.getPredecessor(obj.key, curSeq)); auto succ = cache.getSuccessor(firstKey, curSeq); ASSERT_TRUE(succ); ASSERT_EQ(*succ, obj); auto pred = cache.getPredecessor(lastKey, curSeq); ASSERT_TRUE(pred); ASSERT_EQ(*pred, obj); } // delete curSeq++; objs[0].blob = {}; cache.update(objs, curSeq); { auto& obj = objs[0]; ASSERT_EQ(cache.size(), 0); auto cacheObj = cache.get(obj.key, curSeq); ASSERT_FALSE(cacheObj); ASSERT_FALSE(cache.get(obj.key, curSeq + 1)); ASSERT_FALSE(cache.get(obj.key, curSeq - 1)); ASSERT_TRUE(cache.isFull()); ASSERT_FALSE(cache.getSuccessor(obj.key, curSeq)); ASSERT_FALSE(cache.getPredecessor(obj.key, curSeq)); ASSERT_FALSE(cache.getSuccessor(firstKey, curSeq)); ASSERT_FALSE(cache.getPredecessor(lastKey, curSeq)); } // random non-existent object { ASSERT_FALSE(cache.get(ripple::uint256{23}, curSeq)); } // insert several objects curSeq++; objs.resize(10); for (size_t i = 0; i < objs.size(); ++i) { objs[i] = { ripple::uint256{i * 100 + 1}, {(unsigned char)i, (unsigned char)i * 2, (unsigned char)i + 1}}; } cache.update(objs, curSeq); { ASSERT_EQ(cache.size(), 10); for (auto& obj : objs) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, curSeq - 1)); ASSERT_FALSE(cache.get(obj.key, curSeq + 1)); } std::optional succ = {{firstKey, {}}}; size_t idx = 0; while ((succ = cache.getSuccessor(succ->key, curSeq))) { ASSERT_EQ(*succ, objs[idx++]); } ASSERT_EQ(idx, objs.size()); } // insert several more objects curSeq++; auto objs2 = objs; for (size_t i = 0; i < objs.size(); ++i) { objs2[i] = { ripple::uint256{i * 100 + 50}, {(unsigned char)i, (unsigned char)i * 3, (unsigned char)i + 5}}; } cache.update(objs2, curSeq); { ASSERT_EQ(cache.size(), 20); for (auto& obj : objs) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, curSeq - 1); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, curSeq - 2)); ASSERT_FALSE(cache.get(obj.key, curSeq + 1)); } for (auto& obj : objs2) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, curSeq - 1)); ASSERT_FALSE(cache.get(obj.key, curSeq + 1)); } std::optional succ = {{firstKey, {}}}; size_t idx = 0; while ((succ = cache.getSuccessor(succ->key, curSeq))) { if (idx % 2 == 0) ASSERT_EQ(*succ, objs[(idx++) / 2]); else ASSERT_EQ(*succ, objs2[(idx++) / 2]); } ASSERT_EQ(idx, objs.size() + objs2.size()); } // mix of inserts, updates and deletes curSeq++; for (size_t i = 0; i < objs.size(); ++i) { if (i % 2 == 0) objs[i].blob = {}; else if (i % 2 == 1) std::reverse(objs[i].blob.begin(), objs[i].blob.end()); } cache.update(objs, curSeq); { ASSERT_EQ(cache.size(), 15); for (size_t i = 0; i < objs.size(); ++i) { auto& obj = objs[i]; auto cacheObj = cache.get(obj.key, curSeq); if (i % 2 == 0) { ASSERT_FALSE(cacheObj); ASSERT_FALSE(cache.get(obj.key, curSeq - 1)); ASSERT_FALSE(cache.get(obj.key, curSeq - 2)); } else { ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, curSeq - 1)); ASSERT_FALSE(cache.get(obj.key, curSeq - 2)); } } for (auto& obj : objs2) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, curSeq - 1); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, curSeq - 2)); } auto allObjs = objs; allObjs.clear(); std::copy_if( objs.begin(), objs.end(), std::back_inserter(allObjs), [](auto obj) { return obj.blob.size() > 0; }); std::copy(objs2.begin(), objs2.end(), std::back_inserter(allObjs)); std::sort(allObjs.begin(), allObjs.end(), [](auto a, auto b) { return a.key < b.key; }); std::optional succ = {{firstKey, {}}}; size_t idx = 0; while ((succ = cache.getSuccessor(succ->key, curSeq))) { ASSERT_EQ(*succ, allObjs[idx++]); } ASSERT_EQ(idx, allObjs.size()); } } TEST(Backend, cacheBackground) { using namespace Backend; boost::log::core::get()->set_filter( boost::log::trivial::severity >= boost::log::trivial::warning); SimpleCache cache; ASSERT_FALSE(cache.isFull()); ASSERT_EQ(cache.size(), 0); uint32_t startSeq = 10; uint32_t curSeq = startSeq; std::vector bObjs; bObjs.resize(100); for (size_t i = 0; i < bObjs.size(); ++i) { bObjs[i].key = ripple::uint256{i * 3 + 1}; bObjs[i].blob = {(unsigned char)i + 1}; } { auto objs = bObjs; objs.clear(); std::copy(bObjs.begin(), bObjs.begin() + 10, std::back_inserter(objs)); cache.update(objs, startSeq); ASSERT_EQ(cache.size(), 10); ASSERT_FALSE(cache.isFull()); for (auto& obj : objs) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); } } // some updates curSeq++; std::vector objs1; for (size_t i = 0; i < bObjs.size(); ++i) { if (i % 5 == 0) objs1.push_back(bObjs[i]); } for (auto& obj : objs1) { std::reverse(obj.blob.begin(), obj.blob.end()); } cache.update(objs1, curSeq); { for (auto& obj : objs1) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (size_t i = 0; i < 10; i++) { auto& obj = bObjs[i]; auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); auto newObj = std::find_if(objs1.begin(), objs1.end(), [&](auto o) { return o.key == obj.key; }); if (newObj == objs1.end()) { ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, startSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); } else { ASSERT_EQ(*cacheObj, newObj->blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } } } { auto objs = bObjs; objs.clear(); std::copy( bObjs.begin() + 10, bObjs.begin() + 20, std::back_inserter(objs)); cache.update(objs, startSeq, true); } { for (auto& obj : objs1) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (size_t i = 0; i < 20; i++) { auto& obj = bObjs[i]; auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); auto newObj = std::find_if(objs1.begin(), objs1.end(), [&](auto o) { return o.key == obj.key; }); if (newObj == objs1.end()) { ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, startSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); } else { ASSERT_EQ(*cacheObj, newObj->blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } } } // some inserts curSeq++; auto objs2 = objs1; objs2.clear(); for (size_t i = 0; i < bObjs.size(); ++i) { if (i % 7 == 0) { auto obj = bObjs[i]; obj.key = ripple::uint256{(i + 1) * 1000}; obj.blob = {(unsigned char)(i + 1) * 100}; objs2.push_back(obj); } } cache.update(objs2, curSeq); { for (auto& obj : objs1) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (auto& obj : objs2) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (size_t i = 0; i < 20; i++) { auto& obj = bObjs[i]; auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); auto newObj = std::find_if(objs1.begin(), objs1.end(), [&](auto o) { return o.key == obj.key; }); if (newObj == objs1.end()) { ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, startSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); } else { ASSERT_EQ(*cacheObj, newObj->blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } } } { auto objs = bObjs; objs.clear(); std::copy( bObjs.begin() + 20, bObjs.begin() + 30, std::back_inserter(objs)); cache.update(objs, startSeq, true); } { for (auto& obj : objs1) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (auto& obj : objs2) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (size_t i = 0; i < 30; i++) { auto& obj = bObjs[i]; auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); auto newObj = std::find_if(objs1.begin(), objs1.end(), [&](auto o) { return o.key == obj.key; }); if (newObj == objs1.end()) { ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, startSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); } else { ASSERT_EQ(*cacheObj, newObj->blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } } } // some deletes curSeq++; auto objs3 = objs1; objs3.clear(); for (size_t i = 0; i < bObjs.size(); ++i) { if (i % 6 == 0) { auto obj = bObjs[i]; obj.blob = {}; objs3.push_back(obj); } } cache.update(objs3, curSeq); { for (auto& obj : objs1) { auto cacheObj = cache.get(obj.key, curSeq); if (std::find_if(objs3.begin(), objs3.end(), [&](auto o) { return o.key == obj.key; }) == objs3.end()) { ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } else { ASSERT_FALSE(cacheObj); } } for (auto& obj : objs2) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (auto& obj : objs3) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_FALSE(cacheObj); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (size_t i = 0; i < 30; i++) { auto& obj = bObjs[i]; auto cacheObj = cache.get(obj.key, curSeq); auto newObj = std::find_if(objs1.begin(), objs1.end(), [&](auto o) { return o.key == obj.key; }); auto delObj = std::find_if(objs3.begin(), objs3.end(), [&](auto o) { return o.key == obj.key; }); if (delObj != objs3.end()) { ASSERT_FALSE(cacheObj); ASSERT_FALSE(cache.get(obj.key, startSeq)); } else if (newObj == objs1.end()) { ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, startSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); } else { ASSERT_EQ(*cacheObj, newObj->blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } } } { auto objs = bObjs; objs.clear(); std::copy(bObjs.begin() + 30, bObjs.end(), std::back_inserter(objs)); cache.update(objs, startSeq, true); } { for (auto& obj : objs1) { auto cacheObj = cache.get(obj.key, curSeq); if (std::find_if(objs3.begin(), objs3.end(), [&](auto o) { return o.key == obj.key; }) == objs3.end()) { ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } else { ASSERT_FALSE(cacheObj); } } for (auto& obj : objs2) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (auto& obj : objs3) { auto cacheObj = cache.get(obj.key, curSeq); ASSERT_FALSE(cacheObj); ASSERT_FALSE(cache.get(obj.key, startSeq)); } for (size_t i = 0; i < bObjs.size(); i++) { auto& obj = bObjs[i]; auto cacheObj = cache.get(obj.key, curSeq); auto newObj = std::find_if(objs1.begin(), objs1.end(), [&](auto o) { return o.key == obj.key; }); auto delObj = std::find_if(objs3.begin(), objs3.end(), [&](auto o) { return o.key == obj.key; }); if (delObj != objs3.end()) { ASSERT_FALSE(cacheObj); ASSERT_FALSE(cache.get(obj.key, startSeq)); } else if (newObj == objs1.end()) { ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, startSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); } else { ASSERT_EQ(*cacheObj, newObj->blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } } } cache.setFull(); auto allObjs = bObjs; allObjs.clear(); for (size_t i = 0; i < bObjs.size(); i++) { auto& obj = bObjs[i]; auto cacheObj = cache.get(obj.key, curSeq); auto newObj = std::find_if(objs1.begin(), objs1.end(), [&](auto o) { return o.key == obj.key; }); auto delObj = std::find_if(objs3.begin(), objs3.end(), [&](auto o) { return o.key == obj.key; }); if (delObj != objs3.end()) { ASSERT_FALSE(cacheObj); ASSERT_FALSE(cache.get(obj.key, startSeq)); } else if (newObj == objs1.end()) { ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); cacheObj = cache.get(obj.key, startSeq); ASSERT_TRUE(cacheObj); ASSERT_EQ(*cacheObj, obj.blob); allObjs.push_back(obj); } else { allObjs.push_back(*newObj); ASSERT_EQ(*cacheObj, newObj->blob); ASSERT_FALSE(cache.get(obj.key, startSeq)); } } for (auto& obj : objs2) { allObjs.push_back(obj); } std::sort(allObjs.begin(), allObjs.end(), [](auto a, auto b) { return a.key < b.key; }); std::optional succ = {{firstKey, {}}}; size_t idx = 0; while ((succ = cache.getSuccessor(succ->key, curSeq))) { ASSERT_EQ(*succ, allObjs[idx++]); } ASSERT_EQ(idx, allObjs.size()); } TEST(Backend, cacheIntegration) { boost::asio::io_context ioc; std::optional work; work.emplace(ioc); std::atomic_bool done = false; boost::asio::spawn( ioc, [&ioc, &done, &work](boost::asio::yield_context yield) { boost::log::core::get()->set_filter( boost::log::trivial::severity >= boost::log::trivial::warning); std::string keyspace = "clio_test_" + std::to_string(std::chrono::system_clock::now() .time_since_epoch() .count()); boost::json::object cassandraConfig{ {"database", {{"type", "cassandra"}, {"cassandra", {{"contact_points", "127.0.0.1"}, {"port", 9042}, {"keyspace", keyspace.c_str()}, {"replication_factor", 1}, {"table_prefix", ""}, {"max_requests_outstanding", 1000}, {"indexer_key_shift", 2}, {"threads", 8}}}}}}; boost::json::object postgresConfig{ {"database", {{"type", "postgres"}, {"experimental", true}, {"postgres", {{"contact_point", "127.0.0.1"}, {"username", "postgres"}, {"database", keyspace.c_str()}, {"password", "postgres"}, {"indexer_key_shift", 2}, {"max_connections", 100}, {"threads", 8}}}}}}; std::vector configs = { cassandraConfig, postgresConfig}; for (auto& config : configs) { std::cout << keyspace << std::endl; auto backend = Backend::make_Backend(ioc, config); backend->cache().setFull(); std::string rawHeader = "03C3141A01633CD656F91B4EBB5EB89B791BD34DBC8A04BB6F407C5335" "BC54351E" "DD73" "3898497E809E04074D14D271E4832D7888754F9230800761563A292FA2" "315A6DB6" "FE30" "CC5909B285080FCD6773CC883F9FE0EE4D439340AC592AADB973ED3CF5" "3E2232B3" "3EF5" "7CECAC2816E3122816E31A0A00F8377CD95DFA484CFAE282656A58CE5A" "A29652EF" "FD80" "AC59CD91416E4E13DBBE"; // this account is not related to the above transaction and // metadata std::string accountHex = "1100612200000000240480FDBC2503CE1A872D0000000555516931B2AD" "018EFFBE" "17C5" "C9DCCF872F36837C2C6136ACF80F2A24079CF81FD0624000000005FF0E" "07811422" "52F3" "28CF91263417762570D67220CCB33B1370"; std::string accountIndexHex = "E0311EB450B6177F969B94DBDDA83E99B7A0576ACD9079573876F16C0C" "004F06"; auto hexStringToBinaryString = [](auto const& hex) { auto blob = ripple::strUnHex(hex); std::string strBlob; for (auto c : *blob) { strBlob += c; } return strBlob; }; auto binaryStringToUint256 = [](auto const& bin) -> ripple::uint256 { ripple::uint256 uint; return uint.fromVoid((void const*)bin.data()); }; auto ledgerInfoToBinaryString = [](auto const& info) { auto blob = RPC::ledgerInfoToBlob(info, true); std::string strBlob; for (auto c : blob) { strBlob += c; } return strBlob; }; std::string rawHeaderBlob = hexStringToBinaryString(rawHeader); std::string accountBlob = hexStringToBinaryString(accountHex); std::string accountIndexBlob = hexStringToBinaryString(accountIndexHex); ripple::LedgerInfo lgrInfo = deserializeHeader(ripple::makeSlice(rawHeaderBlob)); backend->startWrites(); backend->writeLedger(lgrInfo, std::move(rawHeaderBlob)); backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfo.seq, uint256ToString(Backend::lastKey)); ASSERT_TRUE(backend->finishWrites(lgrInfo.seq)); { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng.has_value()); EXPECT_EQ(rng->minSequence, rng->maxSequence); EXPECT_EQ(rng->maxSequence, lgrInfo.seq); } { auto seq = backend->fetchLatestLedgerSequence(yield); EXPECT_TRUE(seq.has_value()); EXPECT_EQ(*seq, lgrInfo.seq); } { std::cout << "fetching ledger by sequence" << std::endl; auto retLgr = backend->fetchLedgerBySequence(lgrInfo.seq, yield); std::cout << "fetched ledger by sequence" << std::endl; ASSERT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfo.seq); EXPECT_EQ( RPC::ledgerInfoToBlob(lgrInfo), RPC::ledgerInfoToBlob(*retLgr)); } EXPECT_FALSE( backend->fetchLedgerBySequence(lgrInfo.seq + 1, yield) .has_value()); auto lgrInfoOld = lgrInfo; auto lgrInfoNext = lgrInfo; lgrInfoNext.seq = lgrInfo.seq + 1; lgrInfoNext.parentHash = lgrInfo.hash; lgrInfoNext.hash++; lgrInfoNext.accountHash = ~lgrInfo.accountHash; { std::string rawHeaderBlob = ledgerInfoToBinaryString(lgrInfoNext); backend->startWrites(); backend->writeLedger(lgrInfoNext, std::move(rawHeaderBlob)); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng.has_value()); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); } { auto seq = backend->fetchLatestLedgerSequence(yield); EXPECT_EQ(seq, lgrInfoNext.seq); } { std::cout << "fetching ledger by sequence" << std::endl; auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); std::cout << "fetched ledger by sequence" << std::endl; EXPECT_TRUE(retLgr.has_value()); EXPECT_EQ(retLgr->seq, lgrInfoNext.seq); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); EXPECT_NE( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); retLgr = backend->fetchLedgerBySequence( lgrInfoNext.seq - 1, yield); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoOld)); EXPECT_NE( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); retLgr = backend->fetchLedgerBySequence( lgrInfoNext.seq - 2, yield); EXPECT_FALSE( backend ->fetchLedgerBySequence(lgrInfoNext.seq - 2, yield) .has_value()); auto txns = backend->fetchAllTransactionsInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(txns.size(), 0); auto hashes = backend->fetchAllTransactionHashesInLedger( lgrInfoNext.seq, yield); EXPECT_EQ(hashes.size(), 0); } { backend->startWrites(); lgrInfoNext.seq = lgrInfoNext.seq + 1; lgrInfoNext.txHash = ~lgrInfo.txHash; lgrInfoNext.accountHash = lgrInfoNext.accountHash ^ lgrInfoNext.txHash; lgrInfoNext.parentHash = lgrInfoNext.hash; lgrInfoNext.hash++; backend->writeLedger( lgrInfoNext, std::move(ledgerInfoToBinaryString(lgrInfoNext))); backend->writeLedgerObject( std::move(std::string{accountIndexBlob}), lgrInfoNext.seq, std::move(std::string{accountBlob})); auto key = ripple::uint256::fromVoidChecked(accountIndexBlob); backend->cache().update( {{*key, {accountBlob.begin(), accountBlob.end()}}}, lgrInfoNext.seq); backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfoNext.seq, std::string{accountIndexBlob}); backend->writeSuccessor( std::string{accountIndexBlob}, lgrInfoNext.seq, uint256ToString(Backend::lastKey)); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfoNext)); ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq + 1, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoOld.seq - 1, yield); EXPECT_FALSE(obj); } // obtain a time-based seed: unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); std::string accountBlobOld = accountBlob; { backend->startWrites(); lgrInfoNext.seq = lgrInfoNext.seq + 1; lgrInfoNext.parentHash = lgrInfoNext.hash; lgrInfoNext.hash++; lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash; lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash); backend->writeLedger( lgrInfoNext, std::move(ledgerInfoToBinaryString(lgrInfoNext))); std::shuffle( accountBlob.begin(), accountBlob.end(), std::default_random_engine(seed)); auto key = ripple::uint256::fromVoidChecked(accountIndexBlob); backend->cache().update( {{*key, {accountBlob.begin(), accountBlob.end()}}}, lgrInfoNext.seq); backend->writeLedgerObject( std::move(std::string{accountIndexBlob}), lgrInfoNext.seq, std::move(std::string{accountBlob})); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq + 1, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlob.data()); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq - 1, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlobOld.data()); obj = backend->fetchLedgerObject( key256, lgrInfoOld.seq - 1, yield); EXPECT_FALSE(obj); } { backend->startWrites(); lgrInfoNext.seq = lgrInfoNext.seq + 1; lgrInfoNext.parentHash = lgrInfoNext.hash; lgrInfoNext.hash++; lgrInfoNext.txHash = lgrInfoNext.txHash ^ lgrInfoNext.accountHash; lgrInfoNext.accountHash = ~(lgrInfoNext.accountHash ^ lgrInfoNext.txHash); backend->writeLedger( lgrInfoNext, std::move(ledgerInfoToBinaryString(lgrInfoNext))); auto key = ripple::uint256::fromVoidChecked(accountIndexBlob); backend->cache().update({{*key, {}}}, lgrInfoNext.seq); backend->writeLedgerObject( std::move(std::string{accountIndexBlob}), lgrInfoNext.seq, std::move(std::string{})); backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfoNext.seq, uint256ToString(Backend::lastKey)); ASSERT_TRUE(backend->finishWrites(lgrInfoNext.seq)); } { auto rng = backend->fetchLedgerRange(); EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_EQ(rng->maxSequence, lgrInfoNext.seq); auto retLgr = backend->fetchLedgerBySequence(lgrInfoNext.seq, yield); EXPECT_TRUE(retLgr); ripple::uint256 key256; EXPECT_TRUE(key256.parseHex(accountIndexHex)); auto obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq, yield); EXPECT_FALSE(obj); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq + 1, yield); EXPECT_FALSE(obj); obj = backend->fetchLedgerObject( key256, lgrInfoNext.seq - 2, yield); EXPECT_TRUE(obj); EXPECT_STREQ( (const char*)obj->data(), (const char*)accountBlobOld.data()); obj = backend->fetchLedgerObject( key256, lgrInfoOld.seq - 1, yield); EXPECT_FALSE(obj); } auto generateObjects = [seed]( size_t numObjects, uint32_t ledgerSequence) { std::vector> res{ numObjects}; ripple::uint256 key; key = ledgerSequence * 100000; for (auto& blob : res) { ++key; std::string keyStr{(const char*)key.data(), key.size()}; blob.first = keyStr; blob.second = std::to_string(ledgerSequence) + keyStr; } return res; }; auto updateObjects = [](uint32_t ledgerSequence, auto objs) { for (auto& [key, obj] : objs) { obj = std::to_string(ledgerSequence) + obj; } return objs; }; auto generateNextLedger = [seed](auto lgrInfo) { ++lgrInfo.seq; lgrInfo.parentHash = lgrInfo.hash; std::srand(std::time(nullptr)); std::shuffle( lgrInfo.txHash.begin(), lgrInfo.txHash.end(), std::default_random_engine(seed)); std::shuffle( lgrInfo.accountHash.begin(), lgrInfo.accountHash.end(), std::default_random_engine(seed)); std::shuffle( lgrInfo.hash.begin(), lgrInfo.hash.end(), std::default_random_engine(seed)); return lgrInfo; }; auto writeLedger = [&](auto lgrInfo, auto objs, auto state) { std::cout << "writing ledger = " << std::to_string(lgrInfo.seq); backend->startWrites(); backend->writeLedger( lgrInfo, std::move(ledgerInfoToBinaryString(lgrInfo))); std::vector cacheUpdates; for (auto [key, obj] : objs) { backend->writeLedgerObject( std::string{key}, lgrInfo.seq, std::string{obj}); auto key256 = ripple::uint256::fromVoidChecked(key); cacheUpdates.push_back( {*key256, {obj.begin(), obj.end()}}); } backend->cache().update(cacheUpdates, lgrInfo.seq); if (state.count(lgrInfo.seq - 1) == 0 || std::find_if( state[lgrInfo.seq - 1].begin(), state[lgrInfo.seq - 1].end(), [&](auto obj) { return obj.first == objs[0].first; }) == state[lgrInfo.seq - 1].end()) { for (size_t i = 0; i < objs.size(); ++i) { if (i + 1 < objs.size()) backend->writeSuccessor( std::string{objs[i].first}, lgrInfo.seq, std::string{objs[i + 1].first}); else backend->writeSuccessor( std::string{objs[i].first}, lgrInfo.seq, uint256ToString(Backend::lastKey)); } if (state.count(lgrInfo.seq - 1)) backend->writeSuccessor( std::string{ state[lgrInfo.seq - 1].back().first}, lgrInfo.seq, std::string{objs[0].first}); else backend->writeSuccessor( uint256ToString(Backend::firstKey), lgrInfo.seq, std::string{objs[0].first}); } ASSERT_TRUE(backend->finishWrites(lgrInfo.seq)); }; auto checkLedger = [&](auto lgrInfo, auto objs) { auto rng = backend->fetchLedgerRange(); auto seq = lgrInfo.seq; EXPECT_TRUE(rng); EXPECT_EQ(rng->minSequence, lgrInfoOld.seq); EXPECT_GE(rng->maxSequence, seq); auto retLgr = backend->fetchLedgerBySequence(seq, yield); EXPECT_TRUE(retLgr); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)); retLgr = backend->fetchLedgerByHash(lgrInfo.hash, yield); EXPECT_TRUE(retLgr); EXPECT_EQ( RPC::ledgerInfoToBlob(*retLgr), RPC::ledgerInfoToBlob(lgrInfo)); std::vector keys; for (auto [key, obj] : objs) { auto retObj = backend->fetchLedgerObject( binaryStringToUint256(key), seq, yield); if (obj.size()) { ASSERT_TRUE(retObj.has_value()); EXPECT_STREQ( (const char*)obj.data(), (const char*)retObj->data()); } else { ASSERT_FALSE(retObj.has_value()); } keys.push_back(binaryStringToUint256(key)); } { auto retObjs = backend->fetchLedgerObjects(keys, seq, yield); ASSERT_EQ(retObjs.size(), objs.size()); for (size_t i = 0; i < keys.size(); ++i) { auto [key, obj] = objs[i]; auto retObj = retObjs[i]; if (obj.size()) { ASSERT_TRUE(retObj.size()); EXPECT_STREQ( (const char*)obj.data(), (const char*)retObj.data()); } else { ASSERT_FALSE(retObj.size()); } } } Backend::LedgerPage page; std::vector retObjs; size_t numLoops = 0; do { uint32_t limit = 10; page = backend->fetchLedgerPage( page.cursor, seq, limit, false, yield); std::cout << "fetched a page " << page.objects.size() << std::endl; if (page.cursor) std::cout << ripple::strHex(*page.cursor) << std::endl; // if (page.cursor) // EXPECT_EQ(page.objects.size(), limit); retObjs.insert( retObjs.end(), page.objects.begin(), page.objects.end()); ++numLoops; } while (page.cursor); for (auto obj : objs) { bool found = false; for (auto retObj : retObjs) { if (ripple::strHex(obj.first) == ripple::strHex(retObj.key)) { found = true; ASSERT_EQ( ripple::strHex(obj.second), ripple::strHex(retObj.blob)); } } if (found != (obj.second.size() != 0)) std::cout << ripple::strHex(obj.first) << std::endl; ASSERT_EQ(found, obj.second.size() != 0); } }; std::map< uint32_t, std::vector>> state; std::map lgrInfos; for (size_t i = 0; i < 10; ++i) { lgrInfoNext = generateNextLedger(lgrInfoNext); auto objs = generateObjects(25, lgrInfoNext.seq); EXPECT_EQ(objs.size(), 25); EXPECT_NE(objs[0], objs[1]); std::sort(objs.begin(), objs.end()); state[lgrInfoNext.seq] = objs; writeLedger(lgrInfoNext, objs, state); lgrInfos[lgrInfoNext.seq] = lgrInfoNext; } std::vector> objs; for (size_t i = 0; i < 10; ++i) { lgrInfoNext = generateNextLedger(lgrInfoNext); if (!objs.size()) objs = generateObjects(25, lgrInfoNext.seq); else objs = updateObjects(lgrInfoNext.seq, objs); EXPECT_EQ(objs.size(), 25); EXPECT_NE(objs[0], objs[1]); std::sort(objs.begin(), objs.end()); state[lgrInfoNext.seq] = objs; writeLedger(lgrInfoNext, objs, state); lgrInfos[lgrInfoNext.seq] = lgrInfoNext; } auto flatten = [&](uint32_t max) { std::vector> flat; std::map objs; for (auto [seq, diff] : state) { for (auto [k, v] : diff) { if (seq > max) { if (objs.count(k) == 0) objs[k] = ""; } else { objs[k] = v; } } } for (auto [key, value] : objs) { flat.push_back(std::make_pair(key, value)); } return flat; }; for (auto [seq, diff] : state) { std::cout << "flatteneing" << std::endl; auto flat = flatten(seq); std::cout << "flattened" << std::endl; checkLedger(lgrInfos[seq], flat); std::cout << "checked" << std::endl; } } done = true; work.reset(); }); ioc.run(); }