From 86dd550a9b706195a9d3d6124be3f7d569ccbffe Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sat, 17 May 2025 14:31:38 -0400 Subject: [PATCH 01/11] clusterlin: add known-correct optimal linearization tests (tests) --- src/test/cluster_linearize_tests.cpp | 299 +++++++++++++++++++++++++++ src/test/util/cluster_linearize.h | 6 +- 2 files changed, 302 insertions(+), 3 deletions(-) diff --git a/src/test/cluster_linearize_tests.cpp b/src/test/cluster_linearize_tests.cpp index 3413af4a219..4cce9773278 100644 --- a/src/test/cluster_linearize_tests.cpp +++ b/src/test/cluster_linearize_tests.cpp @@ -15,6 +15,7 @@ BOOST_FIXTURE_TEST_SUITE(cluster_linearize_tests, BasicTestingSetup) using namespace cluster_linearize; +using namespace util::hex_literals; namespace { @@ -36,6 +37,7 @@ void TestDepGraphSerialization(const std::vector>& c depgraph.AddDependencies(cluster[i].second, i); } depgraph.RemoveTransactions(holes); + SanityCheck(depgraph); // There may be multiple serializations of the same graph, but DepGraphFormatter's serializer // only produces one of those. Verify that hexenc matches that canonical serialization. @@ -52,6 +54,63 @@ void TestDepGraphSerialization(const std::vector>& c BOOST_CHECK(depgraph == depgraph_read); } +void TestOptimalLinearization(const std::vector& enc, const std::vector& optimal_diagram) +{ + DepGraphIndex tx_count = 0; + FastRandomContext rng; + + auto test_fn = [&]() { + DepGraph depgraph; + SpanReader reader(enc); + reader >> Using(depgraph); + SanityCheck(depgraph); + std::vector lin; + for (int iter = 0; iter < 200; ++iter) { + bool opt; + uint64_t cost{0}; + switch (rng.randrange(3)) { + case 0: + // Use empty input linearization. + lin.clear(); + break; + case 1: + // Reuse previous optimal linearization as input. + break; + case 2: + // Construct random input linearization. + std::shuffle(lin.begin(), lin.end(), rng); + FixLinearization(depgraph, lin); + break; + } + std::tie(lin, opt, cost) = Linearize(depgraph, 1000000000000, rng.rand64(), lin); + BOOST_CHECK(opt); + BOOST_CHECK(cost <= MaxOptimalLinearizationIters(depgraph.TxCount())); + SanityCheck(depgraph, lin); + auto chunking = ChunkLinearization(depgraph, lin); + BOOST_CHECK(std::is_eq(CompareChunks(chunking, optimal_diagram))); + // Verify that the chunks are minimal. + BOOST_CHECK(chunking.size() == optimal_diagram.size()); + } + tx_count = depgraph.PositionRange(); + }; + + // Always run with 64-bit set types + // - The native one that will be used on this platform. + test_fn.template operator()>(); + // - The one used on 32-bit platforms. + test_fn.template operator()>(); + // - An 8-bit one, which is maximally different in terms of bitset behavior. + test_fn.template operator()>(); + + // Also run with 32-bit set types if the cluster doesn't use indexes above 31. + if (tx_count <= 32) { + // - The native one that will be used on this platform. + test_fn.template operator()>(); + // - An 8-bit one, which is maximally different in terms of bitset behavior. + test_fn.template operator()>(); + } +} + } // namespace BOOST_AUTO_TEST_CASE(depgraph_ser_tests) @@ -174,4 +233,244 @@ BOOST_AUTO_TEST_CASE(depgraph_ser_tests) ); } +BOOST_AUTO_TEST_CASE(depgraph_optimal_tests) +{ + // Compare linearizations with known-optimal chunk feerate diagrams. + + // Hard clusters (according to various metrics) that appeared in replayed 2023 mempool data, ranging from 2 to 64 transactions. + TestOptimalLinearization("9276fa3a009c3b80cd4001a21680d85c02927bfa5403934280845404a04c80cf0805c75b82c22206a46880f532079c7e80b85608965780916a09a04a80cf080a9823809b3e0bb27b81ec0c0c8540c60e0c0c993e80c86a0d0b8540c2020c0c9352809f780c09972f80f84a080c85449900080c875da164080c875081ae54070c8335af4c071f9c6781a72a13010f99108190160e000e85419f2c07168332903c05158540819e54041216ba5183907a140000001182428d080024a25c83c11201040400010609b6668487240004060000000000"_hex_v_u8, {{47188, 8550}, {42800, 7755}, {325314, 79021}, {2070, 833}, {900, 450}, {2226, 1117}, {1664, 836}, {1118, 562}}); + TestOptimalLinearization("850d81fd4000850e81fd400000850d81fd400000850d81fd400000850e8285000000850d8285000000850e8288600000850e8288600000850e828860000000"_hex_v_u8, {{223440, 7034}}); + TestOptimalLinearization("818f1c8a935a0083219530000083219530010083219530020083219530030083219530040083219530050083219530060083219530070083219530080000"_hex_v_u8, {{104325, 39749}}); + TestOptimalLinearization("836880bf4000834c80b6600000836880bf400100834c80b6600001836880bf400101834c80b6600002836880bf400102834c80b6600003836880bf400103834c80b6600004836880bf400104834c80b6600005836880bf400105836880bf400006834c80b6600106834c80b6600106836880bf400206834c80b6600008836880bf400108836880bf40000900"_hex_v_u8, {{12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {11760, 588}, {12320, 616}, {12320, 616}}); + TestOptimalLinearization("84a22e8fce380083208c38000083208c38010083208c38020083208c38030083208c38040083208c38050083208c38060083208c38070083208c38080083208c38090000"_hex_v_u8, {{144756, 91886}}); + TestOptimalLinearization("8361a8340084309730000000"_hex_v_u8, {{2650, 609}, {1560, 688}}); + TestOptimalLinearization("829428f0fe20008319809604000183198096040100831980960402008319809604030083198096040400831980960405008319809604060083198096040700831980960408008319809604090083198096040a0083198096040b0083198096040c0083198096040d0083198096040e0083198096040f008319809604100083198096041100831980960412008319809604130083198096041400831980960415008319809604160083198096041700831980960418008319809604190083198096041a0000"_hex_v_u8, {{933840, 51880}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}}); + TestOptimalLinearization("854ec47200854ec4720001854ec4720001854ec4720001854ec4720001854ec4720001854ec4720001854ec4720001854ec4720001854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec4720000854ec472000000"_hex_v_u8, {{4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}, {4473, 846}}); + TestOptimalLinearization("d1378f927c00e81293cc4001f36095ef5202f20195c33203e93a93817404e76793d0700b80ba72a3e82806fd4697e52207da5d91894608c73d8d98401480990c9d954a0af73292e73a0b8090259bba640c808f689bac760de50692fc1e0eac40888c1a0fbf308bcf481080a3259f980c11c30282fd78128b6f83c2300612932d8ba65e0b09896a88837c010011886281d6380e0000040f953c8594580b000000129e0491c0381300000000148235f72802228653818c08021db40c9180620229833180991c011f854081a95c0020833180bd66002082418097740020953c8784000e00000000000201030d8d7c838d0c04011a874f84842a001600"_hex_v_u8, {{248978, 14346}, {1136423, 78068}, {270282, 20244}, {1423139, 109038}, {903617, 69567}, {106746, 10092}}); + TestOptimalLinearization("836880ba5807845d80df200000836880ba580100845d80df200001836880bd0c0101845d80e2100002836880bf400102845d80e5000003836880bf400103845d80e5000004854e818800010300854e818800000300854e818800000200854e818800000100854e819178000000836880ed1c0000845f819b500017854e818b28001700"_hex_v_u8, {{252408, 12326}, {17172, 846}}); + TestOptimalLinearization("854e829b580088318881580000a37a90ae600000a37a90ae600100a37a90ae600200a37a90ae600300835a819e680303835a819e680403835a819e680503835a819e680603835a819e680703835a819e680803835a819e680903835a819e680a03835a819e680b03835a819e680c03835a819e680d03835a819e680e03835a819e680f03835a819e681003835a819e681103835a819e681203835a819e681303835a819e681403835a819e681503835a819e681603835a819e681703835a819e681803835a819e681903835a819e681a03835a819e681902835a819e681b01835a819e681c01835a819e681c02835a819e681e01835a819e681f01835a819e682001835a819e682002835a819e682102835a819e682301835a819e682302835a819e682501835a819e682502835a819e682602835a819e682801835a819e682802835a819e682a01835a819e682a02835a819e682b02835a819e682d01835a819e682d02835a819e682f01835a819e682f02835a819e683002835a819e68320100"_hex_v_u8, {{100312, 2047}, {584400, 19178}, {381780, 12556}, {363360, 11954}, {142320, 4730}}); + TestOptimalLinearization("83688095520089c17481b5cf780000842d809c700000842d809c700100842d809c700200842d809c700300842d809c700400842d809c700500842d809c700600842d809c700700842d809c700800842d809c700900842d809c700a00842d809c700b00842d809c700c00842d809c700d00842d809c700e00842d809c700f00842d809c701000842d809c701100842d809c701200842d809c701300842d809c701400842d809c701500842d809c701600842d809c701700842d809c701800842d809c701900842d809c701a00842d809c701b00842d809c701c00842d809c701d00842d809c701e00842d809c701f00842d809c702000842d809c702100842d809c702200842d809c702300842d809c702400842d809c702500842d809c702600842d809c702700842d809c702800842d809c702900842d809c702a00842d809c702b00842d809c702c00842d809c702d00842d809c702e00842d809c702f00842d809c703000842d809c703100842d809c703200842d809c70330000"_hex_v_u8, {{9641, 616}, {2544700, 172404}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}, {10104, 685}}); + TestOptimalLinearization("99469a2e008e638f32018e638f32028e638718038e638718048e638718058e648c44068e659002078e639340088f74873c098e6488380a8d6586340b894784240c894784240d8b56852c0ea71993100f8b6d8538108b56852c118e628b76128e638b76138e638b76148e628b76158e648b7616a06c8f78178e638b76188e628838198e638b761a8e6388381b894784241c9412894c1d8e6690521e9c538e1c05199b10f15017000400189527955a1a0000000000001a9e5d8e70120000000000000117b372993c000300000000000000000000000000001800"_hex_v_u8, {{11835, 11678}, {1129, 2022}, {1751, 3398}, {866, 2020}, {827, 2018}, {827, 2018}, {827, 2019}, {827, 2019}, {827, 2019}, {827, 2019}, {827, 2020}, {3025, 8912}, {604, 2018}, {604, 2019}, {604, 2020}, {974, 3795}, {678, 2706}, {542, 2164}, {412, 1645}, {474, 1893}, {1288, 5145}, {406, 1622}, {406, 1622}, {1084, 4332}, {3724, 14884}}); + TestOptimalLinearization("833583380083368338000083328334000083318334000083328334000083368338000083358338000083328334000000"_hex_v_u8, {{284, 565}, {848, 1689}, {850, 1693}, {282, 562}}); + TestOptimalLinearization("80f96f88ab2a008235839b0600008a4880b31801008604ec3c02008235a97c0300833dab7204008752d502050000"_hex_v_u8, {{140794, 37044}}); + TestOptimalLinearization("f9718def4800823ab01c01833980c238028544848f20038478e07c04887280ef2a05823680a54e068081149ea80a07808714ac995208823680a55c0980b85cbcdc020abb759acf180b8338fe680c82359eac280c0c824280a9620d0c857cf9400e0c823ae5520a0c823590c744090c823584ef14090c847c80a32a0a0c8451808b100b0c824280a9420a0c823580a52c0b0c823586bf2c0c0c857c8186200d0c847882c63a0d0c854081cf060e0c833880b66e0f0c8338808e40100b8368809552110b8241de74130a8242c34c1608894480bc48102a851e818356050e0fad56888c401d00000000198fe81c808fc7501a0000000410933d86e120031300000a00"_hex_v_u8, {{42000, 836}, {255465, 8557}, {21507, 832}, {10670, 438}, {10663, 438}, {588686, 25096}, {389673, 16616}, {418483, 17865}, {400487, 17097}, {12508, 569}, {11767, 568}, {16848, 892}, {24863, 1366}, {83604, 4861}, {9184, 568}, {9641, 616}, {10517, 764}, {6138, 449}, {21950, 1708}, {8968, 721}, {84012, 7176}, {4390, 450}, {1184296, 275612}}); + TestOptimalLinearization("82369e200084459e200186569e200282369e200384459e200488649e200586559e200684459e200788649e200886549e200984449e200a82369e200b9961dc600ca34b81da7800000000000000000000000000008368af4c0000869520c6dd6800008368af3800008368af3801008368af3802008368af3803008368af3804008368af3805008368af3806008368af3807008368af3808008368af3809008368af380a008368af380b008368af380c008368af380d008368af380e008368af380f0000"_hex_v_u8, {{692678, 145712}}); + TestOptimalLinearization("8335862e008335862e00008335862e00008335862e00008335862e00008335862e00008335862e00008335862e00008331862600008335862e00008335862e00008335862e00008335862e00008335862e00008335862e0000833d863a00008335862e00008335862e00008544896a000000"_hex_v_u8, {{471, 565}, {471, 565}, {471, 565}, {471, 565}, {471, 565}, {471, 565}, {471, 565}, {471, 565}, {3293, 3951}, {1419, 1703}, {693, 836}}); + TestOptimalLinearization("836183f20200842b84b82e00008a1089f33e0100842a849a7c00018361838f420101842b83ca680002836282f8280102842983b00c000300"_hex_v_u8, {{40129, 609}, {44631, 683}, {89375, 1424}, {42750, 682}, {33825, 609}, {37620, 683}, {32340, 610}, {35910, 681}}); + TestOptimalLinearization("80fc6c80fd300080e03c80f50e01823aaf3402823aac060380f42080f55e04833d84200580ea0580ea6006833e855a0780ce7c81b724088235898b1c08088474bd2c0908875180a10e0a08857cb0000b088239ad260c088336b8180d0889448e660e08823588db0e0e088242ac5a0c08823588df180d08854588660e088478d670120584498a440f08833dbb1e0f088474ea3810088470d83a1108823588ac7812088242844a13088546886614088545d67e1705823a84181b02875c8c0a1707854088601a058241ae3c1b05823587a67e180887548b7e19088241ae3c21018600d6401b08823a84141c088336854825008755f34a1e088335b6001c2d81d25ca2f16e27001e902d80ea6a250318894ce9202206068336d140034c9969829e282800001d82f57fb48c2e260000040e00"_hex_v_u8, {{3098, 442}, {2883, 442}, {1070885, 209051}, {148942, 39219}, {107942, 30418}, {3136, 892}, {738, 713}, {357, 450}, {332, 442}, {624, 832}, {831, 1108}, {837, 1116}, {1011, 1348}, {627, 837}, {627, 838}, {330, 442}, {420, 566}}); + TestOptimalLinearization("80e67480e6740080d53480d56e0180d04881bd62028f108f1c038335cb7204847ce13e0404824184240504823594990c0604823580d07c07048235a6160804857cdd4009048474f2640904823592ce5c0a04833692700b048540ad300d038608b37c0c04823583e0720d048500a9000e048239841c0f048242b70011038540cc681203833e855a13038470bf7c13048545956a16008600be000d1a8f1880990a17000d8a448a4c1600048544f63c1603018f18f1180c0d0b8600ac40041f00"_hex_v_u8, {{4921, 565}, {221979, 33050}, {185495, 29087}, {4980, 832}, {2507, 437}, {9861, 2072}, {4032, 896}, {2968, 832}, {2912, 896}, {76978, 31757}, {1272, 566}, {1461, 837}, {334, 441}, {338, 449}, {429, 574}, {1038, 2064}, {742, 1476}}); + TestOptimalLinearization("84418c6a0080fe1d86fc00000080b97084ea50000080de1c85fc00010080ed4986b930020080a17b848a70030080d07b85c670040080a86b84a630050080e405869320060080db1185ef50070080d11b85c770080080d00185c310090000"_hex_v_u8, {{593589, 297030}}); + TestOptimalLinearization("856ea51800856da5180000856eab080002856eab080002856dab080002856dab08000000"_hex_v_u8, {{16168, 5265}}); + TestOptimalLinearization("83318c2a00833d8c400183318c1c0283318c1c020283318c1c020283358c28020283318c1e020683318c1c020583318c1c0204833d8c40020a833d8c40020883318c1c020683318c1c020e83318c1c020b8331943e0208833d8c40021283318c1e020e83319a0c020a83358d58011183318c1e031083318d08011383318c1c0113833d8c40011583319a0c011483318c1c01169260809308021583358c280118a84380f9100a021383318c1c011983319430001983319938001983319a1c001883319a0c00188540a71609030683319a0c011983319a0c011283319a0c011283319a0c0113833d9a56011483319b52011483359a26011583359b6c011583319a0c011783319b52011683319a0c011983319b52011783319b52011b83319b520118833d9d6a001983309d18001900"_hex_v_u8, {{88836, 35145}}); + TestOptimalLinearization("80886c99f50c00835df5600000835df5600100835df5600200835df5600300835df5600400835df5600500835df5600600835df5600700835df5600800835df5600900835df5600a00835df5600b00835df5600c00835df5600d00835df5600e00835df5600f00835df5601000835df5601100835df5601200835df5601300835df5601400835df5601500835df5601600835df5601700836880cd78180080895badcb740018835b80c9740018835b80c9740118835b80c9740218835b80c9740318835b80c9740418835b80c9740518835b80c9740618835b80c9740718835b80c9740818835b80c9740918835b80c9740a18835b80c9740b18835b80c9740c18835b80c9740d18835b80c9740e18835b80c9740f18835b80c9741018835b80c974111800"_hex_v_u8, {{849296, 46869}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}, {7600, 605}}); + TestOptimalLinearization("8514d518008344be3800008514d51801008344be3800018514d51801018514d51800028344be3801028344be3801028514d51802028344be3800048514d51801048344be3800058514d51801058514d51800068344be3801068344be3801068514d51802068344be3800088514d51801088344be3800098514d51801098514d518000a00"_hex_v_u8, {{5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {4060, 580}, {5516, 788}, {5516, 788}}); + TestOptimalLinearization("8442922e009069bd3a0000872d9c1a010083618f5e010183618f5e020183618f5e030183618f5e040183618f5e050183618f5e060183618f5e070183618f5e080183618f5e09019743a36609018442922e0a01835f8610010b835f8610020b835f8610030b835f8610040b835f8610050b835f8610060b835f8610070b835f8610080b835f8610090b835f86100a0b835f86100b0b835f86100c0b8442d82a0c0b8441e332001700"_hex_v_u8, {{16489, 3892}, {13636, 7762}, {7827, 10423}}); + TestOptimalLinearization("9420928e2c008368f11a0000c52488dd700000834acf100000834acf100100834acf100200834acf100300834acf100400834acf100500834acf100600834acf100700834acf100800834acf100900834acf100a00834acf100b00834acf100c00834acf100d00834acf100e00834acf100f00834acf101000834acf101100834acf101200834acf101300834acf10140000"_hex_v_u8, {{156630, 2720}, {7309, 616}, {79800, 8996}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}, {5128, 586}}); + TestOptimalLinearization("9d2c83920200856cf66200008570f7280100856bf65202008567f60c03008563f54604008572f74c0500856ef7060600856af6400700856af64008008561f52209008566f57a0a008566f57a0b008575f8000c008571f73a0d008571f73a0e00856df6740f00856df67410008565f56811008561f522120000"_hex_v_u8, {{102774, 11745}, {7569, 865}, {7569, 865}, {7604, 869}, {7674, 877}, {7674, 877}, {7709, 881}, {7709, 881}, {7744, 885}, {7665, 876}, {7700, 880}}); + TestOptimalLinearization("854e80806600854e80806600008368e25000008462f54200008368e50601008463f8340001854e80b25c010000854e80b606000183688089400001846280a3620001854e80c00401000083688095520001846280b21c000100"_hex_v_u8, {{120967, 9647}}); + TestOptimalLinearization("836186101483618872001583608842001584508d74001583618812011500"_hex_v_u8, {{2652, 2546}, {585, 609}}); + TestOptimalLinearization("8ee34aafe24c0083208c1a000083208c1a010083208c1a020083208c1a030083208c1a040083208c1a050083208c1a060083208c1a070083208c1a080083208c1a090083208c1a0a0083208c1a0b0083208c1a0c0083208c1a0d0083208c1a0e0083208c1a0f0083208c1a100083208c1a110083208c1a120083208c1a130083208c1a140083208c1a150083208c1a160083208c1a170083208c1a180083208c1a190083208c1a1a0083208c1a1b0083208c1a1c0083208c1a1d0083208c1a1e0083208c1a1f0083208c1a200083208c1a210083208c1a220083208c1a230083208c1a240083208c1a250083208c1a260083208c1a270083208c1a280083208c1a290083208c1a2a0083208c1a2b0083208c1a2c0083208c1a2d0083208c1a2e0083208c1a2f0083208c1a300083208c1a310083208c1a320083208c1a330083208c1a340083208c1a350083208c1a360083208c1a370083208c1a380083208c1a390083208c1a3a0083208c1a3b0000"_hex_v_u8, {{450290, 291274}}); + TestOptimalLinearization("a34c88ff6400835c80a4140000835c80a4140100835c80a4140200835c80a4140300835c80a4140400835c80a4140500835c80a4140600835c80a4140700835c80a4140800835c80a4140900835c80a4140a00835c80a4140b00835c80a4140c00835c80a4140d00835c80a4140e00835c80a4140f00835c80a414100000"_hex_v_u8, {{81970, 4684}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}, {10570, 604}}); + TestOptimalLinearization("83369734008331903c01a41680dd2e02b24989886c0380cb0a86fe4404a97680ff680582368c60068451a53407df7783c500088806b80c09a74a80f1660a841e9644080a823581c71e070a854cb37c09098236ee2e080a8452a534070a8753b2580b068235a104080a847ced5e090a831695740e06895080b7340b0a8312955e0c0a8478de000d088755d57e0e088908c0140f088235910210088500a6001603831199701208833da73013088545a6301b001b85419f541700178451a5340f1e8331b40e1218847cdc000e1b880682bf201800108a4880b84018010f85708088401611824183a3560b188541a52407358335b37c06278752b7220a1900228544a954002221896ac558001805001700"_hex_v_u8, {{82550, 6601}, {136779, 19257}, {12190, 1724}, {3399, 561}, {3390, 565}, {5567, 1109}, {2178, 437}, {2584, 573}, {27411, 6674}, {23563, 5932}, {2458, 721}, {4916, 1443}, {86513, 26559}, {1720, 529}, {2496, 768}, {4170, 1288}, {30286, 10050}, {3308, 1107}, {1466, 534}, {1455, 530}, {5420, 2104}, {1506, 670}}); + TestOptimalLinearization("83689e4c0085599314000083689e4c0100864cc014010185599314010183689e4c0201864cc014010300"_hex_v_u8, {{2022, 616}, {2022, 616}, {2022, 616}, {5460, 1829}, {5460, 1829}}); + TestOptimalLinearization("81c47cafb03400835bd8540000835bd8540100835bd8540200835bd8540300835bd8540400835bd8540500835bd8540600835bd8540700835bd8540800835bd8540900835bd8540a00835bd8540b00835bd8540c00835bd8540d00835bd8540e00835bd8540f00835bd8541000835bd8541100835bd8541200835bd8541300835bd8541400835bd8541500835bd8541600835bd8541700835bd8541800835bd8541900835bd8541a00835bd8541b00835bd8541c00835bd8541d00835bd8541e00835bd8541f00835bd8542000835bd8542100835bd8542200835bd8542300835bd8542400835bd8542500835bd8542600835bd8542700835bd8542800835bd8542900835bd8542a0000"_hex_v_u8, {{643112, 67653}}); + TestOptimalLinearization("83359d2c0083359d2c000083359d2c000283359d2c000283359d2c000200"_hex_v_u8, {{1942, 565}, {1942, 565}, {1942, 565}, {1942, 565}, {1942, 565}}); + TestOptimalLinearization("a335b4af0400832f85b3280000832f85b3280100832f85b3280200832f85b3280300832f85b3280400832f85b3280500832f85b3280600832f85b3280700832f85b3280800832f85b3280900832f85b3280a00832f85b3280b00832f85b3280c00832f85b3280d00832f85b3280e00832f85b3280f00832f85b3281000832f85b328110000"_hex_v_u8, {{1382250, 14723}}); + TestOptimalLinearization("833284fd3a00833581fc3801823684d50e02833181fb500383358695480480d77881b6f12005857c89bf7c0681a21d82999c300780d81d81b3ec6808844581a474098332899a7e0909833185bb04090984398190380809823587ae06070982358ddc1e080982368688220909823585cb000a09823586b3120b09823687aa6e0a0982359bf34a0b09823592ed040b09860880af480f05833286e0460b1d833185ba620b1c833288e64a0b1a833188a270022a833186bb5e0228833e89976a0225833186eb0a012b8332898512012783358a9130012d823986ff5a012883358a8f00012fed4996937e04020b000000000000000100"_hex_v_u8, {{272375, 2247}, {438299, 3939}, {46471, 438}, {353620, 3397}, {58788, 565}, {2916860, 29954}, {86078, 892}, {3665780, 38152}, {2692854, 28242}, {18810, 709}, {189759, 14153}, {11300, 904}}); + TestOptimalLinearization("883180ae7800a37a81cc480001a37a81cc480100a37a81cc480204835aaa160101835aaa160102835aaa160301835aaa160401835aaa160501835aaa160601835aaa160602835aaa160900835aaa160901835aaa160a01835aaa160b01835aaa160c01835aaa160c02835aaa160e01835aaa160f01835aaa160f02835aaa161101835aaa161201835aaa161301835aaa161401835aaa161600835aaa161700835aaa161800835aaa161702835aaa161802835aaa161902835aaa161c00835aaa161b02835aaa161e0000"_hex_v_u8, {{11260, 1201}, {62793, 13760}, {43452, 9546}, {37926, 8342}}); + TestOptimalLinearization("a25c81b56200832fa5000000832fa5000100832fa5000200832fa5000300832fa5000400832fa5000500832fa5000600832fa5000700832fa5000800832fa5000900832fa5000a00832fa5000b00832fa5000c00832fa5000d00832fa5000e00832fa5000f00832fa5001000832fa5001100832fa5001200832fa5001300832fa500140000"_hex_v_u8, {{70961, 16311}}); + TestOptimalLinearization("8368d63c00b273aaf80001842dec5e0101854e829f0201018f6681fb320103854e828b060300019e228ea20a0202835aea780206835aea780306835aea780406835aea780506835aea780606835aea780706835aea780806835aea780906835aea780a06842dec720a05836281a96a0a04836281a96a0b04836281a96a0c04836281a96a0d04836281a96a0e04836281a96a0f04836281a96a1004836281a96a1104836281a96a1204836281a96a1304836281a96a1404836281a96a1504836281a96a1604836281a96a1704836281a96a1804836281a96a1904836281a96a1a04836281a96a1b04836281a96a1c049d1e84a95e132200"_hex_v_u8, {{360000, 6643}, {26625, 846}, {488500, 15592}, {30945, 1462}, {93540, 8253}, {50728, 4555}}); + TestOptimalLinearization("80de6ebba3280081a442d1976601990e86a22a02996686ba6a0381cb6bdcce060480e358beea3205823590802e0506847480cb6c06058241e47a0705845280a22e0805824180ab2c09058241e5120a05854580fd100b05833580b34e0b058235919c180c05833280b8080d05823593c3700b0586048189660c05906383fd600d0582358db5100d0582358087700e05823a8088400f058242e47a10058241eb4016008235ec001304874f81ec4a1701860881896608198a77829b761700000e900f83e20e160002000300"_hex_v_u8, {{644225, 29540}, {935163, 43040}, {822655, 38007}, {641121, 29709}, {23397, 1103}, {11844, 562}, {11559, 565}, {8760, 437}, {8800, 442}, {16264, 837}, {17075, 900}, {17075, 904}, {40880, 2275}, {39111, 2191}, {59605, 3342}, {61173, 3430}, {13110, 756}, {26427, 1527}, {6976, 437}, {6944, 449}, {6537, 449}, {6525, 449}, {6525, 450}, {10455, 722}}); + TestOptimalLinearization("836280c06000836180c06001836280c06002834280b1600202836180c0600302834280b1600302836180c0600402834280b1600402836280c0600502877e81fc200509833a80b1600509836280c0600609877f81fc200608834180b1600608877e81fc200607836280c0600607834180b1600707836180c0600715834280b1600614836280c0600611836180c060050f834380b160050d834180b1600420834380b1600318834280b1600315836180c0600510836280c0600511877f81fc200428880281f96011030e836280c0600315836280c060022e834280b1600030836180c0600130880281fc20080710834280b1600132836280c060022e877f81fc200c0915833b80b1600131877e81fc2002022e877f81fc200a0610877e81fc20010230877f81fc2004110c877e81fc2000130d877f81fc2002110d877e81f96006021500"_hex_v_u8, {{96480, 4675}, {48240, 2338}, {48240, 2338}, {482080, 23373}, {12400, 609}, {12400, 610}, {12400, 610}}); + TestOptimalLinearization("818e6ca18c5000834fc8700001834fc8700100834fc8700200834fc8700300834fc8700400834fc8700500834fc8700600834fc8700700834fc8700800834fc8700900834fc8700a00834fc8700b00834fc8700c00834fc8700d00834fc8700e00834fc8700f00834fc8701000834fc8701100834fc8701200834fc8701300834fc8701400834fc8701500834fc8701600834fc8701700834fc8701800834fc8701900834fc8701a00834fc8701b00834fc8701c00834fc8701d00834fc8701e00834fc8701f00834fc8702000834fc8702100834fc8702200834fc8702300834fc8702400834fc8702500834fc8702600834fc8702700834fc870280000"_hex_v_u8, {{279400, 34796}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}}); + TestOptimalLinearization("8442872600c61981fd680000835a8c420000835a8c420100835a8c420200835a8c420300835a8c420400835a8c420500835a8c420600835a8c420700835a8c420800835a8c420900835a8c420a00835a8c420b00835a8c420c00835a8c420d00835a8c420e00835a8c420f00835a8c421000835a8c421100835a8c421200835a8c421300835a8c421400835a8c421500835a8c421600835a8c421700835a8c42180000"_hex_v_u8, {{25031, 9819}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}, {865, 602}}); + TestOptimalLinearization("8540e74000833180831801875dd14e00000000"_hex_v_u8, {{8460, 561}, {6688, 832}, {5287, 1117}}); + TestOptimalLinearization("808a5292d06800835ad3780000835ad3780100835ad3780200835ad3780300835ad3780400835ad3780500835ad3780600835ad3780700835ad3780800835ad3780900835ad3780a00835ad3780b00835ad3780c00835ad3780d00835ad3780e00835ad3780f00835ad3781000835ad3781100835ad3781200835ad3781300835ad3781400835ad3781500835ad3781600835ad3781700835ad3781800835ad3781900835ad3781a00835ad3781b00835ad3781c0000"_hex_v_u8, {{318528, 35332}}); + TestOptimalLinearization("8a0d8a54008368843000008a0d8a5401008368843000018a0d8a54010000836884300001996cc3320100008a449d1e00018d20a37e01000000"_hex_v_u8, {{11949, 12847}}); + TestOptimalLinearization("80ba1386a6640080c14d86cc1001809b10858b500280927684e2580380e9458893680480b6538695240580bb2086ac2006942981e1240000000000000000900e819b3a0100000000000000e2528bf6420200000000000000854dca54020200"_hex_v_u8, {{565214, 183924}}); + TestOptimalLinearization("80cc3fb8e500008327809a2000018327809a2001008327809a2002008327809a2003008327809a2004008327809a2005008327809a2006008327809a2007008327809a2008008327809a2009008327809a200a008327809a200b008327809a200c008327809a200d008327809a200e008327809a200f008327809a2010008327809a2011008327809a2012008327809a2013008327809a2014008327809a2015008327809a2016008327809a2017008327809a2018008327809a2019008327809a201a008327809a201b008327809a201c008327809a201d008327809a201e008327809a201f008327809a2020008327809a2021008327809a2022008327809a2023008327809a2024008327809a2025008327809a2026008327809a2027008327809a2028008327809a2029008327809a202a008327809a202b008327809a202c0000"_hex_v_u8, {{920592, 51098}}); + TestOptimalLinearization("83ea5fb6be1a0083c44cb2c86c0182ff47abfc140285940cc6dd7c038c3281d1740303841f80a25c0402870880f9740403833d809c400503896a81fb500603897680940608028a4c80b9560603836580941807038365819f0e0803833180874c0903851e80c0600d008331ad6e0c02833180874c0c0383358088480d03833180874c0e0383388089441101833880894412018338808944130183388089441500836180931c1501907d83ae781600158a4d81e82c150010912a84831c16000f8335808848091583388089440616851e80c0600815907583ad001a00000f91238aba401c000007833580884804208338808944041ca84c898e1c1e000000118235809e56042c83388089440220833180874c041e823580ee4c0331833180874c012200"_hex_v_u8, {{2460632, 356111}, {9539, 1398}, {2999, 561}}); + TestOptimalLinearization("8368aa5e008368aa5e018368aa5e028368aa5e02028368aa5e02028368aa5e0202836897260500836897260501a35780b6380106a35780b6380106836797080108836797080208836797080308836797080408836797080508836797080608836797080708836797080808836797080908836797080a08836797080b08836797080c08836797080d08836797080e08836797080f08836797081008836797081108836797081208836797081308836797081408836797081407836797081507836797081607836797081707836797081807836797081907836797081a07836797081b07836797081c07836797081d07836797081e07836797081f078367970820078367970821078367970822078367970823078367970824078367970825078734cb0a2a00002a00"_hex_v_u8, {{2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {4869, 1076}, {1555, 616}, {1555, 616}, {42540, 16995}, {39460, 15765}}); + TestOptimalLinearization("854887862000854887862000008548878a0000008548878a0000008548878d6000008548879520000000"_hex_v_u8, {{397920, 5040}}); + TestOptimalLinearization("8331f924008336fa1400008331f70e00008335f56200008331ee3000008336ef1600008335e17c00008335df62000000"_hex_v_u8, {{7826, 561}, {7882, 566}, {7687, 561}, {7601, 565}, {7128, 561}, {7179, 566}, {6334, 565}, {6193, 565}}); + TestOptimalLinearization("808962829f2c0083628d2c000183628d2c010183628d2c020083628d2c030083628d2c040083628d2c050083628d2c060083628d2c070083628d2c080083628d2c090083628d2c0a0083628d2c0b0083628d2c0c0083628d2c0d0083628d2c0e0083628d2c0f0083628d2c100083628d2c110083628d2c120083628d2c130083628d2c140083628d2c150083628d2c160083628d2c170083628d2c180083628d2c190083628d2c1a0083628d2c1b0083628d2c1c0083628d2c1d0083628d2c1e0083628d2c1f0083628d2c200083628d2c210083628d2c220083628d2c230083628d2c240083628d2c250083628d2c260000"_hex_v_u8, {{62448, 41552}}); + TestOptimalLinearization("b27883865800833e81de3601833ea76a029d1d8e1003af4f82e54804c06484911005ad2d82ce6006ae2c82d86007b10082f32808af3882e35809833eba080aa13681d7300bbc1283e2480ca41d81f3400d847088140d0d824180f0280e0d8331a67e0d0d865adf4a0d0d8241ab120c0d86088081540d0d833eaa600e0d854e88720f0d823580c4580e0d8541b9260f0d833ead6c100d8545c014110d8542885e120d823680cd00120d8446bc7c130d83319d42140d8544c658140d8546c658130d8474f034130c854dca56200083318199601b06833281a4021e04865982aa6c1e05854e81e8681c088239c9121d08847c81b4181d09844582987a1b0c854dca5622068608f1242504833e8a142505857ceb401f0c847caa4c23099254828a502c002a8335a7221e378574b95a1c338332ce7419328335ad1819328331d2241731854680f04628001b8235b706122c9260828a503200012c833db00a06548235d332044a833ea76a0931895080a07e023e80dd6690b7682d0000010000000000031100"_hex_v_u8, {{22491, 574}, {83836, 9123}, {72635, 9291}, {6944, 892}, {74024, 9613}, {77864, 10309}, {57207, 7689}, {51363, 6931}, {50808, 6964}, {41279, 5818}, {42972, 6497}, {8845, 1395}, {2825, 449}, {17265, 2817}, {4843, 845}, {4843, 845}, {3966, 710}, {4588, 836}, {4588, 838}, {5954, 1139}, {166740, 33283}, {4170, 837}, {2800, 574}, {7749, 1700}, {2613, 574}, {32469, 7383}, {2790, 764}, {1953, 561}, {714, 574}, {586, 752}, {633, 846}, {623, 834}}); + TestOptimalLinearization("a25c82906c00835fb17e0000835fb17e0100835fb17e0200835fb17e0300835fb17e0400835fb17e0500835fb17e0600835fb17e0700835fb17e0800835fb17e0900835fb17e0a00835fb17e0b00835fb17e0c00835fb17e0d00835fb17e0e00835fb17e0f00835fb17e1000835fb17e1100835fb17e1200835fb17e1300835fb17e140000"_hex_v_u8, {{25718, 4572}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}, {3263, 607}}); + TestOptimalLinearization("834180872600834a808726018361808b3c028362808b3c03877f81914c0303877e81914c0303834280804c0303834280804c030383618086500208836280841a04048342fc1001098342f97201088361808650020883628081640406877f819142030d8341fc10020b8362808906030b8342f754030a83618086500313877e8196160705088342fe2e030e83628086500218877e819142060906836280865003138346fc10050f8361809028031d83618086500317836180865003178341fc100612834bfc10051383418085080424836180925e05248362808650051d8361809028051c8342fc1006188343fc1008178342808726042d836180902804258347fc10051b8342808508012a877f81881008121c836280902800368342808508003600"_hex_v_u8, {{26297, 1728}, {26297, 1736}, {139807, 9456}, {8990, 610}, {68514, 4674}, {35240, 2406}, {26660, 1828}, {8835, 610}, {8294, 578}, {8151, 578}, {8008, 577}, {8008, 578}, {8008, 579}, {8008, 582}, {8008, 583}, {8370, 610}, {8008, 587}, {7722, 578}}); + TestOptimalLinearization("a44d9db27a00824081e92c0000824081e92c0100824081e92c0200824081e92c0300824081e92c0400824081e92c0500824081e92c0600824081e92c070000"_hex_v_u8, {{434605, 8397}}); + TestOptimalLinearization("880180cf2200829428c7e9180000831ce0040000831ce0040100831ce0040200831ce0040300831ce004040000"_hex_v_u8, {{13329, 1153}, {596620, 51880}, {6210, 540}, {6210, 540}, {6210, 540}, {6210, 540}, {6210, 540}}); + TestOptimalLinearization("f90bc0b7520080840cc7e7780194268aae4602823581931c0202823681931c0302857882dc2e0402857c82de400502823581911c0602860482e26407028236819f1e0802823984a6160902823681a11e0a02860082e55a0b02847082c60a0c02847482c60a0d02847482c60a0e02826684a6160f02847082b8721002824180f0721102852282a8341202850082af001302823681bf201402823581bf201402823681bf201502823681b9701602847c83df681702823581b1201802857882e55a1902860882b0401a02823681c9221b02852982b0241c02847c82e6781d02850082b32a1f01823983b1521e028235819b1e20018235a5441e208242848b1a1c1f8242889a3819238a4485a45424000a823480e96408218a4c85a4542500028d07e0bb1a1100000500001900"_hex_v_u8, {{2320269, 40741}, {38964, 764}, {21137, 438}, {20152, 438}, {19600, 437}, {18447, 438}, {18191, 437}, {31228, 764}, {129164, 3167}, {17678, 437}, {17678, 438}, {28281, 752}, {27925, 768}, {27648, 768}, {31149, 888}, {51562, 1476}, {31149, 896}, {51562, 1484}, {15481, 449}, {30551, 888}, {30688, 892}, {30962, 900}, {42756, 1245}, {27226, 802}, {27744, 904}, {2466, 437}}); + TestOptimalLinearization("8361a17a008430a138000000"_hex_v_u8, {{2237, 609}, {2204, 688}}); + TestOptimalLinearization("fc7d88962000f93b87f516018a50809c6401018235a10402018235f65203018236cd1004018a4c80bb6405018235956e06018604de2c07018332fc480801847cdc0009018336fc700a018a50809e340b0182418c540c01831dcf020d01893c809f400d018470df300e018474dc640f01857cdd4010018802c83811018608dc00120183159a0a14008578dc0015008600dc001501823acf7c0e15857cf12412128336b6760a14926881943019000400"_hex_v_u8, {{171970, 27015}, {134824, 24317}, {2178, 437}, {4700, 1154}, {1463, 437}, {1733, 533}, {874, 449}}); + TestOptimalLinearization("829428f0fe20008319809604000183198096040100831980960402008319809604030083198096040400831980960405008319809604060083198096040700831980960408008319809604090083198096040a0083198096040b0083198096040c0083198096040d0083198096040e0083198096040f008319809604100083198096041100831980960412008319809604130083198096041400831980960415008319809604160083198096041700831980960418008319809604190083198096041a0083198096041b0083198096041c0083198096041d0083198096041e0083198096041f008319809604200083198096042100831980960422008319809604230083198096042400831980960425008319809604260083198096042700831980960428008319809604290083198096042a0083198096042b0083198096042c0083198096042d0083198096042e0083198096042f0083198096043000831980960431008319809604320083198096043300831980960434008319809604350000"_hex_v_u8, {{933840, 51880}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}, {9666, 537}}); + TestOptimalLinearization("870180db6e00841580832400008416808d5400008416eb7800008416808d54000000"_hex_v_u8, {{14135, 1025}, {17596, 1323}, {16102, 1324}}); + TestOptimalLinearization("818f1c85840c0083208b08000083208b08010083208b08020083208b08030083208b08040083208b08050083208b08060083208b08070083208b08080083208b08090083208b080a0083208b080b0083208b080c0083208b080d0083208b080e0083208b080f0083208b08100083208b08110083208b08120083208b08130083208b08140083208b08150083208b08160000"_hex_v_u8, {{49478, 34844}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}, {772, 544}}); + TestOptimalLinearization("81b8668092af300080da66e0f02801a36e8dc30a0280e87af0a9080380de2bdfea4a0480d709ef9f1405860081bd40068332ee300780cb48f1a11c08844682a01808088235d7dc300908826580f94c0a08824180e7600b088316818b4c0c088235c39c320c08833e81a31a0d08833280dd240e08854d81ce401007824280e4421008854b8df30e100893588984141108824180ad041208847483e97c12088235bdb51c1308824280b2261408824180b27e1508824180f66e19058448829a1a1a05823580fc1c1a068235c6f26c1808823581b2221908823981fa1a1a08860982d7681b088336819f0e1c08875d82ba3e1d088235b28b2a1d08823a8188381e08824180be341f08823680ba662008857881bc0a20088235a6d9061f08823a81a1182008824280e45c2108833680de6e2208823a80b2022308833281e41a2c00845182806e2c0024833581ac261d32831d81d04e2224854582ea7a2b010e823980c408062c896483ae002a0404866283d5781717158e10878014210f011fab528cf5380e1f050900"_hex_v_u8, {{1406632, 29465}, {1934192, 40603}, {1338143, 28094}, {1360813, 28571}, {1479376, 31139}, {1261329, 26621}, {19665, 437}, {241164, 5561}, {18572, 442}, {22861, 562}, {16988, 442}, {54538, 1431}, {31485, 837}, {26700, 710}, {26317, 712}, {38797, 1075}, {15863, 449}, {24695, 721}, {65610, 1936}, {30260, 905}, {14896, 449}, {16038, 485}, {14702, 450}, {14689, 450}, {18701, 574}, {18439, 566}, {82250, 2648}, {40755, 1410}, {12019, 438}, {12250, 449}, {26939, 1007}, {35776, 1380}, {11457, 442}, {11519, 449}, {11475, 450}, {28383, 1117}, {14226, 562}, {11138, 449}, {40677, 1784}, {121204, 6276}}); + TestOptimalLinearization("80ba43afe456008b358582100180c16cb587240280d261c0c11a0380d924bbc75c04833180ad5805900e84e9260682359b9b5c060782398087040706823598f2560606823599eb60050684788199680606823594a33c0805823580a95e0905854081f70c0d02824183c8280e028eb06183c190480b00058a7183aa0c0a000400ad428fc3340d000000000800"_hex_v_u8, {{49352, 1589}, {692467, 25721}, {711083, 27542}, {655101, 25377}, {716126, 28377}, {10927, 437}, {18100, 760}, {135450, 5954}, {94469, 4272}, {8706, 441}, {4736100, 252129}}); + TestOptimalLinearization("a25c86f87e008362808722000083628087220100836280872202008362808722030083628087220400836280872205008362808722060083628087220700836280872208008362808722090083628087220a0083628087220b0083628087220c0083628087220d0083628087220e0083628087220f008362808722100083628087221100836280872212008362808722130000"_hex_v_u8, {{239571, 16772}}); + TestOptimalLinearization("864a828f7a008b6384a51401854181d76c02833180e754038431819c3a04892a87a42205894587ba5c06853981d44607861c81ca2c0107857881bb3a010785008193000306823582ee280505854e81ec5a0a000a853581e22c0014896183d36c0007000007833e80f734000e860481c37000098642829e5c0106078a588398500108854181e744010f9659899c24000707843a81ac70000e857881b50e010c860481fb26020788358393240210833180f7420012843e81b51a0011874082e014000807853581ea0a000f853681ea0a000f833e80fc66000e833580f924000e833580fe4c000d00"_hex_v_u8, {{99621, 1759}, {69358, 1349}, {594899, 21560}, {24339, 900}, {20792, 900}, {17664, 768}, {34408, 1496}, {21206, 924}, {20253, 888}, {19847, 888}}); + TestOptimalLinearization("818e6ca18c5000834fc8700001834fc8700100834fc8700200834fc8700300834fc8700400834fc8700500834fc8700600834fc8700700834fc8700800834fc8700900834fc8700a00834fc8700b00834fc8700c00834fc8700d00834fc8700e00834fc8700f00834fc8701000834fc8701100834fc8701200834fc8701300834fc8701400834fc8701500834fc8701600834fc8701700834fc8701800834fc8701900834fc8701a00834fc8701b00834fc8701c00834fc8701d00834fc8701e00834fc8701f00834fc8702000834fc8702100834fc8702200834fc8702300834fc8702400834fc8702500834fc8702600834fc870270000"_hex_v_u8, {{279400, 34796}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}, {4728, 591}}); + TestOptimalLinearization("c7128ac61a00837ae46e0000837ae46e0100837ae46e0200837ae46e0300837ae46e0400837ae46e0500837ae46e0600837ae46e0700837ae46e0800837ae46e0900837ae46e0a00837ae46e0b00837ae46e0c00837ae46e0d00837ae46e0e00837ae46e0f00837ae46e1000837ae46e1100837ae46e1200837ae46e1300837ae46e1400837ae46e1500837ae46e1600837ae46e1700837ae46e1800837ae46e1900837ae46e1a00837ae46e1b00837ae46e1c00837ae46e1d00837ae46e1e0000"_hex_v_u8, {{296758, 28888}}); + TestOptimalLinearization("8440881a0084408c5e000000"_hex_v_u8, {{1468, 1408}}); + TestOptimalLinearization("a22d80e478008e4be10000008e4be42e00029031f0120002903284fe200000d25c9b8d380000835b80c71c0002835b80c71c0102835b80c71c0202835b80c71c0300835b80c71c0400835b80c71c0500835b80c71c0600835b80c71c0700835b80c71c0800835b80c71c0900835b80c71c0a00835b80c71c0b00835b80c71c0c00835b80c71c0d00835b80c71c0e00835b80c71c0f00835b80c71c1000835b80c71c1100835b80c71c1200835b80c71c1300835b80c71c1400835b80c71c1500835b80c71c1600835b80c71c1700835b80c71c1800835b80c71c1900835b80c71c1a00835b80c71c1b00835b80c71c1c00835b80c71c1d00835b80c71c1e00835b80c71c1f00835b80c71c2000835b80c71c2100835b80c71c2200835b80c71c2300835b80c71c2400835b80c71c2500835b80c71c2600835b80c71c2700835b80c71c2800835b80c71c2900835b80c71c2a00835b80c71c2b00835b80c71c2c00835b80c71c2d00835b80c71c2e00835b80c71c2f00835b80c71c3000835b80c71c3100835b80c71c3200835b80c71c3300835b80c71c3400835b80c71c350000"_hex_v_u8, {{1006076, 56244}}); + TestOptimalLinearization("8472e52e008473e52e018473e52e028473e52e038472e52e048472f85c058472e77405058472e52e05058473e52e05058473e52e05058473e52e05058472fe0005058472ef1e050f8473809e14050e8473e874050d8239ad42060d8473809e14060c8472e874060b8472fb58060a847280b024061a847280837406188473e674061684738083740514847280b91405128473fb580424847380cd24031e8472fb58031a8472f85c03178473fb58032b8473f85c03238472f85c031e847380ab14031a8472f85c03318472f85c03288473fe0003228473fb58031d8472f85c03378473f85c011d8473f85c031c8472808028023c8472f85c021e8473f85c021f8473f85c01208473f85c00218e0b81d3280a03010514901a8089000c000502020800"_hex_v_u8, {{50602, 4526}, {196414, 18533}, {7980, 755}, {23898, 2262}, {15918, 1509}, {23856, 2262}, {15580, 1509}, {7790, 755}, {7790, 755}, {7790, 755}, {7790, 755}, {2977, 441}, {8832, 2202}}); + TestOptimalLinearization("8361c74a0084a108cec45c0000831cbe5e0000831cbe5e0100831cbe5e0200831cbe5e0300831cbe5e0400831cbe5e0500831cbe5e0600831cbe5e0700831cbe5e0800831cbe5e0900831cbe5e0a00831cbe5e0b00831cbe5e0c00831cbe5e0d00831cbe5e0e00831cbe5e0f00831cbe5e1000831cbe5e1100831cbe5e1200831cbe5e1300831cbe5e1400831cbe5e1500831cbe5e1600831cbe5e1700831cbe5e1800831cbe5e1900831cbe5e1a00831cbe5e1b00831cbe5e1c00831cbe5e1d00831cbe5e1e00831cbe5e1f00831cbe5e2000831cbe5e2100831cbe5e2200831cbe5e2300831cbe5e2400831cbe5e2500831cbe5e2600831cbe5e270000"_hex_v_u8, {{4645, 609}, {814790, 107880}}); + TestOptimalLinearization("8431e564008369a158018369a15c028369a162038550ae20048369a17a058501a94a068501a946078430e72e088369a178098500a93a0a842fe5640b8502a9360c8501a8740d83699f5a0e8431e5640f8431e564108369a144118550ae2211118737ba280a118369a178081183699f5a051183699e4605118374b1601204891ec536060b0682199b70021582199b70041482199b7006138540ab7c031a853fab76001db6219ddc1007000000000100000000000000000000001e00"_hex_v_u8, {{327699, 22688}, {3248, 628}, {1848, 409}, {1848, 409}, {1848, 409}, {2210, 617}, {10260, 2973}}); + TestOptimalLinearization("836881f0280088318cba380000a37a9986500000a37a9986500100a37a9986500200a37a9986500300835a82ae5c0303835a82ae5c0403835a82ae5c0503835a82ae5c0603835a82ae5c0703835a82ae5c0803835a82ae5c0903835a82ae5c0a03835a82ae5c0b03835a82ae5c0c03835a82ae5c0d03835a82ae5c0e03835a82ae5c0f03835a82ae5c1003835a82ae5c1103835a82ae5c1203835a82ae5c1303835a82ae5c1403835a82ae5c1503835a82ae5c1603835a82ae5c1703835a82ae5c1803835a82ae5c1903835a82ae5c1a03835a82ae5c1a02835a82ae5c1a02835a82ae5c1c01835a82ae5c1c02835a82ae5c1e01835a82ae5c1e02835a82ae5c2001835a82ae5c2002835a82ae5c2102835a82ae5c2301835a82ae5c2302835a82ae5c2402835a82ae5c2502835a82ae5c2701835a82ae5c2801835a82ae5c2901835a82ae5c2a01835a82ae5c2a02835a82ae5c2c01835a82ae5c2d01835a82ae5c2d02835a82ae5c2f01835a82ae5c3001835a82ae5c3101835a82ae5c3102835a82ae5c3301835a82ae5c330200"_hex_v_u8, {{133936, 1817}, {876600, 19178}, {627930, 13760}, {545040, 11954}, {213480, 4730}}); + TestOptimalLinearization("836884c43c00a30da899200000847285dd280000856c86e6180100853586aa700200846085c6400300853586aa70040085168686300500853a86af38060085238694080700844f85b4200800853b86af380900850985f8580a00850185ef480b00853d86b4000c00853c86af380d00855d86d8400e00853e86b4000f00845985c1781000846485cb081100844c85af581200853e86b4001300845085b420140000"_hex_v_u8, {{45406, 616}, {1043316, 14258}, {52560, 719}, {58692, 803}, {60444, 827}, {52268, 716}, {52560, 720}, {53728, 736}, {54020, 740}, {60444, 828}, {63948, 876}}); + TestOptimalLinearization("8369821a008369821a0185018272028369821a038369821a0485018272058431892e0685018272078369821a088431892e09850182720a850182720b855083280c855083280d8369821a0e8369821a0f8369821a108369821a1185028272128369821a138369821a148369821a1585508328168369821a17823d834a188369821a198369821a1a8369821a1b855083281c8430892e1d842f892e1e843086101f8369821a208369821a0e338369821a1b208b0586542218860b8a381a208550832811208e538870111d8550832809208737843609208369821a09208369821a1c0d8369821a09498369821a032b8369821a032a8369821a0b2283699e5a04298369821a032c9d7181ea04012d8369821a012f8359a46c012e8369821a0131833983380034bc5285e37a110000110000000000000000000000000020bf6983b65e09000200000400000000000000000000100900"_hex_v_u8, {{25744, 6035}, {2422, 601}, {61527, 21962}, {43202, 25453}, {899, 2420}, {205, 617}, {205, 617}, {205, 617}}); + TestOptimalLinearization("8089328682520083329b52000083329b52010083329b52020083329b52030083329b52040083329b52050083329b52060083329b52070083329b52080083329b52090083329b520a0083329b520b0083329b520c0083329b520d0083329b520e0083329b520f0083329b52100083329b52110083329b52120083329b52130083329b52140083329b52150083329b52160083329b52170083329b52180083329b52190083329b521a0083329b521b0083329b521c0083329b521d0083329b521e0000"_hex_v_u8, {{114400, 35136}}); + TestOptimalLinearization("872d8f58009928c064000089509a400000895d99700100860d90680200860a905e03008655921c0400872b936e0500857f904006008603904a07008608905408008602904a0900861f91100a00862591240b0000"_hex_v_u8, {{20428, 16489}}); + TestOptimalLinearization("8472cf14008472cf14018472cf14028473cf1402028473cf1401028472cf1403018472cf1401098472cf1403068473cf1402048473cf1402058473cf1402068472cf1402068472cf1402078472cf1402088473cf1402088472cf1402098473cf14020a8472cf14020a8472cf14010d8473cf14030b8473cf14020cb42181bf200d00010e8473cf14020e8472cf14040d8472cf14030e8472cf1402108473cf1402118473cf1402118473cf1402128472cf1402138472cf140213886f80845e090b01148473cf1402158472cf1404148472cf1403158472cf1402188472cf1402178473cf1402188472cf14021b8473cf14001b00"_hex_v_u8, {{5130, 754}, {5130, 754}, {5130, 754}, {5130, 754}, {25650, 3772}, {61560, 9053}, {46170, 6790}, {30780, 4527}, {5130, 755}, {5130, 755}, {29055, 8080}}); + TestOptimalLinearization("808967889a74008367a77400018367a77401008367a77402008367a77403008367a77404008367a77405008367a77406008367a77407008367a77408008367a77409008367a7740a008367a7740b008367a7740c008367a7740d008367a7740e008367a7740f008367a77410008367a77411008367a77412008367a77413008367a77414008367a77415008367a77416008367a77417008367a77418008367a77419008367a7741a008367a7741b008367a7741c008367a7741d0000"_hex_v_u8, {{154054, 36217}}); + TestOptimalLinearization("8335b816008335c44c00008335c44c00008331c40c00008335c44c00008335c44c00008335c44c00008335c44c0000833dc54800008335c44c00008331c40c00008335c44c00008335c44c00008335c44c00008335c44c00008335c44c0000833dc54800008331c40c00008331c40c00008335c44c00008335c44c00008335c44c00008335c44c000000"_hex_v_u8, {{101643, 12995}}); + TestOptimalLinearization("89c0788087d818008448c72800008448c72801008448c72802008448c72803008448c72804008448c72805008448c72806008448c72807008448c72808008448c72809008448c7280a008448c7280b008448c7280c008448c7280d008448c7280e008448c7280f008448c72810008448c72811008448c72812008448c72813008448c72814008448c72815008448c72816008448c72817008448c72818008448c72819008448c7281a008448c7281b008448c7281c008448c7281d008448c7281e008448c7281f008448c72820008448c72821008448c72822008448c728230000"_hex_v_u8, {{1119820, 172280}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}, {4628, 712}}); + TestOptimalLinearization("81c62aebef0200823580816a01819771dba4680280d371c4eb600381ac0ee3960604854080ae0c05845081ce140505823580a59b260605857c82bb400705824180e86008058a4884bc36090582428188380a05833182d3140b058235808f93460a05860881fc300b05832480c92c0c058332818f7a0c05860082b74c0d05844482c1160e058235ed82480f058545828578100583368186741105854c829a121205823980f8361205823981906e13058235809892121405845181b2001505833180f3481605823a8188141705860482b12c1921857c81f5401d00847c82a7201b038b7e84bf581c03824180ef6a1c04875382d1161f02826581ce621e04875385b93a2300854d83c1702400833e81ce202203823681bf182402823581bf182403823581bf182503823681bf182702823681bf182a00823681c75a270484448394622c00823581c75a2d00823981eb302e008a4484cd682533860482e96c2831824181e6282534845182fa5430012797518b902c310028833281b9381930845181dc44300018ad5688bd082e1b857c82dc2e075f823982ac58045c9b698c944e3500021d00"_hex_v_u8, {{2075276, 38979}, {2283550, 42896}, {1937047, 36390}, {1473556, 27686}, {23320, 441}, {22996, 449}, {34161, 708}, {21037, 437}, {21037, 438}, {52893, 1107}, {20492, 437}, {20492, 437}, {20492, 438}, {20492, 438}, {20492, 438}, {32490, 721}, {21489, 485}, {37048, 845}, {28811, 708}, {17527, 441}, {16970, 442}, {16988, 450}, {21456, 574}, {15963, 441}, {27152, 764}, {15413, 449}, {135287, 4031}, {59862, 1792}, {14960, 449}, {35776, 1123}, {121446, 3848}, {28198, 896}, {26313, 844}, {17469, 562}, {27798, 900}, {25020, 837}, {16890, 566}, {21450, 720}, {107879, 3689}, {19648, 721}, {45100, 1662}, {24408, 904}, {29835, 1107}, {23968, 892}, {12950, 548}, {33576, 1553}, {77700, 5974}}); + TestOptimalLinearization("8368aa5e008368aa5e018368aa5e028368aa5e038368aa5e0483689726058368aa5e05058368aa5e05058368aa5e05058368aa5e05058368aa5e05058368aa5e05058368972608038368aa5e010b854ea024080005a35780b638020ba35780b638010e83679708010d83679708020d83679708030d83679708040d83679708050d83679708060d83679708070d83679708080d83679708090d836797080a0d836797080b0d836797080c0d836797080d0d836797080e0d854eba7414001c836797080f1d83679708101d83679708111d83679708121d83679708131d83679708141d83679708151d83679708161d83679708171d83679708181d83679708191d836797081a1d836797081b1d836797081c1d836797081d1d836797081e1d836797081f1d83679708201d83679708211d8734cb0a2a0000308734cf280013112600"_hex_v_u8, {{2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {2799, 616}, {3834, 846}, {4869, 1076}, {12293, 2924}, {1555, 616}, {2130, 846}, {41000, 16380}, {33300, 13305}}); + TestOptimalLinearization("8361ee3400877f80d86401877e80d864028362ee3403877e80d86404877e80d86405877e80d864068362ee34078341e5640707877e80d86408078362ee3408078362ee340807877f80d86408078346e56409078361ee3409078361ee340907877f80f97608078342e56409078361ee340b068361ee340918877e80f51809178346e5640a178360f80c0a168342e5640b168362ee340b158361f80c0a148341e5640b148361fa420b138342e5640c138361fa420a118341e5640b118343e5640b2c8361fa420c2c8361fa420a288341ee5c0b288361fa420a268342e5640b268361fa420b238343ee5c0c23877e80f5180b218343f07a0c21877f80f5180b1c8343f07a0c1c8341f07a0a42877e80f5180b42877f80f5180b3b8341f07a0c3b877e80f5180b378342f07a0c37877f80f5180b338341f07a0c33877f819a421e0a2d877f819a420a1511877f819b2c031921877e819a42061121877e819a42090c3b877e819a420b0737877e819a4203141600"_hex_v_u8, {{263296, 19845}, {267965, 20479}, {7293, 577}, {7293, 577}, {7293, 577}, {7293, 578}, {7293, 579}, {7293, 579}, {7150, 577}, {7150, 579}, {6578, 577}, {6578, 578}, {6578, 582}}); + TestOptimalLinearization("80de1a89c02e0080dd1c8aac460180d10789b70202b04282ee2803854da96204833da30005ae0e82d6200682429268078608ac7408833e973a09b4558397700a823589e10e0a0a8331b54c0b0a8335a0000c0a823588ec180c0a8544ac400d0a8242bd000d0a8235899b7e0e0a8546ac500f0a8544ac5011098242974e12098545d03a110a8331a42215058241974015068545d35c140883319d5017068578b37c130a8541d3261408f43185ba60120a82429b001607833282b6161a048235ac5a140a8336b87219068331b54c14288f1c81a34020001a8751bb1e2000178540ac341f001483359d6611228241cc781024833db67c101a8b708099280d1dbf3a85a62a2301021483319d4a0634833d9e1e0633dd3884c704230000108a58d654120f0a9e65829c580438880ff5100010208474e9700030894880bd1002013200"_hex_v_u8, {{59807, 6900}, {10201, 1286}, {5422, 837}, {173248, 27774}, {454202, 73978}, {39260, 7429}, {5967, 1134}, {13266, 2536}, {37285, 7306}, {61944, 14794}, {1792, 450}, {68941, 17522}, {5610, 1496}, {2112, 565}, {1575, 450}, {1960, 561}, {2920, 836}, {1568, 449}, {3855, 1105}, {1971, 565}, {1957, 561}, {2920, 838}, {2912, 836}, {7642, 2250}}); + TestOptimalLinearization("81903fc9ee3800819170cfe6520180d334bef35002819540d0d4480380da6ebdd94a0482359094100405823580a7120504823589bd760504847c819d100504823585f11c060482358d8d6a0604823589c62e0604823580b5520704a51586dd5008000000000000"_hex_v_u8, {{784665, 36085}, {764717, 35881}, {611877, 29016}, {598334, 28389}, {747940, 35621}, {63400, 4885}}); + TestOptimalLinearization("808963829f2c0083638d2c000183638d2c010083638d2c020083638d2c030083638d2c040083638d2c050083638d2c060083638d2c070083638d2c080083638d2c090083638d2c0a0083638d2c0b0083638d2c0c0083638d2c0d0083638d2c0e0083638d2c0f0083638d2c100083638d2c110083638d2c120083638d2c130083638d2c140083638d2c150083638d2c160083638d2c170083638d2c180083638d2c190083638d2c1a0083638d2c1b0083638d2c1c0083638d2c1d0083638d2c1e0083638d2c1f0083638d2c200083638d2c210083638d2c220000"_hex_v_u8, {{58776, 39148}}); + TestOptimalLinearization("8d29889718008368809064018368809064028d1386db10020292618bd5080302846380ac380301846380ac38030283688090640307854e80c6580500038360b8540403824182812a0503844682b6300603846380ac38040e8368809064050e8368809064050b846380ac38060a842ebf400609846380ac38031683688090640415846380ac380410846380ac38011a854e80c65802030b846380ac3800108368809064010e846280ac38000f8368809064010d846280ac38000e8368809064010e846380ac38000f00"_hex_v_u8, {{203813, 4811}, {28120, 710}, {63240, 1811}, {9330, 616}, {9330, 616}, {9330, 616}, {9330, 616}, {9330, 616}, {62880, 4156}, {11100, 738}, {11100, 738}, {11100, 739}, {11100, 739}, {11100, 739}, {11100, 739}, {11100, 739}, {11100, 739}, {11100, 739}, {11100, 739}, {11100, 739}, {3690, 608}, {4128, 686}}); + TestOptimalLinearization("81bd6f8dc370008544a9200180df1588f04e028541ac340380c97c88b0240480e74989ca6c0583359a38068608d7240606854ec40a070687128091760806941381c52009068608d7240a06854de8200b05a32081e9680c058445c4200d058335ad300e05823580b40e0f05875d80856810058450809a380f068578d60e10068545d03a11068546b63212068a54808b20130682419c7214068474b93e15068235a26216068608d7241706833ead6c1806823580840a19068242cd221a068754c4081b06847480971a19068235c5281a068a58809c641b068474d2101c068239c2041d06854cd11e1e06823aa16e1f06ab7282a6702006823580887621068608a22825028540af5224048500e23425048336c0502604823abf2627048235fa3a2804896a80984e2904823997162a048803bd7a2b04823a97082c0483329d4a2d048332b1343002857cd64028388d7c818d0033002d8500ca0022328452ec581d36845180804835011a8545b9541a368546c658242dac1f82811c3702178450af18360210a70de536270e10854dee6c2c0c0400"_hex_v_u8, {{248259, 59175}, {180475, 43031}, {27624, 6624}, {167256, 41844}, {116449, 30424}, {3113, 832}, {1547, 441}, {4029, 1155}, {1540, 442}, {1957, 562}, {1756, 565}, {2260, 904}, {6555, 5133}}); + TestOptimalLinearization("c554baaa4600835c82fa220000835c82fa220100835c82fa220200835c82fa220300835c82fa220400835c82fa220500835c82fa220600835c82fa220700835c82fa220800835c82fa220900835c82fa220a0000"_hex_v_u8, {{486115, 9044}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}, {32465, 604}}); + TestOptimalLinearization("8184258aa316008185018287740180f14981ea6c028183288285600381a93a82c0180481831082a64005818a4182b2460681884983ae0a0781911684fd2208857cc81608088950904409088a4480d3240a08823590e0460a0882358fba2e0a08823590d1380a08823593963a0a08823590ed140908823590ae680b07823584640b08823a9f540d078235846e100585159a640f078541886014038478b41015038a54f370120782368a7a1604823588be08190282358fde161308854188601408823ab9661a03823a85101a048544c0141906844487281c048332aa561b068239a6181b078944e9201b0882358ee5281b088540bf6c1c08850088001d088c5f934a1e088445be0e2601833587662206854ce25a26038236856c22088541c30c2a018332875e0d2a8444c554250408875dd152121716833d8776023080970e80e35a013a9c6d81aa082b0000000000002400"_hex_v_u8, {{188460, 36250}, {175929, 34601}, {161920, 31928}, {182991, 36239}, {179050, 35516}, {176697, 35119}, {179744, 35778}, {194089, 38639}, {169428, 33733}, {7480, 1492}, {19140, 3821}, {4170, 836}, {4150, 832}, {2795, 562}, {2090, 442}, {3400, 760}, {5913, 1950}, {1778, 789}, {765, 438}, {438, 438}, {563, 565}, {1130, 1135}, {392, 442}, {375, 437}, {370, 437}, {1122, 1360}, {14637, 19470}, {532, 708}, {576, 768}, {624, 833}, {1317, 1759}}); + TestOptimalLinearization("836880ed0e00d724a0e46400008430818630000084308186300100842c81846c02008430818630030084308186300400842c81846c0500842c81846c060084308186300700842c81846c080084308186300900842c81846c0a0084308186300b0084308186300c0084308186300d00842c81846c0e00842c81846c0f0084308186301000842c81846c11008430818630120000"_hex_v_u8, {{15239, 616}, {276850, 11300}, {16758, 684}, {16758, 684}, {16758, 684}, {16758, 684}, {16758, 684}, {16758, 684}, {16758, 684}, {16758, 684}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}, {16856, 688}}); + TestOptimalLinearization("d02a82c24800fe6b84ff6001de7983ff08028235930202028578e04803028336915204028a48809e3405028474829172060285409a3807028474e30c08028a48809e34090283128d3c0a0283118d3c0b028608e1700c028948fd600d02857ce4060e028a5880aa140e028542b5700f028a4480a70e100282359f0412018600e44012028600e8001302b62180aa301402854182ad48150282368f0216028a4cef3417028a50d65418028500ee761a018600e4401a028236a3041b0286538292241d018578eb021e01823684fe6c1e028235a9061e028478f1241f02857cee4020028474dd4021028236ad0623018235a50423028241f41824028235a104250282418f0225238a5480a83c2800188235900a1e20824181834e181e82359f3e270f8a5080b9002b01078241a9060d2700"_hex_v_u8, {{119706, 15443}, {6467, 892}, {178169, 24786}, {6244, 888}, {6328, 904}, {10202, 1480}, {10202, 1480}, {2947, 438}, {8112, 1352}, {2306, 438}, {90860, 18386}, {7194, 1484}, {3512, 834}, {5610, 1488}, {3272, 1003}, {1281, 437}, {1025, 438}, {1025, 449}, {1756, 832}, {2019, 966}, {926, 530}, {10968, 7073}}); + TestOptimalLinearization("80896681da040083668b04000183668b04010083668b04020083668b04030083668b04040083668b04050083668b04060083668b04070083668b04080083668b04090083668b040a0083668b040b0083668b040c0083668b040d0083668b040e0083668b040f0083668b04100083668b04110083668b04120083668b04130083668b04140083668b04150083668b04160083668b04170083668b04180083668b04190083668b041a0083668b041b0083668b041c0083668b041d0083668b041e0083668b041f0083668b04200083668b04210083668b04220083668b04230083668b04240083668b04250083668b04260083668b04270083668b04280083668b04290083668b042a0083668b042b0000"_hex_v_u8, {{56090, 44782}}); + TestOptimalLinearization("80ea55a1fc3c0080e47da4dc2c0180e159a1c3340280e411a4ca500380d35ba1f20c04823586f77404048a5080b00405048265994a0604823a994a07048242c90a08048500e91a0904823680830e0904850081884e0a0482358397480b048474d8080c0485208194460d048542bf6c0e04854dc40a0f048239c92010048335ad181203823585e33a1104853f80871812048474e80613048236e47a1304823582a2261404823582894414048241c86a1504854cc40a17038658d0301704854dc0641804865fc4601c00138546c3361c0109833eb00a041c8452ba141c0004875c80c2541e0002833ead6c032900"_hex_v_u8, {{386635, 31880}, {338487, 29454}, {351640, 30602}, {341304, 30204}, {311144, 27664}, {12522, 1116}, {4752, 441}, {4741, 450}, {4725, 449}, {8716, 831}, {6723, 756}, {6797, 768}, {11330, 1488}, {5700, 756}, {7562, 1418}, {5208, 984}, {3786, 722}, {4421, 845}, {2956, 565}, {4379, 838}, {2998, 574}, {4210, 845}, {4150, 834}, {4464, 991}, {1701, 442}, {1701, 485}}); + TestOptimalLinearization("83698158008570832e0183698158028361831403875387440483698158058369815806b95d82d2480785018222088369815809836981580a836981580b836981580c8669831a0d836981580e836198220f8369815810860b843211836981581111843083221111bf69a814111183698158121184308336121183698158101188018362101183698158130a836981580e108430883c0b1183698158092b8339836c092a83698158082983698158082883698158082583698158061f83698158061c83698158091883698158073b83698158073983698158052983698158042383698158042183698158044687538744052d83698158042d83698158042883698158034d83698158012a836981580329836981580252823d880c022b83698158013d83698158003e83698158003d857c8968050a1f823d8400013eb623c2281104000004010500000000000019a63281c2600b0100010203050000000f00"_hex_v_u8, {{29924, 7517}, {1617, 609}, {25754, 20468}, {754, 1062}, {546, 1107}, {1036, 2126}, {7609, 17367}, {1180, 3530}, {2634, 8297}, {172, 617}, {172, 617}}); + TestOptimalLinearization("836280c06000836180c06001836280c06002834280b1600202836180c0600302834280b1600302836180c0600402834280b1600402836280c0600502877e81fc200509833a80b1600509836280c0600609877f81fc200608834180b1600608877e81fc200607836280c0600607834180b1600707836180c0600715834280b1600614836280c0600611836180c060050f834380b160050d834180b1600420834380b1600318834280b1600315836180c0600510877f81fc200327880281f96010030d836280c060012b834280b160002c836180c060012c880281fc2006070e834280b160012e836280c060022a877f81fc200a0913833b80b160012d877e81fc2002022a877f81fc2009050e877e81fc2001022c877f81fc20040f0a877e81fc2000110b877f81fc20020f0b877e81f96006021100"_hex_v_u8, {{96480, 4675}, {48240, 2338}, {48240, 2338}, {482080, 23373}, {12400, 609}}); + TestOptimalLinearization("836880bf4000834c80b6600000836880bf400100834c80b6600001836880bf400101834c80b6600002836880bf400102834c80b6600003836880bf400103834c80b6600004836880bf400104834c80b6600005836880bf400105834c80b6600006836880bf400106834c80b6600007836880bf400107834c80b6600008836881b768010800"_hex_v_u8, {{130900, 6160}, {11760, 588}, {11760, 588}, {11760, 588}, {11760, 588}, {11760, 588}, {11760, 588}, {11760, 588}, {11760, 588}, {11760, 588}}); + TestOptimalLinearization("8369817400885084460183698174028369817403842482760485028244058501824406836981740783698174088550827609866883460a836981740b836981740c850282440d836981740e836981740f8550827610836983261183698326128550846613836983261483698326158501841c168501841c1783698326188550846619836981741a836983261b836983261c836983261d842486681e836983261f83698326208501841c2185508276228550827647850182442483698174258550827626836981742783698174288369817429836981742a866783462b836981742c836981742d836981742e836981742e2f956f9220152f873786240e2f8667855c142a8b0589240f23823d897c0453bc50d70c06000000000000000000000024bf66db78061e00000000000000000000000000000035c30085961a0200020b000000000000000000000000000000003500"_hex_v_u8, {{59283, 28013}, {7894, 15440}, {9513, 20143}, {466, 1079}, {430, 999}}); + TestOptimalLinearization("cc1b8ae36800d6628bd74401912d81d342028368d406038e54819144048f7481a73405af7687b42806d35e8aa12c078416ab0c088f4781d31609a42e85905a0909b53087da7c07098459e30a0809cc4789cd7e0709912881db4405098422de500509a84b8693220e000d901e828d56050f823cce0e030f9017828c2201158608809b340807000d901e81d8420116925282b97c080600020a901c81d8420117901f81d8420012ca088ab75202010102020200"_hex_v_u8, {{68948, 6262}, {127805, 11981}, {21771, 2119}, {44226, 4410}, {22113, 2207}, {146849, 14665}, {162611, 16557}, {31803, 3253}, {151865, 16609}, {6120, 674}, {216238, 23904}, {6405, 729}}); + TestOptimalLinearization("8530af0600853daf60018930cd0602893abe00038638b70204862db62805853da600068a39e134078531af060707853caf600707853ca60007078531af0607078530af0606078530a53808068332b77408078540e6600c04883cc62009078500dc3e0c058500dc3e0a078312bf5e0b07853caf600b198531af060b168a4cd6180a1682359d5e09148241a0200b11853caf6008138239be4c09138541bf6c0b0e853daf60072b8745bf1c06258530af06081d8751d50209148f4bfb581200188531af0604278530b53a03228500dc3e05208638ab68032d8332b774042c8531af0604298a4880a930032b853db940051f8c33e3640b10248931cd0605348235a44806348530b53a052a8570f4340423853ca600044387548091640430853db9400328937081fa741410002e8835c564033c8a4c80a9300b12168236b366012a8c3be4200a0e23894480997c021c138e4bf4180102308f1c80cc6e06020832853ca60001338654f55602298534a55001338824ce14003f853ca600003f00"_hex_v_u8, {{16424, 2763}, {9568, 1720}, {54506, 9812}, {11635, 2160}, {45440, 8447}, {20893, 4158}, {4150, 833}, {5505, 1105}, {7182, 1503}, {68976, 14854}, {1967, 437}, {11126, 2482}, {24695, 5526}, {13379, 3279}, {10034, 2836}, {2496, 828}}); + TestOptimalLinearization("80884d94bf40008349db4000018349db4001008349db4002008349db4003008349db4004008349db4005008349db4006008349db4007008349db4008008349db4009008349db400a008349db400b008349db400c008349db400d008349db400e008349db400f008349db4010008349db4011008349db4012008349db4013008349db4014008349db4015008349db4016008349db4017008349db4018008349db4019008349db401a008349db401b008349db401c008349db401d008349db401e008349db401f008349db4020008349db4021008349db4022008349db4023008349db4024008349db4025008349db4026008349db4027008349db4028008349db4029008349db402a008349db402b008349db402c008349db402d008349db402e008349db402f0000"_hex_v_u8, {{460320, 45693}}); + TestOptimalLinearization("80cc00829900008449c452018336c318028335b4200380c95c86c57004ce3488a30c0580b63281ed680683358c1a078477da46088336b37c0988728092340a8540955c0b9f0a82831c0c8474c2760c0c82359094240d0c8335b2160e0c8239af7e0f0c823aad50100c9738c840110c8478c3520e0c823581db200f0c82358ae40e100c847cbb000f0c8478cf74100c8331a006110c8311af5c120c82358dd418130c8239ab58140c8331b97c1708854d926018088478c32419088600cf401e048621b8701f048235b13a1c088336bc4a160c8242b01c1e048335b216142d8331b97c08268474d14c131b967e81f164040a0a000009bd6580a1061f00000000000000000f00"_hex_v_u8, {{5859, 759}, {4364, 566}, {76038, 10164}, {9434, 1266}, {187130, 27709}, {3774, 561}, {3774, 561}, {172985, 27560}, {4457, 713}, {209015, 34252}, {3408, 565}, {3118, 529}, {3275, 565}, {3275, 565}, {4393, 760}, {4347, 756}, {4370, 760}, {5152, 896}, {3840, 764}, {3704, 929}, {2115, 561}, {9990, 3884}, {1264, 845}, {845, 565}, {10371, 8037}}); + TestOptimalLinearization("cf0884cf6c008324a54400008324a54401008324a54402008324a54403008324a54404008324a54405008324a54406008324a54407008324a54408008324a54409008324a5440a008324a5440b008324a5440c008324a5440d008324a5440e0000"_hex_v_u8, {{46134, 10248}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}, {2466, 548}}); + TestOptimalLinearization("8474ad70008474ad70018470ad70028470ad70038470ad70048470ad70058470ad70068474ad70078474ad70088470ad70098474ad700a8474ad700b8474ad700c8470ad700d8474ad700e8474ad700fb83285d734000000000000000000000000000000000f00"_hex_v_u8, {{102810, 19414}}); + TestOptimalLinearization("a408f07c00832c8c320000832c8c320100832c8c320200832c8c320300832c8c320400832c8c320500832c8c320600832c8c320700832c8c320800832c8c32090083308c3e0a0083308c3e0b0083308c3e0c0083308c3e0d00832f8c3a0e00832f8c3a0f00832f8c3a1000832f8c3a1100832f8c3a1200832d8c341300832d8c341400832d8c341500832d8c34160000"_hex_v_u8, {{27053, 17567}}); + TestOptimalLinearization("8362861c00da64e1740000832a83460000832a83460100832e834a02008331834e0300832a83460400832a83460500832a83460600832a83460700832a83460800832a83460900832a83460a00832a83460b00832a83460c00832a83460d0000"_hex_v_u8, {{462, 610}, {6330, 11748}, {295, 561}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {291, 554}, {293, 558}}); + TestOptimalLinearization("80df4cacb754009e3d87e13a0180f36aac8350028541808e5803833180867004833180ce0a05971285db6206ab6d8bbe0407854180f21608841280c278099a2e86ce3c0a83328086700ba81f8abc700c857c80ef360d860080fb000e8331809e140f857881922210ad498bfa16118331808e1a1281c806d98a341380d468ad813a148235a4fa741415824180a40015148239d05c1614823ad22017148236d1101714847480bc561714823980804018148235ace32819148331e7681a138236e35c1d11833580d5681e11854480c66a19148236f9301614833280a23e1c0d8331808612181285788187681514857c81a01a1414846681882e1414823a809620151482359ec46414148335f12e151482359392721514854480a5161614847c80f9502b008241e252191382358082122b02833df27e1130857c818b260b338578818b260b31854d80aa041d1105857c819222013d857881aa5403358a7581d40e3100000029857c81aa540133c92091aa5e2a0000000000000000011e823580a6480035a10988bb460103010b0e000000000d00"_hex_v_u8, {{13253, 561}, {707608, 30199}, {996428, 42555}, {743996, 31775}, {16040, 764}, {36126, 1780}, {88603, 4456}, {34788, 1780}, {542102, 27805}, {8393, 437}, {12540, 658}, {8288, 441}, {15563, 833}, {10463, 562}, {132850, 7145}, {10186, 561}, {70262, 3940}, {71837, 4029}, {55089, 3090}, {102338, 5741}, {94072, 5279}, {15387, 892}, {265139, 15986}, {12139, 756}, {8649, 561}, {12789, 836}, {6446, 438}, {6377, 449}, {31027, 2358}, {7319, 565}, {10946, 845}, {10635, 836}, {14131, 1134}, {5328, 442}, {5256, 438}, {5230, 441}}); + TestOptimalLinearization("846ea73e00846ebc68018540ab6602846ebc68038540ab6604846ebc6805846ea73e06846ebc68078540c33208846ebc6809846ea73e0a8540ab660b8540ab660c8517a9520a0c8517a9520a098517a952030c8540ab660e000e8540ab660e000d8540ab660d000d8540ab660e030a8540c3320d000d8540ab660e000d8540c3320d000c8540ab660e000c8540ab660b02098540ab660e00098540ab660a020d8540ab6601178540c3320e010a8540ab66021b8517c00c01198540ab6609071b8540ab660907178540ab660d02198540ab6609090d8540ab660e021b8540ab660c041b8540ab660a00178540ab660a04138540c3320702168540ab660605178540ab6604031b8540ab660411108540ab6601228540ab6605031a8540ab66020b168540ab66050e108653b72a0a00032800"_hex_v_u8, {{3956, 750}, {3956, 750}, {3956, 750}, {3956, 750}, {3956, 750}, {4377, 832}, {4377, 832}, {6968, 1582}, {17006, 4078}, {10111, 2496}, {20807, 5971}, {2591, 750}, {2591, 750}, {2729, 791}, {2729, 791}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}, {2867, 832}}); + TestOptimalLinearization("836880d74800836880d74801836880d74802836880d74803834c80cf100303836880d7480403834c80cf100403836880d7480503834c80cf100503836880d7480603836880d7480603834c80cf100703834c80cf10060d836880d748070d834c80cf10060c836880d748070c834c80cf10060b836880d748070b834c80cf10070a834c80cf100519836880d7480619836880d7480516834c80cf100616836880d7480513834c80cf100613834c80cf100423836880d7480523834c80cf10051e836880d748061e834c80cf100519836880d7480619834c80cf10042c836880d748052c834c80cf100426836880d7480526834c80cf10041f834c80cf100334834c80cf10022b887685bb3e0301010b0300"_hex_v_u8, {{316363, 12974}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}, {13320, 588}}); + TestOptimalLinearization("808934829e280083348c28000183348c28010083348c28020083348c28030083348c28040083348c28050083348c28060083348c28070083348c28080083348c28090083348c280a0083348c280b0083348c280c0083348c280d0083348c280e0083348c280f0083348c28100083348c28110083348c28120083348c28130083348c28140083348c28150083348c28160083348c28170083348c28180083348c28190083348c281a0083348c281b0083348c281c0083348c281d0083348c281e0083348c281f0083348c28200083348c28210083348c28220083348c28230083348c28240083348c28250083348c28260083348c28270083348c28280083348c28290083348c282a0000"_hex_v_u8, {{63216, 41968}}); + TestOptimalLinearization("80af1a838c0a0080b37b82af76018335854e028335a64e03867cb2760483358a0c0580cd6485e13206854c8f3a0606823586e37207068241894608068750901a0906857cf2400a06823aab020b06824194060c068541bc500d068608f3440e068241a2000e06854ebd420f06823588b41610068500e13e11068241a2501206894480c21413068500905a1406854d8f3a150683368766160683358a0c1306823a983a1306823586bd7e140685458f261506833da17616068242a0341321854dc0641d000a83318a02061d854dc064120d8751901a0124b524808a58091200000000000200"_hex_v_u8, {{128151, 26614}, {4001, 846}, {127437, 27752}, {2535, 565}, {116824, 26905}, {2235, 573}, {1629, 442}, {3323, 1020}, {1347, 449}, {3239, 1554}, {675, 449}, {1133, 768}, {2125, 1691}, {1053, 844}, {9363, 7513}, {1053, 845}, {1043, 837}, {1101, 1105}, {563, 566}}); + TestOptimalLinearization("818241dbac3c0081c70c809fe6240180d740e79b0c0280eb5af7b4420380bf15cec96a0480fa208081cf4c0580ee2bf89d3a06950d8a8d7e0780c557e3bb7c0881866980839b52098235829f6a090982359fbf3209098335818f5a0a098331818d620b09823596fa1e0b09847882bc280c09823580e97e0d09845081de320e09823592c7020e09847482d4420f0985428291041306824281ec6c1208847882955c12098235a1b3181209863083f66a1309823580fa20130982358fec3c1409823593a460140984788298581608823680dc761609824180d87c1807863d83f32e160982358fc3761709823596ca261709857c82de401809823a80805019098239cb261b088948ce1c1b09a04c90d5682400228449829160220015866083b07022010c845181fa581f000b875183ee50210304857c82f460082186618381462800001e896d849d302500001200"_hex_v_u8, {{1073483, 28599}, {974262, 25978}, {28500, 760}, {1176771, 31491}, {1583595, 42433}, {1276412, 34206}, {1159437, 31072}, {998800, 26825}, {1208196, 32597}, {16080, 437}, {35960, 992}, {25776, 713}, {39912, 1105}, {62800, 1784}, {26220, 760}, {15039, 437}, {26030, 760}, {24300, 721}, {32931, 993}, {14203, 438}, {91071, 2829}, {22489, 720}, {13950, 449}, {17453, 565}, {17329, 561}, {42904, 1389}, {953801, 38788}, {8296, 442}, {4883, 441}, {5070, 1352}}); + TestOptimalLinearization("a91cb4e81c00fc20809ebc6801a454aef86e02ad6cbad74c03e044fba04e04c020d2950605a004a9893e06a004a9893e07a454aef86e089b38a39a1009a928b4e81c0a8d4c91cc040b8d4c91cc040c81940681dfa368000000000000000000000000000000"_hex_v_u8, {{440910, 5404}, {681347, 8352}, {152386, 1868}, {152386, 1868}, {488998, 5996}, {1017959, 12484}, {344735, 4228}, {344735, 4228}, {392823, 4820}, {392823, 4820}, {296648, 3640}, {1306484, 16032}, {440910, 5416}, {2885940, 35462}}); + TestOptimalLinearization("836181ba1c0088318bdc580000a34a97cd280000a34a97cd280100a34a97cd280200a34a97cd280300832a82965e0303832a82965e0403832a82965e0503832a82965e0603832a82965e0703832a82965e0803832a82965e0903832a82965e0a03832a82965e0b03832a82965e0c03832a82965e0d03832a82965e0e03832a82965e0f03832a82965e1003832a82965e1103832a82965e1203832a82965e1303832a82965e1403832a82965e1503832a82965e1603832a82965e1703832a82965e1803832a82965e1903832a82965e1a03832a82965e1902832a82965e1b01832a82965e1c01832a82965e1d01832a82965e1d02832a82965e1f01832a82965e1f02832a82965e2002832a82965e2201832a82965e2301832a82965e2302832a82965e2402832a82965e2502832a82965e2701832a82965e2801832a82965e2901832a82965e2a01832a82965e2a02832a82965e2b02832a82965e2d01832a82965e2d02832a82965e2f01832a82965e300100"_hex_v_u8, {{124474, 1810}, {827900, 17978}, {540855, 11884}, {462570, 10222}, {201620, 4682}}); + TestOptimalLinearization("921a84f92a008312809d6801841280c870028b7082891803945985e91004997e87ad7c0580b815ada40006941185c61807974d86cf62089f0a88f80209926d85932e0a963686a0560b945a85dc400c8d2183b53c0d9c5288972a0e9b4e87ee380f943585d12c108e1183d818119625869b5a128f3184896213911a84d154149305859a6415914284de0816980986e22e17920d84f54e18841280c2061818875c82817a1918854d818c5c1518823583bf4c1518861c818d101717857080d7481718866081cc2017188235fe2c1618866281d13e1518847880e9441518857880eb781318894c82b7781218850df5601018847c80ed601018854d80e51e0f18847480ed601018854280a71e1018850081a50002298d7f84983828000025dd44a5ec3425000025823980ac38014e866081f670022726ac7cc3fd5016000000000000000000000000000000000001042900"_hex_v_u8, {{1679688, 62029}, {24765, 1116}, {21663, 994}, {76772, 3548}, {21328, 992}, {28220, 1356}, {33551, 1613}, {17262, 845}, {790344, 39015}, {15280, 756}, {15280, 764}, {15010, 760}, {12483, 658}, {17288, 924}, {49284, 2640}, {8150, 437}, {15164, 888}, {13860, 880}, {10767, 834}, {7600, 781}}); + TestOptimalLinearization("836880832c00850d80a738000000"_hex_v_u8, {{19250, 1397}}); + TestOptimalLinearization("836883680084428f4c01836883680284428f4c03836883680483688368058368836806836883680783688368088368836809836883680a836883680b80a70f80a7100b0b809b7c809b7c0a0a809947809948090b809b3e809b40090b80a12b80a12c090b809a2d809a30090b809c0b809c0c090b809554809554090b809913809914090b809b53809b54090b854e80aa4c091f854e80a13a091c872d9804140111854e80a76a0a1b854e80a44e0a1a854e80a9000a19854e80a1440a18854e80a44a0a17854e809d0a0a16854e80a3140a15854e80a13c0a1484418f4c082e867a80c3560628854e8188060c131f854e81887c0c121d84428f4c033184428f4c0034854e80ea2a0d002c84418f4c012a867a80bc3407062b84418f4c012b84418f4c002984428f4c002684428f4c0023854e80a45003072284428f4c0123886080be340a020427854e80a300050620854ed83c05051d886080be7402090524854ed84600092900"_hex_v_u8, {{38835, 23106}, {40075, 24533}, {37668, 23160}, {110289, 72339}, {1062, 705}, {1062, 705}, {1062, 706}, {1062, 706}, {1062, 706}, {33045, 22510}, {30667, 21592}, {31176, 22039}, {26458, 22359}}); + TestOptimalLinearization("8a4481f65e008f7783dc7c00008f7783c70801008f6f83c70801008f7083c7080100008f7783bf620100008f7783bf620100008f7783bf620100008f7783c70801019b1686e862000200"_hex_v_u8, {{313375, 18069}, {36913, 2167}, {36913, 2167}}); + TestOptimalLinearization("dd5096a94c00db479a8f2201de64999944028236809310020285008082080302847c8083020402857c80830205028a5480d9140602847080f950070285008083020802857cfc38090283318c9b520a02823686d3220a02824187b83c0b02823680ad120c028236e65c0d028478fd180e028239c5640f028a5480d91410028a4880d60811028544e5301202854588ba341302847c8082641402823a87f5101402823687ab7e1502823680a1121602823580ad121702823680a7121802833e80c05e1902823ae9161a028241e1381b02850080966c1c0284788083361d028500fe641e02857c8083021f028a4480d9142002847cee0021028578ff7622028a4480d32423028500eb0024028a4c80cf582502833dc52826028a4c80d914280016824185df5e1128823a80ad1211288a4480cf582a000100"_hex_v_u8, {{431706, 13571}, {421921, 14027}, {11145, 437}, {11145, 438}, {11145, 442}, {10761, 438}, {299471, 12673}, {9480, 438}, {12399, 574}, {16040, 752}, {6795, 442}, {6638, 438}, {6300, 449}, {9718, 768}, {8475, 760}, {8449, 764}, {8434, 764}, {8449, 768}, {8388, 768}, {8178, 768}, {8076, 760}, {4530, 441}, {8449, 892}, {8449, 892}, {13962, 1476}, {13962, 1484}, {13962, 1492}, {13962, 1492}, {13764, 1480}, {7104, 764}, {8251, 888}, {13586, 1476}, {13356, 1476}, {6912, 768}, {8028, 892}, {13356, 1484}, {4500, 573}, {6552, 836}}); + TestOptimalLinearization("e05381ab28008756a16001904deb4a02a2608a900003971e81fb4604f32981e44005f179829b2606a51f80ea6a07e15182bc4208e55583b34e099029ed540a8d7adb1c0b882cc9200c8d29d72e0d9235f6600ef77284982a0f823581ea3c0f0f8235975c100b8600e1000c0f823582ab740d0f8265900c0e0f823581ed660e0f964181f340110d9838829b241609823a80cc6a100f857ceb40100f823580da10110f8474829172120f8235b94c130f8235fb58130f82419504160c82358c76140e82428c76150e85429632190b823980a002130f854480b348140f823580a61a140f80c7638487361e000418809e1a83c44421000000002080cf6484a24207130000000e9121d5361800000003020500"_hex_v_u8, {{91200, 4576}, {48195, 6111}, {29525, 3762}, {28266, 5337}, {79016, 15147}, {68868, 16192}, {4752, 1196}, {1582, 437}, {56602, 16222}, {53282, 15911}, {49862, 15150}, {44115, 13578}, {7082, 2217}, {1410, 449}, {5655, 1833}, {6949, 2253}, {5902, 1914}, {5531, 2337}, {1094, 485}, {891, 437}, {2224, 1110}, {891, 450}, {37218, 20378}, {1497, 834}, {43233, 26724}, {41499, 25699}}); + TestOptimalLinearization("80d25b87e93a0081fc0b8ef06a01d96182e34002d51082ca6603c36081ee3c04e02a83865e05d43882c72606c24581e7500780a61285fb1a08bc3f81c73409d87582df160ac74d8282560bce0f82a5480c808b3084ec3a0dc57681f96a0e80852c84cc0a0fce1482a54810be2b81d12011833e9c0011118241956e121183158e5013118450a3501311857c80b80414118242956e15118416ad3e1211857ced5e131182419b20141182399c3e1411847480971a1511880fde3414118544bc7614118331977015118336aa7e1511847cd30015118608f34416118449f54c1611854dc40a17118241a2141711844ab62c17118335993218118241921419118a5080b574181183369b4c1911847ca3001a11954880fc6026058451a540270583158e50193d8474808526183c8451a3502800218331aa56123483168e50035983168358005caa17c280442200000000000000000000000000000000002d00"_hex_v_u8, {{1360002, 282751}, {2975, 662}, {1887, 441}, {1808, 449}, {2464, 721}, {1463, 449}, {2344, 720}, {1463, 450}, {2344, 721}, {1856, 574}, {1830, 566}, {2304, 764}, {1689, 565}, {1592, 561}, {1226, 449}, {1000, 533}, {1000, 533}, {1000, 534}, {300, 534}}); + + // Hard clusters (according to various metrics) in synthetically generated clusters, with 26-64 transactions. + TestOptimalLinearization("826db830008414a23c01744d028723d67e0103845cb0480201846ca1060202802d8b0e030104852dc545040104800e825f040283548424000f830aba39010a8241991402068612d67503030d8360b66301108712eb58070003843b905805040d8449865e05000b8503cd1908040003825ca40b03060204844b8a3c03030313852e8456030404000c834c9d1107000103000b8717b2590a00000200000b8035901106030003000f810c8a1c0a00000003000f862ea7130608000b816aa87706040400188112905b05070a8327bb040d02000000000d844cce410a05010009807994720502040e803c84690a0202020000058061813301050520800e8d6f0906010000001c84288f45090204000100001f820aa37607020206000d8073857c0602091f8539c4120602020502001a8728c826050c01078364c673060c0106832da9030b02020300000000286c877903040a00258466be7b050606000020840e9b7105010a23815f9321080300040002000000867fdf300500000a02002285168b50060008000100010123841ac7210704000200000101001c834d816706020302020000000022845ace070505000000010000000f81419e650207000002002c844eb3520b0000000100000000001582129f1c0b000000020000000008807b98060a0200020000000000038011825908010102000000000100001580378e3006040200000000038561ac27030103020e82459c170002000202000b8336a9420203000000000000000f00"_hex_v_u8, {{3672, 493}, {9871, 2079}, {7901, 1719}, {3172, 732}, {1434, 591}, {1470, 1295}, {734, 715}, {-3533, 9003}, {-3439, 2406}, {-1059, 680}, {-15211, 8262}, {-2491, 993}, {-1297, 351}, {-11899, 3148}, {-1868, 453}}); + TestOptimalLinearization("823f9b1b00864fa2680182328c07028017890c02038607d0770105864dd04a0205852f80020108801e8c6c0208840aac1c050006813f9b0e010e826b9340040006806b856006000969823108000000870bb1230403010c837cb754040502867f934c0600010983388872050100088549c578060001010f861b812304040109872daf3004050109841f8a3208010100000a86008b0d04000407824ea36902090015850f802d01081881068d7f0501000310862fab02000915827e961f0308000011855ac76f030405028668b31a0202061c8602a81b020700001e82029017010a8351af460105000009801b866601030300010e837aa96602030200010e8420af76020400000008850fb76701000201000014861fc37804000100001000"_hex_v_u8, {{9571, 3032}, {9352, 6065}, {1318, 1023}, {633, 568}, {729, 671}, {7060, 11184}, {-8300, 1641}}); + TestOptimalLinearization("8365c026008371c34b01841bc8160284789d30038141a3160486478906058645803806855f977d078175a27308831dab3e0986188b780a8031876b0b8539c1050c8708ea770d825b88020e82588c000f814d9f59108560de11118643c124128058856b13833da2251483229a35158723cc5116800e80241780108e5418807e9432198269a64c1a855cb10d1b82628d431c834cb73600000000000000000000000000000000000000000000000000000000001d8545a35601000000000000000000000000000000000000000000000000000000001c866bd03002000000000000000000000000000000000000000000000000000000001a8251a152030000000000000000000000000000000000000000000000000000000019847d8c2104000000000000000000000000000000000000000000000000000000001887129a26050000000000000000000000000000000000000000000000000000000017840b8517060000000000000000000000000000000000000000000000000000000015857c9c7f0700000000000000000000000000000000000000000000000000000000148608860d080000000000000000000000000000000000000000000000000000000013855d987a0900000000000000000000000000000000000000000000000000000000128461a33f0a00000000000000000000000000000000000000000000000000000000128701e97a0b0000000000000000000000000000000000000000000000000000000010820e84790c00000000000000000000000000000000000000000000000000000000108521d6290d00000000000000000000000000000000000000000000000000000000108406bd0e0e000000000000000000000000000000000000000000000000000000000d850e9f140f000000000000000000000000000000000000000000000000000000000b8424b46510000000000000000000000000000000000000000000000000000000000a830e9c6e110000000000000000000000000000000000000000000000000000000009850d894912000000000000000000000000000000000000000000000000000000000881308b0c1300000000000000000000000000000000000000000000000000000000058273a1581400000000000000000000000000000000000000000000000000000000048426cc27150000000000000000000000000000000000000000000000000000000002817f9964160000000000000000000000000000000000000000000000000000000001873ad510170000000000000000000000000000000000000000000000000000000001816f832a180000000000000000000000000000000000000000000000000000000001874aaf1c19000000000000000000000000000000000000000000000000000000000100"_hex_v_u8, {{2315, 321}, {4683, 667}, {1002, 144}, {4179, 613}, {1369, 254}, {2847, 541}, {2534, 489}, {4242, 963}, {1944, 760}, {832, 472}, {577, 475}, {828, 920}, {643, 967}, {82, 142}, {3527, 21301}, {-455, 904}, {-396, 651}, {-677, 781}, {-381, 398}, {-849, 765}, {-1920, 892}, {-2336, 737}, {-3443, 676}, {-5589, 801}, {-4948, 678}}); + TestOptimalLinearization("8741984d008704eb3f01815799390281319457038626a424048661dc1705861fbf1106814685750782448178088331af0c098525ae120a8662aa220b816d450c8210a2430d870e9a160e804692290f845690751081158744118029844312871ac2091381589435148520ad471580548441168512d313178422b948188647b82e198267a50f1a81479b4d1b815c8c291c8640a8031d851faa720000000000000000000000000000000000000000000000000000000000001d813587770100000000000000000000000000000000000000000000000000000000001d84268e170200000000000000000000000000000000000000000000000000000000001a815f99300300000000000000000000000000000000000000000000000000000000001783569c6c04000000000000000000000000000000000000000000000000000000000016850fa31c05000000000000000000000000000000000000000000000000000000000015833eac460600000000000000000000000000000000000000000000000000000000001081589c4e0700000000000000000000000000000000000000000000000000000000000c805395560800000000000000000000000000000000000000000000000000000000000b817a83730900000000000000000000000000000000000000000000000000000000000a807e8e530a00000000000000000000000000000000000000000000000000000000000a842ac5360b000000000000000000000000000000000000000000000000000000000009803388160c00000000000000000000000000000000000000000000000000000000000785069b4d0d000000000000000000000000000000000000000000000000000000000007831b9e0b0e000000000000000000000000000000000000000000000000000000000003803989220f0000000000000000000000000000000000000000000000000000000000038663ca511000000000000000000000000000000000000000000000000000000000000383789013110000000000000000000000000000000000000000000000000000000000028418907f1200000000000000000000000000000000000000000000000000000000000286178422130000000000000000000000000000000000000000000000000000000000028072935e140000000000000000000000000000000000000000000000000000000000018222825e1500000000000000000000000000000000000000000000000000000000000000"_hex_v_u8, {{3748, 674}, {3078, 561}, {3671, 967}, {3017, 805}, {2769, 994}, {2386, 934}, {546, 277}, {1739, 1038}, {188, 452}, {-35, 365}, {-443, 326}, {-27524, 18833}, {-1152, 664}, {-1098, 632}, {-572, 309}, {-1831, 774}, {-1990, 539}, {-1002, 254}, {-4841, 995}}); + TestOptimalLinearization("814e9f7400857edb66018043813402821fa96e038608bd4c048348bc2d05815ca33c06833387610780068b7408842ca841098059823d0a835382320b85418e4b0c84079d380d814b93520e83218a1a0f816b83491084088a4f11802e910512810a8e67138418c1541480408b2715845aaf6d1682529e5c000000000000000000000000000000000000000000000016837e88730100000000000000000000000000000000000000000000148733ce200200000000000000000000000000000000000000000000118279913c0300000000000000000000000000000000000000000000118276af0204000000000000000000000000000000000000000000000f80598c2405000000000000000000000000000000000000000000000f8734e31706000000000000000000000000000000000000000000000e804b803507000000000000000000000000000000000000000000000a85308b320800000000000000000000000000000000000000000000098418c039090000000000000000000000000000000000000000000008852898740a0000000000000000000000000000000000000000000006807596680b000000000000000000000000000000000000000000000585389e3e0c00000000000000000000000000000000000000000000058612cd260d00000000000000000000000000000000000000000000048338a41c0e000000000000000000000000000000000000000000000486068d430f0000000000000000000000000000000000000000000004806595121000000000000000000000000000000000000000000000038719de681100000000000000000000000000000000000000000000038301ab6912000000000000000000000000000000000000000000000380158b2b1300000000000000000000000000000000000000000000038138844b1400000000000000000000000000000000000000000000038465cb1615000000000000000000000000000000000000000000000386768e491600000000000000000000000000000000000000000000028072920f17000000000000000000000000000000000000000000000200"_hex_v_u8, {{2334, 348}, {5939, 894}, {2743, 415}, {4266, 664}, {2106, 334}, {826, 134}, {4006, 904}, {1321, 331}, {1948, 647}, {22824, 14732}, {793, 816}, {-91, 203}, {-997, 1014}, {-634, 638}, {-930, 902}, {-358, 312}, {-1224, 242}, {-790, 149}, {-2869, 513}, {-6412, 1076}, {-4189, 664}}); + TestOptimalLinearization("84648d7e00851fb02200018604a232010185649025020084139c5b0300863fc31200000384248d2d010003843d822e020002816f9c360400018432830704000009851fa258060000048045826d060001008618c665070000000881138c4305010008825497140305038263985006030002845e9d0404030000000c8140884a010402000986549e79030401000380198b7c03030001011382409164060300000011831598630701000000001181778c2b070200000000000f82118565040005098464b735040300000000007b8863010501001181229368020501000e82039006000300038014817f010001000000000003804e8d080100010000001b00"_hex_v_u8, {{4112, 1539}, {2265, 900}, {8705, 4603}, {2414, 2781}, {76, 1413}, {-1707, 2591}, {-854, 375}, {-4513, 1397}, {-3611, 740}}); + TestOptimalLinearization("6f856d0087059a220172853002813d9a2c038730867604803d820c05842e9b53068066806e07873bb577088146922009847891710a865ba91f0b8018825d0c807e8b550d803789560e810e9c370f8361a271108455c650118440c30b1282068341137f8939148010841515802d917b168736c03217871f801718847ac31819805a211a82569d310000000000000000000000000000000000000000000000000000001b803a916b01000000000000000000000000000000000000000000000000000019836d912a0200000000000000000000000000000000000000000000000000001980348345030000000000000000000000000000000000000000000000000000168124804004000000000000000000000000000000000000000000000000000016843eb63705000000000000000000000000000000000000000000000000000015856c932a0600000000000000000000000000000000000000000000000000001580068a6507000000000000000000000000000000000000000000000000000013801f844408000000000000000000000000000000000000000000000000000012824cb6070900000000000000000000000000000000000000000000000000000f802c8a430a00000000000000000000000000000000000000000000000000000f8477d0400b00000000000000000000000000000000000000000000000000000e8319bf140c00000000000000000000000000000000000000000000000000000d816e9a2c0d00000000000000000000000000000000000000000000000000000d835c886b0e00000000000000000000000000000000000000000000000000000d81709c680f00000000000000000000000000000000000000000000000000000d821f9f161000000000000000000000000000000000000000000000000000000c802582151100000000000000000000000000000000000000000000000000000b84488b0b1200000000000000000000000000000000000000000000000000000b81679e591300000000000000000000000000000000000000000000000000000a8741c4261400000000000000000000000000000000000000000000000000000a805c975b1500000000000000000000000000000000000000000000000000000a7b8946160000000000000000000000000000000000000000000000000000068255820717000000000000000000000000000000000000000000000000000006810b8178180000000000000000000000000000000000000000000000000000068656ac4419000000000000000000000000000000000000000000000000000005810180081a0000000000000000000000000000000000000000000000000000058447a3081b000000000000000000000000000000000000000000000000000004864e960b1c0000000000000000000000000000000000000000000000000000038273a1501d0000000000000000000000000000000000000000000000000000038304a6161e00000000000000000000000000000000000000000000000000000282189d3e1f0000000000000000000000000000000000000000000000000000018338b83820000000000000000000000000000000000000000000000000000000815f92662100000000000000000000000000000000000000000000000000000000"_hex_v_u8, {{4584, 725}, {4364, 762}, {1750, 317}, {4185, 1078}, {1232, 326}, {683, 183}, {408, 114}, {1745, 1029}, {198, 189}, {18555, 18373}, {188, 267}, {96, 292}, {68, 257}, {-196, 469}, {-630, 604}, {-774, 712}, {-203, 165}, {-1478, 974}, {-291, 180}, {-1945, 470}, {-738, 172}, {-3548, 702}, {-755, 134}, {-2029, 359}, {-1206, 186}, {-1582, 220}, {-3524, 460}}); + TestOptimalLinearization("832a8e4f008160850201854bbe4e0283429e1e0382558b2404872c937d05831593610672801d0780088b0c088708a5330982159d510a865db6010b83618d160c8526b8030d824f89520e813090590f80598d62108637b42a1181348e6d12863eb44a13863bc76d1483329779158543b65816813d9a141781198f4618810d934d00000000000000000000000000000000000000000000000000188319ad1601000000000000000000000000000000000000000000000000188634c8740200000000000000000000000000000000000000000000000017826087340300000000000000000000000000000000000000000000000011830d82050400000000000000000000000000000000000000000000000011860b8356050000000000000000000000000000000000000000000000000e807c9141060000000000000000000000000000000000000000000000000985349100070000000000000000000000000000000000000000000000000100"_hex_v_u8, {{774, 136}, {1738, 317}, {4071, 843}, {945, 217}, {3564, 835}, {1059, 281}, {3413, 951}, {3429, 958}, {1999, 578}, {786, 469}, {907, 609}, {681, 463}, {385, 352}, {-79, 114}, {-14191, 11733}, {-1185, 252}, {-1319, 269}}); + TestOptimalLinearization("806c883b00852cb52d0184028f590285248a01000584149e6d0203872de672040284648f2905028554b02800000b8637b471010006860ca51a0402028405a80706000009842ebb39020307845d814d03030006817da346040300058453a9760700010003835c8f680309804a8f0e070200108739ae4c070200010580588310090002000382738e6f090200000c8559c940040501000d87198e190005188417a5560202040e816e803203010600098112956402031d8450a5700a000000000100188117981f0b00000000010013820ba5100406021a8311b240080201000101188060933d0106011b846dc679050301030b840db86c000b0b83619c26080003000000000e842a93440c0000000000000000068675cf6a070005000000001d817b92730307000000178608ac780401001384158255070301000000128449b62709000000000100108124824f020800001e82059501000201000000002281398258020100000001000e00"_hex_v_u8, {{6043, 1305}, {2111, 1592}, {16829, 16660}, {2522, 3629}, {-232, 292}, {-1546, 885}, {-4578, 1405}, {-1409, 389}}); + TestOptimalLinearization("853d9a0300872ce27c01855dc769028042831b0380268234048670b972058159931a0109815b9c66040782548d5d000b8263ab21020a805f86510401088261ab3b06000204811e816802060a8070805704050a8701ad740501010c852fb83f09000001000880538b4c0305048510c24904000a85479a3b030302128413b0290207078409aa5d060500000480518a4103090a805f8413080500000009841ead02040f80058173001b842d802d0903000002098448a96004030617841aba6a09000012835c950c030b011783099f700211840eb8440502080d810e8f79040a010f81588229020704058040080500050302068619a95d0d0003000000068440a122060a010000068518a070030a04248177957b05090104855cca650802040420830c850508050401010d8348a8690206022e71871f000104020728870ec35d020001000000000200"_hex_v_u8, {{6398, 1068}, {3769, 1008}, {1511, 511}, {6, 7736}, {-2660, 6557}, {-1198, 1987}, {-717, 747}, {-2735, 921}, {-2555, 645}, {-2677, 584}, {-9778, 2011}}); + TestOptimalLinearization("80278170008458b93c01840e9c220284358d1b0377800c04871ca3470580338c00068219a40807861f9b4f088200977309810a847c0a82398b2e0b814e9b760c8177915c0d8657a5360e872ba73a0f81290810847db55111854cae4300000000000000000000000000000000000012852bbb420100000000000000000000000000000000001181168d270200000000000000000000000000000000001186049933030000000000000000000000000000000000118304a5550400000000000000000000000000000000000f8338926b0500000000000000000000000000000000000f853c830e0600000000000000000000000000000000000f8530ba630700000000000000000000000000000000000f853191580800000000000000000000000000000000000e870d85570900000000000000000000000000000000000b827e83180a00000000000000000000000000000000000b874abd420b000000000000000000000000000000000008803689030c000000000000000000000000000000000008826287720d000000000000000000000000000000000007836daf3b0e0000000000000000000000000000000000078678ba550f000000000000000000000000000000000006855e877610000000000000000000000000000000000006815385071100000000000000000000000000000000000681200d120000000000000000000000000000000000058350af75130000000000000000000000000000000000028713db5e140000000000000000000000000000000000008257813b1500000000000000000000000000000000000000"_hex_v_u8, {{2372, 409}, {1851, 334}, {3742, 728}, {832, 179}, {1198, 375}, {1873, 654}, {2459, 983}, {2589, 1067}, {791, 441}, {382, 266}, {184, 167}, {6046, 9350}, {268, 510}, {263, 828}, {-7, 288}, {-158, 471}, {-428, 1037}, {-388, 339}, {-1690, 900}, {-1270, 568}, {-916, 278}, {-642, 182}, {-3042, 844}, {-3819, 1016}, {-3826, 816}, {-2475, 516}, {-3102, 621}, {-3131, 592}}); + TestOptimalLinearization("825a8f49008163894a018617c37e028211980d0380649505048345b34905805f976006864f840e07856bde290885638c5c09866bee300a8171805e0b8735db090c8564c6010d8117947d0e841abf260f81209259108559917811814789531284109f4d138325b5191480528b3a158401bf7616864aae10178554db4318800d856719871cc20b00000000000000000000000000000000000000000000000000001a843a994e01000000000000000000000000000000000000000000000000001981339d580200000000000000000000000000000000000000000000000000198644d25103000000000000000000000000000000000000000000000000001980468724040000000000000000000000000000000000000000000000000019810a8d390500000000000000000000000000000000000000000000000000198446b2120600000000000000000000000000000000000000000000000000188575c56f070000000000000000000000000000000000000000000000000017813a880e080000000000000000000000000000000000000000000000000014811f832409000000000000000000000000000000000000000000000000001481289f620a0000000000000000000000000000000000000000000000000011836ab4600b000000000000000000000000000000000000000000000000001186119c020c00000000000000000000000000000000000000000000000000118673b5060d0000000000000000000000000000000000000000000000000010845c93070e000000000000000000000000000000000000000000000000000d804b856b0f000000000000000000000000000000000000000000000000000d857d835d10000000000000000000000000000000000000000000000000000c81469a5e11000000000000000000000000000000000000000000000000000981548a511200000000000000000000000000000000000000000000000000088661c1371300000000000000000000000000000000000000000000000000088119867b1400000000000000000000000000000000000000000000000000088467cb101500000000000000000000000000000000000000000000000000068526a4451600000000000000000000000000000000000000000000000000068278aa281700000000000000000000000000000000000000000000000000068329973818000000000000000000000000000000000000000000000000000582688425190000000000000000000000000000000000000000000000000005853988131a0000000000000000000000000000000000000000000000000005862098561b00000000000000000000000000000000000000000000000000038222a55c1c00000000000000000000000000000000000000000000000000038166a4101d00000000000000000000000000000000000000000000000000028730ef3b1e000000000000000000000000000000000000000000000000000183798e511f0000000000000000000000000000000000000000000000000001836d907620000000000000000000000000000000000000000000000000000000"_hex_v_u8, {{7128, 1003}, {1584, 223}, {4155, 641}, {4115, 666}, {4415, 919}, {797, 210}, {3016, 970}, {677, 355}, {1212, 857}, {878, 867}, {327, 975}, {111, 369}, {-1440, 17399}, {-303, 893}, {-339, 488}, {-586, 825}, {-1001, 633}, {-1284, 732}, {-510, 281}, {-438, 203}, {-745, 340}, {-2403, 806}, {-925, 266}, {-4294, 1052}, {-4252, 993}, {-4536, 885}, {-5353, 964}, {-7198, 1072}}); + TestOptimalLinearization("83158670008454b00201853eae2100038357ac07010384369d5c02028429822b0007801e887b010482216f050001800a877a0600008603a0410403800c873105000c8559ab0c040886199159070001038374b0290103108050807c0205010a830cb359040003010e8413926f04030107807c85220703000000058631af6009000001000a8637a46305050185609360070102000110846cc57b08020100000d6587160805000a8240a87007020201158551874806040101128177974c03080d84368c5f060400041a8559905a040204000c8422903002070513850ead0c09000401001d853dbd51070202020101018574d03d060009001c8104885e0c0200010103867b853c050007010b8158803c00090301118019847605060001000100038361ae5807020202000000058566ac5a0401040100010101835b854f07010000030001098237a37c0402010002001986608e5102040102001f855da12f0201000000010000"_hex_v_u8, {{3137, 724}, {1966, 694}, {2184, 1015}, {1077, 671}, {-56, 417}, {-4305, 19152}, {-424, 603}, {-2200, 861}, {-6216, 1876}}); + TestOptimalLinearization("8711d24400834a8e460180458b4302841fbf3200028025883800068672d2640104841fc210020104856fbd3001078039820e050000018175930b03038174915d070000008469864b011086248a4004098475a8040701000a844e925c080000000d8157a0540302108442c13504020f8162895105020685169902050401128559c2360101020b81568a5207000300028427c340060500001180118d520400000204137a835c0901010001118318ad6606010204000a847c8f39050805835da52600248455cb19050206048400b27b010c048604b97b0302080a842695560024835f96790002158530bc2f030703038365a02f012d807481580800040003001c801e851e01218631a40503050004218535c7030405000413800e82080202040602846ec55f09020002000000000017831fb37904020104000000001f85588150030005010101000885449c3d0600010400000f872e8f3f080101000100000e804a825106000100011e81619a5c03010200000c80118b1301010102002c00"_hex_v_u8, {{5346, 1041}, {12977, 2549}, {3992, 879}, {604, 165}, {9466, 2925}, {10459, 5394}, {1398, 1369}, {-5465, 2673}, {-3270, 1378}, {-25877, 8528}, {-4167, 688}}); + TestOptimalLinearization("845c9571008502c1220180458c6002822dae760381738272048546dc02058710ac1606826c9a240780108c6d088563b973098261b34a0a8625b4170b8532b2110c8376813d0d8424c34c0e8031911a0f81179d031081308c0911872abb6b128670a65813853ab85c148678f60a158672f17316843ba46617865d8214000000000000000000000000000000000000000000000000188537bd66010000000000000000000000000000000000000000000000188669c638020000000000000000000000000000000000000000000000188343b60c03000000000000000000000000000000000000000000000018835bb632040000000000000000000000000000000000000000000000188303b4090500000000000000000000000000000000000000000000001783039c2e060000000000000000000000000000000000000000000000178455c17207000000000000000000000000000000000000000000000016802f8b16080000000000000000000000000000000000000000000000158619a50309000000000000000000000000000000000000000000000014837db56e0a000000000000000000000000000000000000000000000014866897370b000000000000000000000000000000000000000000000014843f87570c0000000000000000000000000000000000000000000000148651ca440d000000000000000000000000000000000000000000000014863cde0f0e000000000000000000000000000000000000000000000014820990560f000000000000000000000000000000000000000000000014820a826710000000000000000000000000000000000000000000000013798c281100000000000000000000000000000000000000000000001182039f0212000000000000000000000000000000000000000000000010833dbc42130000000000000000000000000000000000000000000000108377a51b1400000000000000000000000000000000000000000000000e83649e3a1500000000000000000000000000000000000000000000000d870beb4f1600000000000000000000000000000000000000000000000b83008d401700000000000000000000000000000000000000000000000b842dc51c1800000000000000000000000000000000000000000000000b8359c5141900000000000000000000000000000000000000000000000b835bbe521a00000000000000000000000000000000000000000000000a8255876b1b000000000000000000000000000000000000000000000007824b97161c000000000000000000000000000000000000000000000007834ca4411d0000000000000000000000000000000000000000000000058673a6301e00000000000000000000000000000000000000000000000287218e731f000000000000000000000000000000000000000000000001835e8d622000000000000000000000000000000000000000000000000000"_hex_v_u8, {{7621, 1016}, {3067, 429}, {5953, 838}, {3365, 481}, {1165, 177}, {4390, 676}, {4241, 770}, {3694, 826}, {880, 197}, {1746, 492}, {2419, 699}, {2891, 1040}, {2540, 1008}, {32264, 19146}, {945, 606}, {202, 989}, {-244, 394}, {-556, 703}, {-1018, 1057}, {-566, 469}, {-1564, 1000}, {-2434, 921}, {-2446, 631}, {-2401, 588}, {-6088, 956}, {-3397, 515}, {-6952, 1035}}); + TestOptimalLinearization("805683130081759877018501ac560282608b1e038703b72f048554861d058445a50606856db672078359a70f08864abd4f097e86010a806d82030b82048b470000000000000000000000000c8712ce7e0100000000000000000000000c8470947b0200000000000000000000000c840e9d100300000000000000000000000b84539e0e0400000000000000000000000a8613b00b0500000000000000000000000a8346863906000000000000000000000009863ac27507000000000000000000000006870ebe7f08000000000000000000000005860d842109000000000000000000000005845589030a000000000000000000000002845588460b00000000000000000000000180198b490c000000000000000000000001812b972b0d0000000000000000000000008328a3770e000000000000000000000000872fa0700f00000000000000000000000000"_hex_v_u8, {{3577, 877}, {2923, 769}, {2435, 709}, {783, 480}, {-1399, 8615}, {-337, 909}, {-477, 582}, {-642, 725}, {-1406, 752}, {-804, 388}, {-3142, 915}, {-4096, 1038}, {-2364, 552}, {-4347, 954}, {-1558, 299}, {-805, 153}}); + TestOptimalLinearization("8327982d0080608f67000085299d2a0100847b8207020082769438030082479f0b0202822a996a01000887298a350100098432a61f010883029b5b04000006870ec96a03048172812b03010008824d916f010102000c833aa8040110801a847f0402000002832c862104000000000a873d9c060202010383208e7803010110815d1101041268870f050001851254010e8124804a020411822c9028060001000e866d8a77030105836ba27e001a8503890804030116857b8f620110850ba7510008188704b103010201001c857cc54f000001000000"_hex_v_u8, {{8000, 4089}, {-935, 11933}, {-3202, 1028}, {-2601, 779}, {-4520, 892}}); + TestOptimalLinearization("8678e51700857aca3901844ab2170282049f5f038003895f048638b6740405861f7e0208804491420206842e83130301078100901e02010b852bac6104030281759b31030301058533a67d02098275aa0108000000028178865107010001098336942a02050f841d99450305010e8501ba150304010a820b853205040a836eb53c060501057a83340904000004815e846b050416820d852b03090e845ebb310b020100000b8702835d0212815f9044000d09867ea171001e80359138020705178106880e09010000050f864fc97c040504011683109034030b010b824c8737080202000300000e847d8045090202000101000d835587720507030018862c9d00060500050b820587090608030000008461a5000103020003208601ab780106010102000183069130010305058734844f050200010100000004872fdb12050000000102000100078313ab200203020000248404a92503000101000100"_hex_v_u8, {{497, 327}, {-4592, 24583}, {-360, 1076}, {-2707, 644}}); + TestOptimalLinearization("87139152008053853701864db30c0280338815038544b2040486788f4f05841e841e06862ea22907823d8f150886706f09837b872e0a830e9f7f0b8465c07b0c831eab0b0d810d825d0e832b81100f8435a76a108602b63811810f897c000000000000000000000000000000000000118445b63d01000000000000000000000000000000000011847fc24c0200000000000000000000000000000000001080558c3f03000000000000000000000000000000000010861fbd0304000000000000000000000000000000000010871fde1805000000000000000000000000000000000010815c93240600000000000000000000000000000000000f8726b74c0700000000000000000000000000000000000e817c823b0800000000000000000000000000000000000e865e8c220900000000000000000000000000000000000c8309833a0a00000000000000000000000000000000000b8000854d0b00000000000000000000000000000000000b83049e620c00000000000000000000000000000000000a82619d510d00000000000000000000000000000000000a870899120e00000000000000000000000000000000000a7d670f00000000000000000000000000000000000a8354a83c1000000000000000000000000000000000000a8159941111000000000000000000000000000000000008821da75e12000000000000000000000000000000000007807c8f6013000000000000000000000000000000000007835d840f140000000000000000000000000000000000038212a00a1500000000000000000000000000000000000383039a0c160000000000000000000000000000000000018605c2501700000000000000000000000000000000000100"_hex_v_u8, {{3548, 898}, {3266, 836}, {2613, 693}, {3334, 973}, {21647, 16912}, {849, 990}, {285, 521}, {-52, 125}, {-328, 605}, {-222, 380}, {-423, 128}, {-1353, 345}, {-864, 213}, {-1961, 481}, {-3970, 927}, {-3551, 709}}); + TestOptimalLinearization("8225830d00801c874a01832393380281088e7c038600d033048639927a0581249631068324b3150782649f4c08816840098527b34b0a802c816c0b850a8f040c866cc57e0d811b90350e801a8d5e0f80308c2710810f917011800b835f00000000000000000000000000000000000012836ebc6d0100000000000000000000000000000000001281659b6c020000000000000000000000000000000000128375980a030000000000000000000000000000000000128746857a040000000000000000000000000000000000128170960d050000000000000000000000000000000000118025825f0600000000000000000000000000000000000d810098330700000000000000000000000000000000000c8572b06c0800000000000000000000000000000000000b852da800090000000000000000000000000000000000098427a87c0a000000000000000000000000000000000009806c83380b0000000000000000000000000000000000098470bd020c000000000000000000000000000000000008852b845c0d000000000000000000000000000000000008825f6d0e0000000000000000000000000000000000078151902b0f00000000000000000000000000000000000382369d1d10000000000000000000000000000000000003835f8a7e11000000000000000000000000000000000003855f864c120000000000000000000000000000000000038640bc4013000000000000000000000000000000000003861fa83e140000000000000000000000000000000000038447ba3e15000000000000000000000000000000000003802c8e54160000000000000000000000000000000000018653d11b170000000000000000000000000000000000018638cb2518000000000000000000000000000000000000866fbb471900000000000000000000000000000000000000"_hex_v_u8, {{943, 154}, {4543, 1004}, {1208, 271}, {2086, 484}, {1022, 264}, {549, 156}, {1308, 547}, {1277, 953}, {1026, 778}, {12943, 11680}, {486, 863}, {366, 811}, {445, 1094}, {-55, 479}, {-240, 165}, {-304, 139}, {-1110, 337}, {-3876, 1007}, {-1479, 368}, {-1935, 438}, {-4883, 952}, {-5262, 979}, {-1626, 256}, {-3959, 622}}); + TestOptimalLinearization("813c865a0080178a6f0183449e0202836ca622038446c40d0485028e6e0405816fa17203026c865604028419c03b000e82148c0f0500098351997a070103861b9f0405020a8542a07b07010002861383700111832e89530111860a830c000c8342b138020b810e97660704000006821ba6330803000008855fc612050401000d8028915e03040308832cb03c0a020000000000048509c97d0605000100118167820f07040200086586710703010000000b85762804080104851dcd4105050217854acb3a0a0301000000000f8514976b04080c83449a100808000009874485630b000102010c85799c750d00020001001b80368d0f07030005098259a0700c00000002010a8056876b050b0000001581328a660e0000000100001b807f800405060600098569850a0307050012860a933506040003000200058463b22f09020004000000000021805188430401020602188052853305010203020204824b9e150c0001000101000000248114842c0501030101030a851d944c0503010300238449a2560a0201000000000000000023844ea548070700000000000f8277ae15020700011981479a7e070500000000000000000d81339c5a070003020000000000000016867395490702000200000100001080278938030302000100007a81150400020001318457b11200010201000002827fb116010000010000002300"_hex_v_u8, {{4709, 1055}, {2513, 620}, {2218, 909}, {6605, 3186}, {6218, 3702}, {703, 1507}, {20, 886}, {-2372, 6067}, {-10262, 12450}}); + TestOptimalLinearization("803a8f330086548209018265ac21028521bf2e0104800b844d02018321a05d0006835e933e040003835792230400048047865703068201a07d04068662a16d0308865ad476000e8431bb620200020b8264841503010c8625ba5704000209837d9d52021686378e68020e837b8174020e840e80590301060383329a4007020100118262885c0305010a834d8d2a02128133964d0600040d8437ac2a0503040005816b810705020419840cb2440d00000100000b865ad07005030003118419ae21060201030586748076060005000c80068a0e050103030006870dc31208020000020010863bc91f060100020301000a80218b4f0400020d817c8e6f080100020201000880108c4f010d1b834db0320607000017841ca15c0500040000020478853200020100000000001300"_hex_v_u8, {{3922, 1781}, {3529, 2772}, {16566, 14073}, {579, 1338}, {-1016, 380}, {-7368, 1740}, {-808, 161}, {-872, 144}}); + TestOptimalLinearization("83049f4a0083539553018011896b02843fae55000583309e310201841ba76701028628b504010204850ea4570009820c846d030000078451a60303010006802c8877030203857a9e280304805e857005010c83798c7001050c8441811d0403018550bd5f0305008636c9210701000e846d9e1d010311821d933c06020006847e961a0503000a7a8945060200148610bf4d0115842fa97b0402040476841c010716872d28040201058631a10a0104001a81579d3a060001000100001681078c43020401011b8438844406000001000000000f83678264070000000000000c820c8800040101010017814c9103030300000011840c912200000000001200"_hex_v_u8, {{2085, 516}, {-1, 2091}, {-214, 2450}, {-2803, 3167}, {-16800, 11295}}); + TestOptimalLinearization("8600ce6f00678071018613875102865ed14301000384579a0c010103854cbe730201028376900f03010278810703068355833202058354ab7e03000b844b9a370402098269a4780303068300a66e020001010c8519a4670300020a84319c090600000007698027030204872ac12f05010100000782369e5106010000000f82549d5006000100000f845b9a7b0601010c8536a42e0601000b8325b33a0503010006826498040400050d80488e5702158206906e0702020b6b86170b000100088745ad1703108406aa3b03090e823b9117010b01821394500107148626c44e0b00000001000f82019f2a0103030001128153900a040401000000000d866bde1c03060000000d80548e180503020001068455a7100604010000001e867fd30e0a00020000000000058505891f060101000000000004840b9d3a0303010000000000268631bc12050100020000000500"_hex_v_u8, {{9901, 20654}, {1369, 3124}, {-656, 773}}); + TestOptimalLinearization("851a8e58008454b44d01841a8c2902856eb42e0380314404841bbf4b0582368b3a068067897407823ca94d088420823a098555be160a823a87700b835086700c8350845e0d8747984d0e8137870c0f810f8006108417ab0911842fae6c1280358071138571c674148734d83f158327aa76168228892a178706d45018821e97001983479b591a826ca46b1b856daf59000000000000000000000000000000000000000000000000000000001b8428b309010000000000000000000000000000000000000000000000000000001a826f9021020000000000000000000000000000000000000000000000000000001a86729e61030000000000000000000000000000000000000000000000000000001a8643880604000000000000000000000000000000000000000000000000000000158646cc0e0500000000000000000000000000000000000000000000000000000012846e935e06000000000000000000000000000000000000000000000000000000128551884f0700000000000000000000000000000000000000000000000000000012811e9836080000000000000000000000000000000000000000000000000000000f82308c66090000000000000000000000000000000000000000000000000000000d865ea4640a0000000000000000000000000000000000000000000000000000000884269c770b00000000000000000000000000000000000000000000000000000008831d80570c000000000000000000000000000000000000000000000000000000068531b4620d00000000000000000000000000000000000000000000000000000005865e92390e00000000000000000000000000000000000000000000000000000004855f892d0f00000000000000000000000000000000000000000000000000000003863c8b0110000000000000000000000000000000000000000000000000000000038374aa22110000000000000000000000000000000000000000000000000000000381078e2712000000000000000000000000000000000000000000000000000000038153932c13000000000000000000000000000000000000000000000000000000018321ad5814000000000000000000000000000000000000000000000000000000018318ba1b15000000000000000000000000000000000000000000000000000000018469a57516000000000000000000000000000000000000000000000000000000018310854e17000000000000000000000000000000000000000000000000000000018543a57e180000000000000000000000000000000000000000000000000000000000"_hex_v_u8, {{5480, 1030}, {4602, 881}, {2811, 551}, {4043, 853}, {3062, 687}, {3415, 878}, {1536, 414}, {698, 231}, {797, 438}, {518, 311}, {661, 424}, {568, 442}, {1004, 794}, {504, 592}, {367, 592}, {221, 672}, {67, 271}, {34, 177}, {-526, 14670}, {-108, 541}, {-616, 849}, {-663, 863}, {-769, 956}, {-1245, 990}, {-2033, 1010}, {-1105, 495}, {-1916, 678}, {-2491, 745}, {-3117, 877}, {-980, 263}, {-3333, 680}, {-3790, 536}}); + TestOptimalLinearization("82549265008464d27c01866ab47902837da12a0380368f74048738dc4d05842e8b3b06853dc84a078661ab22088454a16b09865f957f0a8279ab2b0b837b84050c837dc31a0d837db5180e7b816f0f8346ac4310800b883d1180148709128349a63c1380629800148638ae3f15802c8b59168223ae4117801d8e1718812f893319853bdc3b1a841caa5b1b8354aa55000000000000000000000000000000000000000000000000000000001c870abd4d010000000000000000000000000000000000000000000000000000001b85768103020000000000000000000000000000000000000000000000000000001a801c8861030000000000000000000000000000000000000000000000000000001980658e150400000000000000000000000000000000000000000000000000000019826da47005000000000000000000000000000000000000000000000000000000198100884506000000000000000000000000000000000000000000000000000000188528d056070000000000000000000000000000000000000000000000000000001880718a5d08000000000000000000000000000000000000000000000000000000178677ba060900000000000000000000000000000000000000000000000000000017815889110a00000000000000000000000000000000000000000000000000000016813187200b00000000000000000000000000000000000000000000000000000015804a800a0c00000000000000000000000000000000000000000000000000000015847a81550d0000000000000000000000000000000000000000000000000000001486799e420e00000000000000000000000000000000000000000000000000000013835cb8120f00000000000000000000000000000000000000000000000000000012865ff84910000000000000000000000000000000000000000000000000000000118403ac6111000000000000000000000000000000000000000000000000000000108656db361200000000000000000000000000000000000000000000000000000010815da06013000000000000000000000000000000000000000000000000000000108740f30c1400000000000000000000000000000000000000000000000000000010843ad1361500000000000000000000000000000000000000000000000000000010806a92421600000000000000000000000000000000000000000000000000000010874acc3017000000000000000000000000000000000000000000000000000000108001897218000000000000000000000000000000000000000000000000000000108213a100190000000000000000000000000000000000000000000000000000001085159f141a0000000000000000000000000000000000000000000000000000000e806288681b0000000000000000000000000000000000000000000000000000000c842d86401c0000000000000000000000000000000000000000000000000000000b860bb3081d00000000000000000000000000000000000000000000000000000008814ca17f1e0000000000000000000000000000000000000000000000000000000680418c4e1f00000000000000000000000000000000000000000000000000000006820e98212000000000000000000000000000000000000000000000000000000006856db851210000000000000000000000000000000000000000000000000000000300"_hex_v_u8, {{5374, 740}, {1600, 226}, {4365, 637}, {1082, 182}, {4709, 829}, {3468, 637}, {2526, 585}, {2197, 637}, {2833, 993}, {14949, 22604}, {69, 202}, {-130, 886}, {-171, 762}, {-649, 344}, {-611, 256}, {-751, 241}, {-4007, 1034}, {-625, 156}, {-1617, 398}, {-3689, 877}, {-971, 229}, {-2929, 643}, {-2795, 596}, {-2240, 332}, {-7781, 991}}); + TestOptimalLinearization("863e9b20008701a22e0182579614010281579c03010381008737030180078e2603028345911504028417a101030981318d650508867b9a11070006823c924f0105058647a60b050101058579a75b07010001861bdf4c060001010c834e841d0700010106855dca03070102008633dc7f03060d8654bf39090000020c854bc4370802000103846aac180c000100018058811e03138727b30b0a0200010a845a902206050104804191120703010108844cc60f030b00188530b810090000030d8434be3403070d833fad1b00298038953206051d801a520606000f8216a05a030b218007891e020911815b9716030b0b867ab553050709802391740f030000010007817896370d010003000200168041880803080518816ea42a050902040e872d8a09080200050302000e8576960e050b010400098330bb6e0c06020200098418ad6a1002000300000000148704c07008020405030011851f8657060d040e8619be7016000101000000088559d32c0213078307891a08040a00011e8313b627010c0104001e853ad64004070300051b815d834205030c038423aa3904040701030001128155a3390f000000000500308262a43b0a000107010b8136861d0505000701010f82359b1307010005000401001e8028841605000603010200002981679d16030705000300298710cd6c080601010000000101308055803e0802040100010002001681758e2a090400010100000100258741ac0d0a00030001030000000680738e2c06020500000001001783629a68020703000001098707dd08000001030000001000"_hex_v_u8, {{3242, 1160}, {9343, 4022}, {7247, 25626}, {-574, 1753}, {-2604, 5019}, {-2887, 1089}}); + TestOptimalLinearization("83638b0300845bac6801871ac51d028278937a036a5004855494040405814883580405872dd30f030203816f9009030301856bb80f0501000684008658030406805380100400068632a8520500010b80658e2f0403010580068a340702000002845cae500802000000806b8c350400040584319f5a0900000000086f827c09000100000184627b06000004038341844d0603000000001181208d070502000102048043760600000200000010862fad260603000000000a817786690402000003000e84059e410305020004861eb63a0401020001138016862600080486419756020500010008833ca46f04040000001182019658020000000200000800"_hex_v_u8, {{2932, 731}, {1341, 504}, {3541, 2737}, {492, 640}, {40, 106}, {72, 211}, {-779, 2654}, {-2914, 8443}, {-359, 577}, {-2424, 572}}); + TestOptimalLinearization("8015812700866ade5401811c8f7b028442ac3003866fc73d00058220984901048676c4440204813194740500057b8b1b060005845fa71e050106837bb34d060003853abd55070001008519ab1102030e824f94580602000a8405b15c0403020b806a8b78040303088724be690603020380708230080003000d851f9b5d05050000008674dc540401050011864f9652060200020110841c8a61090001000101832c8819060303000005814b9c160601010400068109885a06050002018304b203050001061686419e69020b00128638c97d0a010100000000000018856dc2060302080000028419b54c0307030781568573090300000000000000028428a7150702000100001d864db25f020207000a85409403000700000018822f920c0300010201001483409a1a0501000100000013856ac8200400010000010011803e8f620500000000000019836692540302000000208548895a00000000000c867ecc2801000000000a00"_hex_v_u8, {{6122, 1002}, {7354, 1720}, {2575, 735}, {1254, 454}, {1609, 1061}, {302, 747}, {-6224, 20869}}); + TestOptimalLinearization("8677a44400823d9b1101847c893e0102831ea2130201865b944502028560ac640301856cc01101088644cc2c0302867ea77d0008843d8949040202843a9d0307000081528f5707000005804e8c5b0403000585169c7e06000201834e8e0d0503000d835e96250500000f8628a9550104078018847d0302000201821f90780301000002000681648442030103028433976b070000000000000e844a9116040202000d82349016000880158b240600010100108006831101060001850bb04e0101010100088723835a010002010c830593360203000003811a8e150400000000001c7f8168010000001300"_hex_v_u8, {{2402, 1015}, {2700, 1506}, {671, 764}, {1129, 1309}, {-7302, 12461}, {-1410, 818}, {-971, 282}}); + TestOptimalLinearization("8744ad0c008552be55018377a07f02844ec32802038655a103020482109b72020006867ee92c030006847fb879040003867cc5770301098620c77d06000001834e9c6b050102831ea745070000048736e7040309854bb64e04000a82129d600303068327847c03060c81749e6a0604000c8616af13070400008062915d08010200078007880206030301823e815106000609862ab9220502178451b018080000010c82469e2a0803000001058579c33c090000020116852f8b210704000001068168a05d04070001148616c21d0801020001000e8327991f05000f8025860a0b000002010003862f8a250c00010100000b8002480a00010002000b801f877005000600011e8539be10010b011c8341b558060302000001001a83758b0c09030000000000038738d71b070500000000010000088527c943070005000001001d81789e5204060300001c850eb12a0b03000100000000058676d84b0602010103000b834aaa310202020001000100001c8425af0c0302020001000100000d8107942e02010201000200001c830f9f330000000002000012830ea4700200000000000000000d00"_hex_v_u8, {{7322, 1810}, {2404, 2903}, {8842, 11211}, {4511, 6807}, {764, 2139}, {-1680, 551}, {-4367, 1277}, {-10769, 2999}, {-2074, 527}}); + TestOptimalLinearization("82159935008379b66e01830bae7202803d87690203826ea53b0302821a993303038408b52a0502864de64005028326bc7b050106827a9b050508842ace1c010f836f966204020865810a080106816ba5640703018270844306010301851f8e69050201000d8500866c0505000b8357ac7d040500098542881e040109822f8451050303048257985708010400028555bd29020e8116875e0603010211865c9f0906010408846db41b0206050786259654050503000011855ac662080300010100068655c91b020910830bb7540d0000020000000d8563da4d0e0000010000001481148d1f0e010000010012831193360a000004010a860ece440807000100068402b614060404020e8712da6e06080001028377997c070401040000018224a23c040800041e805f976e0c0201000200018655e22305060501088656a83c0801000401011e8248a140010a02002385633903010703011c80528207080302000200002780038e130c01000300000000268421330701020103010b800682590a0102020000001f863ca66005050202012b831fb146070300000202000024834c9f5d060003000100010d840dc01a040201010319857a8a2606010101000001000009824f814701040301136e876d05000202010000000c871cad4d0107002c803d884203000402008446cd4e0000010000000d00"_hex_v_u8, {{10199, 1606}, {3065, 523}, {1786, 1053}, {1089, 913}, {792, 773}, {1104, 1232}, {13868, 19409}, {-4512, 8170}, {-567, 110}}); + TestOptimalLinearization("8673c728008004810c01805e967802866fb254038536a93f048712a43d0584498d14068610c30a07822dac46088401a72f09860082450a8279a0490b6a853e0c817c991e0d815795060e8349c2490f7e8972108438b610118616b67f12807485361382058940148739c34615847ea91016805f8421178662d04718805b835019852b97491a857aaf401b832090231c810991491d802184130000000000000000000000000000000000000000000000000000000000001e855fb371010000000000000000000000000000000000000000000000000000000000198563b31d0200000000000000000000000000000000000000000000000000000000001980178c10030000000000000000000000000000000000000000000000000000000000198201867404000000000000000000000000000000000000000000000000000000000018873b86670500000000000000000000000000000000000000000000000000000000001382279e3e060000000000000000000000000000000000000000000000000000000000118442ae4d070000000000000000000000000000000000000000000000000000000000118707c3000800000000000000000000000000000000000000000000000000000000000f8533b9360900000000000000000000000000000000000000000000000000000000000e833d94160a00000000000000000000000000000000000000000000000000000000000e843381770b00000000000000000000000000000000000000000000000000000000000e811590520c00000000000000000000000000000000000000000000000000000000000d8729993f0d00000000000000000000000000000000000000000000000000000000000c82149f050e00000000000000000000000000000000000000000000000000000000000c8648c6740f00000000000000000000000000000000000000000000000000000000000c832fa20e1000000000000000000000000000000000000000000000000000000000000b860e862711000000000000000000000000000000000000000000000000000000000009842f923212000000000000000000000000000000000000000000000000000000000009815c967613000000000000000000000000000000000000000000000000000000000008824aad4714000000000000000000000000000000000000000000000000000000000008854c844715000000000000000000000000000000000000000000000000000000000008851da76b1600000000000000000000000000000000000000000000000000000000000781449e7517000000000000000000000000000000000000000000000000000000000002832e8d051800000000000000000000000000000000000000000000000000000000000000"_hex_v_u8, {{1532, 222}, {2915, 429}, {697, 126}, {3528, 696}, {4357, 912}, {4628, 1011}, {1679, 380}, {1411, 343}, {4387, 1081}, {415, 106}, {2696, 766}, {3104, 890}, {3306, 1007}, {672, 389}, {411, 244}, {296, 219}, {906, 713}, {134, 132}, {-227, 896}, {-3629, 13571}, {-188, 691}, {-356, 844}, {-500, 1083}, {-468, 910}, {-1696, 1065}, {-899, 558}, {-330, 161}, {-2614, 797}, {-3343, 867}, {-3385, 863}, {-3047, 706}, {-2051, 404}, {-2043, 324}, {-2980, 458}}); + TestOptimalLinearization("8063850a0084718e1f018627d96c028369c06503842ec7070480158a0505853fbf200683719c3e07806f906a088611df4e09816fa0570a863e94760b800485030c8307b4590d8642e7400e8459bf1d0f803461108259aa03118671ad171280708651138257907e14821986091582203b168527c013178315aa081880558e67198664c5511a872afc330000000000000000000000000000000000000000000000000000001b863b8d480100000000000000000000000000000000000000000000000000001b847f81610200000000000000000000000000000000000000000000000000001b8526852d03000000000000000000000000000000000000000000000000000018857d901b0400000000000000000000000000000000000000000000000000001884238e240500000000000000000000000000000000000000000000000000001885049c610600000000000000000000000000000000000000000000000000001880401a070000000000000000000000000000000000000000000000000000168349a2100800000000000000000000000000000000000000000000000000001581539c5f09000000000000000000000000000000000000000000000000000015810f96030a0000000000000000000000000000000000000000000000000000148406b8190b0000000000000000000000000000000000000000000000000000138005801a0c000000000000000000000000000000000000000000000000000013863c99570d000000000000000000000000000000000000000000000000000013842ea1130e000000000000000000000000000000000000000000000000000012810b8a5e0f000000000000000000000000000000000000000000000000000011861c8474100000000000000000000000000000000000000000000000000000118117903911000000000000000000000000000000000000000000000000000010850b981d12000000000000000000000000000000000000000000000000000010822d9d73130000000000000000000000000000000000000000000000000000108655c73614000000000000000000000000000000000000000000000000000010862baf0c15000000000000000000000000000000000000000000000000000010758c001600000000000000000000000000000000000000000000000000000f80739a7f1700000000000000000000000000000000000000000000000000000f8070856b1800000000000000000000000000000000000000000000000000000f837c8d4e1900000000000000000000000000000000000000000000000000000e806888691a00000000000000000000000000000000000000000000000000000b8570de661b0000000000000000000000000000000000000000000000000000098332bb0b1c0000000000000000000000000000000000000000000000000000098240ae6b1d000000000000000000000000000000000000000000000000000008861599711e000000000000000000000000000000000000000000000000000006821da71e1f0000000000000000000000000000000000000000000000000000038334af692000000000000000000000000000000000000000000000000000000200"_hex_v_u8, {{6688, 962}, {6183, 913}, {5814, 935}, {2756, 533}, {4112, 831}, {1141, 239}, {1887, 625}, {1151, 471}, {389, 227}, {1403, 958}, {-30, 416}, {-49, 180}, {-13972, 17369}, {-1102, 893}, {-1708, 956}, {-438, 240}, {-1721, 917}, {-1615, 779}, {-1905, 772}, {-629, 232}, {-2186, 686}, {-1117, 279}, {-1978, 429}, {-1474, 271}, {-3125, 564}, {-1904, 339}, {-3661, 646}, {-3062, 448}, {-3846, 562}, {-1792, 243}, {-8026, 1066}}); + TestOptimalLinearization("822f8a1500813c8e2c01812b92570280708513038608a92804835ca704058668d931068417a738078402a65b08853f9749098176994f0a81438c730b8261827e0c816c816f0d831d8a450e825a901f0f8105852600000000000000000000000000000000108338ab330100000000000000000000000000000010852dbe30020000000000000000000000000000000e82419630030000000000000000000000000000000d813c867c040000000000000000000000000000000b831b9d1a050000000000000000000000000000000b84069108060000000000000000000000000000000b854b967b070000000000000000000000000000000b8031820b080000000000000000000000000000000b81518a2e090000000000000000000000000000000b83208e010a0000000000000000000000000000000a8670d4660b0000000000000000000000000000000a860dce2a0c0000000000000000000000000000000a8451c1270d000000000000000000000000000000098543b1490e0000000000000000000000000000000883609b660f00000000000000000000000000000006843da97210000000000000000000000000000000068028897f1100000000000000000000000000000000842ab52d120000000000000000000000000000000000"_hex_v_u8, {{2562, 604}, {2588, 663}, {982, 316}, {2708, 904}, {8802, 12587}, {-198, 177}, {-961, 544}, {-1534, 843}, {-3237, 835}, {-704, 168}, {-2842, 568}, {-3479, 682}, {-4244, 721}}); + TestOptimalLinearization("8333b0280080278a4701831688680002870ecd590102866f74010581298e1203000280238a42030006648603040006850aac5b05000680048845020c8314866b000f8355b84a06000005856d814a070000058172823103020f8409b86904020c8664e05502050b824eab0501030681708f410014847dc92c090000000000018511993e0302010681548c17001a8276a87b050201000012816b933e0401030d853fa06b020604857ec0300901000000000b872dde14020700000f8252851c04000313813a8d0305000500088051816a04070000018228913a02081b8323854d002183458170050501000b8517ca10050201020015802f89210204178509c61503041682529401050102010686269150010003031a806e821c000002010204856798430100000101020e845498700104002000"_hex_v_u8, {{3156, 563}, {1594, 1161}, {3115, 10582}, {-3684, 5344}, {-423, 547}, {-4401, 3628}, {-1634, 871}}); + TestOptimalLinearization("810c8e3d008316881a01863fa11b02831dad01038049881804873d9a410203822e9f420302822299290204842d32040208874a8734020b80278a6b020b6884100408803b812505000e857ba64a0801000a874bc7760604088600aa69060980618e7a05060a8472a23a010b0b873bcf000604000e816d97620a01020f86409a730215872c8e22010c04801b8963010a18855c8b3f0a00010200000015864c98150701000401000a81428f60080005098458c254040601020d837cae5c04010a0c8273a56209050101000000058507b07b080200061080519068090003010100118310a0050503020000010000148727d92f0502020200001785599e740207000201000b8421905004010102010124854d8b0f00050003002200"_hex_v_u8, {{588, 201}, {3571, 2051}, {6111, 4552}, {5853, 5439}, {4559, 5181}, {-800, 860}, {-4457, 3265}, {-2115, 528}, {-5784, 1063}}); + TestOptimalLinearization("850c8f35008651ce2f0165807a028725d844038227a4150481378e1c058729b702068279a853076f8878088239a3380981228d300a805c85710b86559b420c8653a67a0d860eac270e823d8c0a0f82539142000000000000000000000000000000000f80068543010000000000000000000000000000000f825f8156020000000000000000000000000000000d83459166030000000000000000000000000000000b84209c0a040000000000000000000000000000000b807e9509050000000000000000000000000000000a80709452060000000000000000000000000000000a70823d070000000000000000000000000000000a8301a000080000000000000000000000000000000a8168806c090000000000000000000000000000000a8120865e0a00000000000000000000000000000009801583190b00000000000000000000000000000009857ed1480c00000000000000000000000000000006806082300d00000000000000000000000000000004801f89590e000000000000000000000000000000038103965a0f00000000000000000000000000000003801d822d10000000000000000000000000000000038459a3031100000000000000000000000000000002872eaf7f1200000000000000000000000000000002854b9658130000000000000000000000000000000100"_hex_v_u8, {{636, 111}, {5730, 1061}, {2332, 441}, {3585, 1065}, {920, 290}, {974, 311}, {2557, 979}, {837, 445}, {1825, 981}, {125, 101}, {2546, 9635}, {-215, 157}, {-269, 149}, {-223, 112}, {-3136, 1070}, {-418, 134}, {-2306, 729}, {-685, 159}, {-1413, 254}}); + TestOptimalLinearization("844f8835008631d1440183318e0a02801b896803863dc83402048525bb440304811a937e04000581669911040006872aaa100600058452b04607000480208c7005020681319777060200852dcc3c0701000b821ea3640801000481168f0808000201860c843204088715df6f0c00000000870ebd7b07030013816f9831021a82778d4200188018854a0c01000103825093730601011684298a2b0402070011832dad31030603098571cc760c00000001000000058704dd7b0b0000000000000000000b8257845005050000000000000000178630c00e04060000000001108435c159090101000000000100078474ba01050004068447ba4506030102000200028408490901030000000111850db1610a00020100000000001a8557c6570307000000011d8275ab4706000000010217648325010303188419801d04000600010012837ebd2202091785548e2f050000000103108028852702030001030383689059040300020102862aa26c010003000000002086199a08020003000000001300"_hex_v_u8, {{5282, 945}, {4698, 957}, {3874, 805}, {692, 155}, {14104, 3949}, {6151, 2252}, {345, 908}, {-589, 2663}, {-696, 870}, {-892, 981}, {-1375, 1112}, {-32657, 11825}}); + TestOptimalLinearization("824b8e1200805c8610018129801f028373a87d0380767f0486319546058649b858068623a50f07871ca14908805c9121098637af790a8668d1220b6480290c801e827d0d845eb6420e857ec3680f8600c332108517857f118126880100000000000000000000000000000000000012806b917101000000000000000000000000000000000011815e8069020000000000000000000000000000000000118468a40e0300000000000000000000000000000000000d8355ae150400000000000000000000000000000000000b832c99610500000000000000000000000000000000000a8720cb11060000000000000000000000000000000000098623b37c0700000000000000000000000000000000000880668f1b08000000000000000000000000000000000008835b9813090000000000000000000000000000000000088279892f0a000000000000000000000000000000000006802488110b0000000000000000000000000000000000038337a31a0c0000000000000000000000000000000000038574aa730d00000000000000000000000000000000000100"_hex_v_u8, {{5265, 1000}, {4404, 894}, {4377, 896}, {3553, 734}, {3692, 969}, {969, 459}, {456, 220}, {1443, 945}, {-64, 246}, {-80, 297}, {-448, 791}, {-4017, 6631}, {-664, 505}, {-577, 294}, {-1610, 603}, {-1713, 556}, {-2810, 884}, {-585, 164}, {-1038, 230}, {-4873, 1056}, {-3019, 597}, {-1209, 235}}); + TestOptimalLinearization("866db148008305944a018340810400038721c963010282528f4802028532a1260301852e954c030685778653050002668530010c806a4a020c8451aa0f030102866c8e250202000b812e9071060000000009852f875105010000000d834e98750400020003814b8b63040000020007840fa646050000020001812c891d0203021082428c7606000001000d822e875d01118502bf170701000006840a8e6c050002010a820c9b1c0204107c871f030100031682259a480401000300188676815a0107128034845b03030002138458a24205000000000013861ca528050000000001048560b41e0200000101000e80618757020200001300"_hex_v_u8, {{3236, 1005}, {3576, 1335}, {1060, 466}, {1984, 1492}, {37, 234}, {-3095, 13613}, {-556, 225}}); + TestOptimalLinearization("8260991f008662892d0001837fa91b01008401a85d0001870cad1f0101810c8658010681356a0206846b824c0400058661c47805000183058c7104028609914d06000a864fc143070004852fb7020601078132943c03040008845eb202040201048626a67f02000682358c18040004000384249049050000030781428c4c02020c8361962f030204028078873307000001010008821b986c0205010005853b8a0e070001000000000c8260956c0301040c862ca078030000010200186b825d010401000c807d930100040101821196590500000000000000088235880e020000001300"_hex_v_u8, {{1910, 8479}, {-3111, 8308}, {-1517, 401}, {-1281, 253}}); + TestOptimalLinearization("842ba05d0083138d6d018720e40a0268816b038548bc49000684269a620105840dab78030007850cc00c010009835486260200088667873a0300078065811006000103865b943f050303836db67905078749c8200700010b8742c257080001000d8351a3460500098604a67e060000108355896d0407000c8712b40005050004832e854607000400128421a30704060f8416944b020a01804e8d5207020100001081598f290209088422862b0700030306867cb04a06030100128733c8370005050e840a884d020105031b8477ba4404050100020886669d270203051886378d2e04030100020000000d8745db65020700000000198465845e0205001382059f3a07020001000000001c843ca06304060100001c854d8f670208000b81519352000004000f84598017000001000200"_hex_v_u8, {{6469, 1056}, {1595, 782}, {12744, 6989}, {-189, 1663}, {-1826, 9149}, {-470, 674}, {-615, 650}, {-8266, 5425}, {-5939, 1093}}); + TestOptimalLinearization("810e982400840a9a7e018619dc02028157a542038640a10b04844ec93a058055805f06872eb8660783718a1f08874bcc070980477a0a820194780b8345a66d0c8310a0420d8437b4660e8672af300f8225957310801a826f11807b877e128558d32c136e87581483299c7a00000000000000000000000000000000000000000015805f904d01000000000000000000000000000000000000000015831f922a020000000000000000000000000000000000000000158570be5d03000000000000000000000000000000000000000015863c88160400000000000000000000000000000000000000001480178919050000000000000000000000000000000000000000128563816406000000000000000000000000000000000000000012864bc86307000000000000000000000000000000000000000012841c9a7b0800000000000000000000000000000000000000001182029c6e0900000000000000000000000000000000000000000f8234922d0a00000000000000000000000000000000000000000d800582520b00000000000000000000000000000000000000000d8570cb7d0c00000000000000000000000000000000000000000c8471c94d0d00000000000000000000000000000000000000000c8013865b0e00000000000000000000000000000000000000000c83348a680f00000000000000000000000000000000000000000b811496721000000000000000000000000000000000000000000b850cd3641100000000000000000000000000000000000000000b8738905f1200000000000000000000000000000000000000000b6d812e1300000000000000000000000000000000000000000a7a81501400000000000000000000000000000000000000000a836ba6171500000000000000000000000000000000000000000a8736e1381600000000000000000000000000000000000000000a834a833c170000000000000000000000000000000000000000078340af00180000000000000000000000000000000000000000068460cd141900000000000000000000000000000000000000000686429d651a00000000000000000000000000000000000000000382179f3d1b0000000000000000000000000000000000000000038651b36c1c0000000000000000000000000000000000000000038405c04b1d000000000000000000000000000000000000000002874182401e0000000000000000000000000000000000000000028127827d1f00000000000000000000000000000000000000000100"_hex_v_u8, {{2465, 343}, {4765, 718}, {5953, 921}, {5398, 856}, {1618, 270}, {556, 110}, {3443, 695}, {2145, 528}, {1404, 385}, {3699, 1070}, {3096, 1010}, {1791, 650}, {575, 251}, {17859, 10290}, {151, 109}, {168, 122}, {756, 564}, {587, 956}, {286, 586}, {224, 1089}, {178, 867}, {-255, 295}, {-1136, 1080}, {-1971, 962}, {-1790, 668}, {-1239, 436}, {-494, 147}, {-2508, 619}, {-653, 151}, {-4079, 880}, {-4722, 971}, {-1127, 223}, {-2079, 407}, {-4927, 880}, {-4775, 753}, {-4198, 645}}); + TestOptimalLinearization("852dbb7700865eeb7601817ca676028220a65e000580368e660204850932030281369816030106872caf420208861bdc08020209861eba7403020880378d6703020b8253965b040209823f95360013826ab4040402048713bd3606010101817f88540009837cb26c060003000d81628e4e07000300058009811605000511817a9c10090002000005821e9379040700048656d7620500060b850e8d6803080008841a9f44040700138239a535060005010f842e81630605000114822a8f73040800000a8539b3580b010000028407c670020a0e811f0b050702058616cb1303081c8315ae6406040301001c806f9921020c16860fe23e040c038563cf050a0001040f8023842d020e16803f885c050700040182109c010206080d8242ae6702042c813ca3320209041f843ec61401030a1082659641080201020223864cbc42010e1e8442a279012980608866010b0010836c991e0106010f8637903f09010002001e820998050a0500000000108328b41c0200050a825a8d63000b0f827bb9030009108152923704020300020080298155060201020000048023802102093283758a2c000600010035805a8403030203218516c7410300010000000200"_hex_v_u8, {{6971, 990}, {2555, 380}, {2543, 416}, {1011, 182}, {9375, 2190}, {3834, 926}, {7100, 2111}, {24543, 8471}, {999, 354}, {948, 782}, {3401, 2813}, {599, 974}, {-2440, 4852}, {-946, 474}, {-9312, 3117}, {-3594, 1125}, {-1603, 393}, {-4641, 790}}); + TestOptimalLinearization("803894000087239a23018325873d02812fa0580100028315a2570200027c8a3f0300028608b21b030102870aeb0c05000283728121060000853fcb4d070000856ddb50020b802c927803030b8361c41105030a874af55b050209814d825101168319b7420600040e752f0601030e8054947c0700000d841ca56f0409817a26080501827cb2340108020c83239b340301060d8155a0020a0000000207867cea37010b06836bc00905060101000001836abc570a0300000000001680228f7d0703030000108400ac050703000002168302853b08000103000100068418c6790602010002020017865793550603001c803d84280d00000001000011850c812901168671af190a01010004000783159302060029850a6102081f8718ee2b010703218544d150040b0008835680790607020200804c8d43010f27870b9069030d00218168a83f0117857fe51c06080102001c80248c6b070604001c8526a136060308188607ad41030d010c8576ca010d03000102088545b84a050d0000002e805e934e020a0500078539a877040003092a825e977203040102348342bc010408020101000000198622e778010d000017801582190300000802822b833a07020003000000000000338702ca070108000000002c806c957307000001000100000000028350c36105000200000200002b8315a53205020000000001002000"_hex_v_u8, {{1344, 184}, {14532, 3445}, {2929, 1712}, {19, 378}, {-2610, 2705}, {-19575, 18795}, {-12674, 4996}, {-2889, 974}, {-4804, 1026}, {-1466, 236}, {-3905, 578}, {-4401, 592}}); + TestOptimalLinearization("8705ca08008231997201826b964d02872adf1b03807f994d0303861d9d0d01000584008f3502000281029c0703000283678c2804000104860ad4540500010480308a0e030580028e0d00108266a15d070000048134826d06010e8576806b0600000f82098f6a0a0000000c821e95270506088535826c060303865ca8310104147e8c3b06000e832090420600020311861d995305020202018330901f05000100148511bf1d050202030002833d92660801000200028337805606060000001380528f6206020301000007830cac650406000117813698660a00020000000000000683688f1c04020006000011814b8e63020614857592200c010000000007810f872e05080000028634ac16020c1980148e2800288309893f09050001000000028704af74012a8546a0300c010201000381658c090401288068905e0201278135956303070100010026855aab5109020200000000000016813d933d030402041d874bb051030a0000158577650501040300000009817f9c1e0205030c84419f6c0502040001000d8457d22c03030600001c8033883c0205041e80698b0e080202010000000c84058726010801278222876e0c0000000100000001871c820001080100208747e02f030100000117864585210003001084789b5a0002002200"_hex_v_u8, {{4804, 1029}, {1721, 433}, {-813, 15413}, {-1617, 12598}, {-7653, 3678}}); + TestOptimalLinearization("873b94550082209e2201835ac664028740b35703806d82630480568177056e83670683069c77078058920108855a974909871cf6520a821c912d0b820595650c8560864a0d844f86290e801790420f843280631080799b7e11812b9209128742a66c138674d41214807e281583309615168444881917844fd17c188356c12b19810681070000000000000000000000000000000000000000000000000000198240a9580100000000000000000000000000000000000000000000000000188707ea160200000000000000000000000000000000000000000000000000188471931b030000000000000000000000000000000000000000000000000014788a6d040000000000000000000000000000000000000000000000000014864a98300500000000000000000000000000000000000000000000000000128733ca0206000000000000000000000000000000000000000000000000001284099c5b07000000000000000000000000000000000000000000000000001283499319080000000000000000000000000000000000000000000000000012805487620900000000000000000000000000000000000000000000000000128051820c0a0000000000000000000000000000000000000000000000000010823f87730b00000000000000000000000000000000000000000000000000108366ae390c0000000000000000000000000000000000000000000000000010815180370d0000000000000000000000000000000000000000000000000010810692790e000000000000000000000000000000000000000000000000000c855ec0220f000000000000000000000000000000000000000000000000000c82269f2010000000000000000000000000000000000000000000000000000c824ab42811000000000000000000000000000000000000000000000000000c853f825b12000000000000000000000000000000000000000000000000000c833db84713000000000000000000000000000000000000000000000000000c81028c6214000000000000000000000000000000000000000000000000000c8272943b150000000000000000000000000000000000000000000000000008821191781600000000000000000000000000000000000000000000000000068057963317000000000000000000000000000000000000000000000000000684768176180000000000000000000000000000000000000000000000000005807182601900000000000000000000000000000000000000000000000000058309ac2a1a0000000000000000000000000000000000000000000000000005846acb461b00000000000000000000000000000000000000000000000000058564a1251c00000000000000000000000000000000000000000000000000058446a5791d00000000000000000000000000000000000000000000000000048142917a1e0000000000000000000000000000000000000000000000000004830898781f00000000000000000000000000000000000000000000000000048150a17120000000000000000000000000000000000000000000000000000100"_hex_v_u8, {{4594, 602}, {1855, 249}, {1121, 151}, {5310, 719}, {7657, 1052}, {5449, 1012}, {2001, 416}, {2550, 1090}, {18542, 18063}, {240, 241}, {198, 209}, {187, 758}, {-92, 337}, {-238, 831}, {-132, 262}, {-570, 447}, {-1294, 753}, {-1293, 585}, {-2195, 868}, {-1374, 498}, {-1902, 649}, {-2493, 710}, {-1277, 262}, {-3037, 614}, {-759, 120}, {-3684, 573}, {-2233, 336}, {-1498, 215}}); + + // Clusters which admit repeated states in SFL. + TestOptimalLinearization("862ea341008713833c018745817502827a814f0382528b6704813381280581308548010005815a8e7a0100010684019f0005000003802783720500000009856cae0005000001028601ac0e050000000475811a0601000000008135834c070000000000088007863e0800000000000284299b710900000000000200"_hex_v_u8, {{7533, 8149}, {-1849, 681}}); + TestOptimalLinearization("82759235008069896c0186438e3d028244912600000003851ba405010000038214845c0200000183018a4b03000001807f841f00000000078573823a0100000007820f8a65020000000164805703000000018622ab5804000000018412936600000000000c82749c04010000000008832b977602000000000700"_hex_v_u8, {{694, 233}, {2856, 7908}}); + TestOptimalLinearization("85478e3b00823c904101820c893f0283179c5903814c893104854282530583429f140100000103830d8509010001000284409a70020100000181168c190300000001088718b06a0400000001058553ac2c0500000000000b8506a22e060000000000008552803a0000000008800f861e020000010008817c844802000100010700"_hex_v_u8, {{6613, 7335}, {-322, 2176}}); + TestOptimalLinearization("8146845100804b890d0186768b55028406a32f03863ca23104832f8e3b05810a892b0680358253078502ac5d08872d96700983718004000100000002018273952801010001000100097b3c02020000000100058562a14203010000000000010d8718817804000000010000000c862f956c050000000000000000000900"_hex_v_u8, {{1528, 1069}, {-5842, 9025}}); + TestOptimalLinearization("8460992400872dbd1901867fb07f02852a817d00000003852d87540100000181348f60020000018435942b03000000837f8943000000000681608e730100000005857da67f0200000005843794080300000004658217040000000086628a1500000000000b8708805401000000000a847fa32402000000000700"_hex_v_u8, {{1682, 736}, {-8507, 10189}}); + TestOptimalLinearization("81668e3500804e844f018435927302861f9d5f03846ea342010000018261897a010000018165831102000100863e895003000000078232985e04000000018606823f000001000986318a570200000009804300020100000981408323040000000885179a260400000000068510af7800000000000b8360966f01000000000000"_hex_v_u8, {{4144, 9101}, {-1528, 608}}); + TestOptimalLinearization("861e862e00851aa57f018060816b02831ba009038628b818048576b3010580072a068478911d07854fa8600003000007816d86560200000000048576b11403000000010009850aa7100300000100010782178f040400010000010280758a22050100000000000d860eb344060000000000010c8501975a070000000001000500"_hex_v_u8, {{3660, 936}, {6832, 9471}}); + TestOptimalLinearization("802e7300824b95370000821c8a5e0002827a89580102813185560201867ba17f0301810e83580000000000835d9a0c010000000087218c1602000000008458905a03000000008319937d00000000068216900801000000068677ab2a0200000005826f91710300000004864f4404000000038702ac3c00000000000900"_hex_v_u8, {{6067, 6956}, {392, 3033}}); + TestOptimalLinearization("8151801500823e835201842ba81902837b881e038674b461048676897a058369894306873e8511078514a802010001010001833a8f51010001000100078236885502000101000003872cab780300000101000381035605000000000000038123825305010000000000018152847106000000000000000100"_hex_v_u8, {{5948, 5991}, {-8330, 3591}}); + TestOptimalLinearization("6f845f008472960a01842084330286779f7903800786310481588d0505865994390683449e480100000000000387218e3a02000000000000811d22020000000100008500812a0300000000000003825e970e04000000000000028436815a05000000000000027c812e060000000000000000"_hex_v_u8, {{1477, 754}, {-526, 7248}}); + TestOptimalLinearization("83419a7b00860e9172000183289a1901018157824f020183209e53030085398e5a040081458e2d00010000068334a04c010000000005840d8f1d02000000000281398b70030000000000872f8f5a040000000000816a8070010000000b843ba5140100000109823a91680200000000078267903303000000000700"_hex_v_u8, {{2212, 8180}, {-1114, 487}}); + TestOptimalLinearization("867e816800827e9a7101811c8f470000018613905501000080708c0e020000865c963a030000873a834e0400008267921900000000000486238a5a0100000000028614914a020000000002823083560300000000008652993e040000000000813c8e03010000000a803c817601010000028507990402000000000a00"_hex_v_u8, {{3395, 9748}, {-962, 316}}); + TestOptimalLinearization("856d9d530083029d1d0186429d3f02866b927b038234947a000201835389400101028460895c0201000582128f5303000100803183290400000184358f790500010082779924060000000682416d0700000006867b9a4003000000000a83149b200100010100000482488f62020100000000000b00"_hex_v_u8, {{-524, 9354}}); + TestOptimalLinearization("8748865d0082558d3d01825d8f7a0281678623000003815e8b750100000277831e02000001873d8f3a03000000805b841f0001000682548f0b010000018376891e0200000007826d8320030000000482288b71040000000282078b0c000000000d82288e26010000000d872994040200010009800e800a03000000000b00"_hex_v_u8, {{1085, 477}, {513, 7734}}); + TestOptimalLinearization("817b8f1100832c965701811b8a4400000281418f16010002804f86370200028573941d0300028179820f04000280238743000000000006845291470100000000037f826c0200000000018602a563030000000001850d902000000000058652826301000000058673a03602000000028379970d030000000200"_hex_v_u8, {{-3780, 7686}, {-1543, 633}}); + TestOptimalLinearization("85179c690081659269018635831a02866289680385438d3704867eae70058726b035068009856307816e926001000100048742b83801000000010a822190360201000001098721b05603000000000108861db2580401000001000080758a30050000010000000d8354a334060100000000000d8629a87c070000010000000a00"_hex_v_u8, {{3064, 1022}, {11548, 10758}}); + TestOptimalLinearization("813d894b008263966701802d827f028375997a00000003861891180100000387219a68020000018462827503000001821a924504000001833a9d16000000000003810e8d2d01000000000284299632020000000001820993160300000000008656b4780000000009845c8f03010000000300"_hex_v_u8, {{7961, 7623}, {-1026, 732}}); + TestOptimalLinearization("86639b0b0084139655018156830f000002872895040100028567862f02000280468504030001830c8427040000870b987900000000000680527801000000000280148635020000000001860c952a0300000000018216867600000000098426a04c010000000481658d55020000000100"_hex_v_u8, {{-583, 8038}, {-939, 357}}); + TestOptimalLinearization("8718a9310084369903018666a2270000018207936e0100018470800102000084609c240300008324985c04000086418d2100000000000285708d7c0100000000028362875802000000000182519504030000000001854fa01a000000000b851fa3120100000008834d8b47020000000300"_hex_v_u8, {{4600, 9729}, {-804, 589}}); + + // Clusters which have lots of potential SFL states/transitions. + TestOptimalLinearization("84559211008539851b01825098430285268d1e00010181008e700100028425a22e02000005860a9c1403000005830b924004000005810d87040500000483768808060000038731854007000003862b9066000001010a83799a540100020104850c9a5702010000020482098c0003000002000002826897580400010000010000"_hex_v_u8, {{10878, 9607}, {-1772, 780}}); + TestOptimalLinearization("8657833900865d887e018043894b0287338377038624880100010001851fa82401000000038421990e02000000038723af560300000003866dae0a04000000028265860005000000028031863800000001088353a12c0101000000088630b51a02000000000003866ba1650300000000000300"_hex_v_u8, {{15815, 9911}, {-2227, 1003}}); + TestOptimalLinearization("852a9d7d008350920901825193330280691201000181428c25010001823d8d1f020000018355460300000182448c0f040000008608994d05000000843ea14a01000100078511a07001000200058737ad5802000200058569ae3f04010000026e80520401000000058509ac7605000000010400"_hex_v_u8, {{1641, 8273}, {-3040, 873}}); + TestOptimalLinearization("803a88570078825101806a88390282048777038430920a000003811b841f010000038003801c0200000006822092420300000002821a96380400000001820280110500000001806d8834000001000006871aaf2b01000000000003806a8a2a02000000000001864c9e600300000000000100"_hex_v_u8, {{4968, 4685}, {-3094, 1050}}); + TestOptimalLinearization("8437801e00845d833f01816b907800000286438821010002867cac720200028202895e030000861db23d00000000068419a47301000000048404966b0200000002812a817c000000058438824b01000004822c980e0200000286168f4c03000002860c8a5b040000008542997a00000000000a8247815101000000000800"_hex_v_u8, {{4563, 3197}, {-4405, 7734}}); + TestOptimalLinearization("8556877f0081658a7b01803865028412813e03810e816d048743b56e0581398d070680378048010100000001812e85280100000100000780018226020001000000078370891403000000000100824a994a05000000000000822c835a050000000000000980608376060000000000000100"_hex_v_u8, {{3511, 1091}, {1365, 4984}}); + TestOptimalLinearization("66844f008651aa61018409810902816f814d03823084080000000004843b8144010000000483528d16020000000082758c5403000000008342927f000000000886780c01000000088656802302000000088637b649000004855f8f4001000009815a8c5002000009814b8d240300000900"_hex_v_u8, {{292, 8437}, {-3557, 951}}); + TestOptimalLinearization("825d842900824a800401864bb0360281598d13038039835f04834a80310585568663068555862e00000000000000078552935e010000000000000785738c5c02000000000000058163864403000000000000058069837e040000000000000480708610050000000000000000"_hex_v_u8, {{3163, 971}, {1861, 6319}}); + TestOptimalLinearization("83249365008149926b01817a925202874bba1f038631ab2b0484788e55058613992d06857aa671078367883600000100000001844f916e010100010000008179867002000100000100847a90600300000000000007842c902a0500000000000004825e856b0500000000000000068016831206000000000000000200"_hex_v_u8, {{1257, 378}, {-10088, 9271}}); + TestOptimalLinearization("802488410085669221018528aa5b02800a8539038278921601000382338719010103802b8744030102842b8404050002857b886a040102864d9317050100088267920006000001806b813407000100863a5d080000000182529b23000000010000010884259254010000010100000781739540030000000000000600"_hex_v_u8, {{-125, 8361}, {-1810, 466}}); + TestOptimalLinearization("81528b45008715a71201834c8c090102802f85320202866a89320301812985680001068120817301010005837a836a020000000581448a060300000005846e6b0400000001814c9355000100000582758c5a0101000002833b872d02000000000884539c000300000000068459a140000000000b83098f5a010000000300"_hex_v_u8, {{2569, 1045}, {4798, 7773}}); + TestOptimalLinearization("8635a94d00866f9a5901847ea66802821c81640203856694370101018713b00d030001813b8b3603000000811980480400000086108960000100000881068b4301000001028033866202000001008556b22f030000000003835ba028040000000001815789540100000006823d903e01010000058629b01202000000000500"_hex_v_u8, {{2548, 766}, {-3769, 9412}}); + TestOptimalLinearization("81188d5500822c855b01821c3a02863a9117038425930d048207906f057243068235804f07853f8b16000002000108822a8a5601000100010880738e07020001000008850d8c4605010000000681038c2205010000010681628b4a050001000007840a8d440600000002000a8538986a070000000001000000"_hex_v_u8, {{1557, 7818}, {-964, 243}}); + TestOptimalLinearization("8522934b00812a8b15018610b44e0285789a1f0386509173048233901705840f914906857a924f07831d8e58020000000107860b806502000000010380328724030200000000824b5f0301010000000181518964040000010000000c816e8b24060000000000000b853f45070000000000000400"_hex_v_u8, {{3431, 912}, {-5784, 8563}}); + TestOptimalLinearization("832f8f59008730831a018022822b02801239038275987b048520a0590582448b6506826398500001010007854b971a0100020000088105894c0201000001068737992603000101000387249424040001010003815b82220500000001028706934a0600000100000d8602ae3d0701000000000c844789560801000000000100"_hex_v_u8, {{3440, 9506}, {-3039, 898}}); + TestOptimalLinearization("8458a65900850aac29018201855f02854fab75038461877504821e86330584328d3706861b8d7107844e9d2808847c9250000000030004836d8d3e040000000000098179916402020000010005825b95700400000100000000867dae5e04000000000001000a8162941e05000000000100000682628a4e06000000010000000600"_hex_v_u8, {{1940, 718}, {-1665, 9596}}); + TestOptimalLinearization("8147814100805389410185059d29028045833103850834048073883f05803580330000000106832b9f6c0101000105821c0b020000000000068519a8600300000000000484759a66010000018339a07201000001854ea048020000018730ba160400000086409d3c040000000c00"_hex_v_u8, {{12873, 8672}}); + TestOptimalLinearization("8219866f008118842f01854ba30b0283058a1f038614921b04866a807005840f9b140000000000000584339d1601000000000004834d925402000000000004811d8b3403000000000003844f8552040000000000038217821e0500000000000100"_hex_v_u8, {{1429, 7313}}); + TestOptimalLinearization("8177892100831b9b490181418f05027d803603872c8040048438837805852ea30200000000000006871e956c01000000000005824c9036020000000000018471972203000000000001865d8e28040000000000008566a4100500000000000000"_hex_v_u8, {{6781, 8064}}); + TestOptimalLinearization("8534a80f008374901f018379995e000002807a8c4201000285438d340200028019856403000282178b22040001830b930605000080608b06000000000000048621ad3601000000000001842ba10a02000000000000825494320300000000000000"_hex_v_u8, {{9556, 6553}}); + TestOptimalLinearization("81398a1700805f84750183639f61028032835f0387498f1b048526992b0581688f4e0000000000000480028348010000000000038005821c02000000000003864fad6803000000000003816c9156040000000000038715b5320500000000000000"_hex_v_u8, {{3013, 6235}}); + TestOptimalLinearization("83799c23008671ad4901815c896b02854fab3303810481680000000004803181130100000003845f8743020000000383258862030000000381658f6d0400000002802384590000000000058318876701000000000380758d020000038618951201000366812a02000200"_hex_v_u8, {{-7815, 6881}}); + TestOptimalLinearization("8225945300815931018413962f02836a9635038720b46d04842b840f058659974a0000000000000587179118010000000000058710a5540200000000000187169242030000000000018620ac6c04000000000001844182700500000000000000"_hex_v_u8, {{1461, 9533}}); + TestOptimalLinearization("806e801500871ca421018125845d02841a9c3703845da477048528997d058510a820000000000000068745946e01000000000004840f9b1202000000000004822d8e280300000000000484599f0004000000000004807d81050500000000000200"_hex_v_u8, {{2, 7480}, {-131, 253}}); + TestOptimalLinearization("84098a0d008000811f01812f831902867a843a038124890f04821e821905870b9e4400000000000004840aa00601000000000002852e97020200000000000284769b18030000000000018042847604000000000001853290160500000000000100"_hex_v_u8, {{7324, 7073}}); + TestOptimalLinearization("834a9b5f00820a89790180598b3f0273815703816e807004805683370580308062068417a42c0000000000000002865098610100000000000000831a93590200000000000000801981000000000a856eb5520100000786409000020000058336903803000005836b9a020400000583599c0c0500000500"_hex_v_u8, {{5272, 8022}}); + TestOptimalLinearization("85268b4c0082748120018068802102807986310381428d770480268659058340994e00000000000004806b8a3e01000000000004811f8c3c02000000000002840a8d6c030000000000018608af5604000000000001822492100500000000000000"_hex_v_u8, {{7536, 5347}}); + TestOptimalLinearization("83718f5300862d9663018700ae190281338d3303825c865104832c916705851b9c64000000000000068419a256010000000000028510a35802000000000001816b861a030000000000018564ad6804000000000001832e85480500000000000100"_hex_v_u8, {{2190, 7962}}); + TestOptimalLinearization("842a980f008356951f01805c82570282308f4303802d8317048415a13705863e51000000000000058530a15201000000000004827c865402000000000004845e93260300000000000281638e68040000000000028121832e0500000000000100"_hex_v_u8, {{-1542, 6426}}); + TestOptimalLinearization("855f8024008024816501807c81020284629b030383078631048044895c000002048078100100000000068315856a020000000004800784130300000000028167800c04000000000281758a14010000000885249a1a0100010004852ca63e020000010300"_hex_v_u8, {{3618, 5996}}); + TestOptimalLinearization("855f97230080678a4f018210925d02856113038105892b04854e996105805a8a480000000000000283028b7a01000000000002846d9f080200000000000283189656030000000000018277862e0400000000000185508d200500000000000000"_hex_v_u8, {{573, 6834}}); + TestOptimalLinearization("866faa23008646b71501851f96130282588f1103804d851c0000000003822097600100000003850a8f310200000003856e9201000000038656935f01000003855b8f5f020000018743923a0000000a812b810b010000098403a328020000068712970203000005833e98400400000200"_hex_v_u8, {{-4960, 11011}}); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util/cluster_linearize.h b/src/test/util/cluster_linearize.h index c79f414a0e4..220b5fe3f84 100644 --- a/src/test/util/cluster_linearize.h +++ b/src/test/util/cluster_linearize.h @@ -331,7 +331,7 @@ void SanityCheck(const DepGraph& depgraph) VectorWriter writer(ser, 0); writer << Using(depgraph); SpanReader reader(ser); - DepGraph decoded_depgraph; + DepGraph decoded_depgraph; reader >> Using(decoded_depgraph); assert(depgraph == decoded_depgraph); assert(reader.empty()); @@ -384,12 +384,12 @@ void SanityCheck(const DepGraph& depgraph, std::span Date: Sat, 17 May 2025 19:08:25 -0400 Subject: [PATCH 02/11] clusterlin: replace benchmarks with SFL-hard ones (bench) This also adds a per-cost variant of each. --- src/bench/cluster_linearize.cpp | 192 ++++++++++++++------------------ src/cluster_linearize.h | 9 ++ 2 files changed, 93 insertions(+), 108 deletions(-) diff --git a/src/bench/cluster_linearize.cpp b/src/bench/cluster_linearize.cpp index 1cd36b53939..a56566c19f0 100644 --- a/src/bench/cluster_linearize.cpp +++ b/src/bench/cluster_linearize.cpp @@ -210,40 +210,48 @@ void BenchMergeLinearizationsWorstCase(DepGraphIndex ntx, benchmark::Bench& benc }); } -template -void BenchLinearizeOptimally(benchmark::Bench& bench, const std::array& serialized) +void BenchLinearizeOptimallyTotal(benchmark::Bench& bench, const std::string& name, const std::vector>& serializeds) { - // Determine how many transactions the serialized cluster has. - DepGraphIndex num_tx{0}; - { + for (const auto& serialized : serializeds) { SpanReader reader{serialized}; - DepGraph> depgraph; + DepGraph> depgraph; reader >> Using(depgraph); - num_tx = depgraph.TxCount(); - assert(num_tx < 128); - } + auto bench_name = strprintf("%s_%utx_%udep", name, depgraph.TxCount(), depgraph.CountDependencies()); - SpanReader reader{serialized}; - auto runner_fn = [&]() noexcept { - DepGraph depgraph; - reader >> Using(depgraph); + // Benchmark the total time to optimal. uint64_t rng_seed = 0; - bench.run([&] { + bench.name(bench_name).run([&] { auto [_lin, optimal, _cost] = Linearize(depgraph, /*max_iterations=*/10000000, rng_seed++); assert(optimal); }); - }; + } +} - if (num_tx <= 32) { - runner_fn.template operator()>(); - } else if (num_tx <= 64) { - runner_fn.template operator()>(); - } else if (num_tx <= 96) { - runner_fn.template operator()>(); - } else if (num_tx <= 128) { - runner_fn.template operator()>(); - } else { - assert(false); +void BenchLinearizeOptimallyPerCost(benchmark::Bench& bench, const std::string& name, const std::vector>& serializeds) +{ + for (const auto& serialized : serializeds) { + SpanReader reader{serialized}; + DepGraph> depgraph; + reader >> Using(depgraph); + auto bench_name = strprintf("%s_%utx_%udep", name, depgraph.TxCount(), depgraph.CountDependencies()); + + // Determine the cost of 100 rng_seeds. + uint64_t total_cost = 0; + for (uint64_t iter = 0; iter < 100; ++iter) { + auto [_lin, optimal, cost] = Linearize(depgraph, /*max_iterations=*/10000000, /*rng_seed=*/iter); + total_cost += cost; + } + + // Benchmark the time per cost. + bench.name(bench_name).unit("cost").batch(total_cost).run([&] { + uint64_t recompute_cost = 0; + for (uint64_t iter = 0; iter < 100; ++iter) { + auto [_lin, optimal, cost] = Linearize(depgraph, /*max_iterations=*/10000000, /*rng_seed=*/iter); + assert(optimal); + recompute_cost += cost; + } + assert(total_cost == recompute_cost); + }); } } @@ -290,71 +298,57 @@ static void MergeLinearizations64TxWorstCase(benchmark::Bench& bench) { BenchMer static void MergeLinearizations75TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(75, bench); } static void MergeLinearizations99TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(99, bench); } -// The following example clusters were constructed by replaying historical mempool activity, and -// selecting for ones that take many iterations (after the introduction of some but not all -// linearization algorithm optimizations). +// Constructed from replayed historical mempool activity, selecting for clusters that are slow +// to linearize from scratch, with increasing number of transactions (9 to 63). +static const std::vector> CLUSTERS_HISTORICAL = { + "8540e93a008331c66a018331c66a028331c66a038331c66a048331c66a058331c66a068f1b84a1060000000000000007995e888c04010000000000000000"_hex_v_u8, + "8e4886a46000835c819a100000835d819c000100835f819c000200837581a7200300835f819c000400835c819a1005008360819c000600836e81a3400700835f819c0008009c728d813000000000000000000000854e88df0600008e48a9c5700100835f8bfd40000183608bfd40010100"_hex_v_u8, + "836880bf4000834c80b6600000836880bf400100834c80b6600001836880bf400101834c80b6600002836880bf400102834c80b6600003836880bf400103834c80b6600004836880bf400104834c80b6600005836880bf400105836880bf400006834c80b6600106836880bf400106834c80b6600206836880bf400107834c80b6600207823c80bf400108834c80b660020800"_hex_v_u8, + "81b0708db03800ef7a84a6700180c57b87c87802f24f83fc7e0381a3688ce20a04cb0d82a27205b40481887e068331965207d84384a07a0880850887b31609d57a83b62a09098865c82e0707c84083a736060009932d8098240a00058c1edf48070503f55a85fc560803058b6ddf660e0002e67d858d68090107921a81a1560d0302857cb32206148235986406149a5680e67c0b000106854cc12001118578e228001281cf6499b350120000000000108578e22801128578e228001300"_hex_v_u8, + "80df2a87e05c00f62883d34801f23983c02c02808e0784ca280380c35b86d64c0480956584f1080580a91b85d20c06808c4b84c27c0780b473868c4408f54983cf7c0980aa5385d9380a80b00285f4140b80804f8487100c8099308582700d80b3138683640efe1183fa640fa734849e28000000000000000000000000000000000fc82788ce48010000000000000000000000000000000fa92b84be300200000000000000000000000000000000ab3b84e1460300000000000000000000000000000000833dc93c0102833dc93c02028331c14402038540e12603038431cc3405028335c50e030a8331c7760506930880af020307833dc07605068331be0205068335be3a05078d09fb76030d842dcb6e030d00"_hex_v_u8, + "8368903600836890360183689036028368903603836890360483689036058368903606836890360783689036088368903609836890360a836890360b836890360c836890361b836890360e836890360f83689036228368903611836890361283689036138359900a13158359900a13158359900a13158359900a13158359900a13158359900a13158359900a13158359900a13158359900a13158359900a13158359900a13158359900a13158359900a12148359900a12148359900a11138359900a11138359900a11138359900a1113a42281a91012000000000000000000000000000000000000002600"_hex_v_u8, + "83689220008734a05001836892200283689220038368922004854e99400583689220068368922007836892200406836892200306836892200207854e994009000983689220030c854e9940080107836892200309854e99400b0101836892200416854e9940070209854e994005060c854e9940020c0e854e9940030804854e9940030706891aa770070207854e9940040413854e994006020d854e99400311836892200310836892200217836892200113854e994003070d854e9940020410854e994002020d8734a05006010b854e9940050612854e994002128734a0500014854e9940050015836892200114854e994001090e854e99400507148734a050020b058734a0500210836892200222854e994004090e854e9940041200"_hex_v_u8, + "db3284df6200e45386c550018235a906028235a906038236a906048235a906058600da0005058578d94c06058236a104070582429e10080582429b040905831d982e0a05854181e1040b058f1880f2240c058235a5040d058236a9060e05823581c3080f058235a90610058235a9061105857c80804012058235ab0613058235a90613058236a90614058236990215058474ca6e16058470ca6e17058a5480df0418058470ca6e19058316a43e1b04847c8088701b0586088086481d048236af061d058235a9061e058235a9061f058235b10620058236b10621058236b10622058236b1062305847080870224058546b34425058234b30626058235838f3227058242a504212a8241a9061b2d824181be76192d875282ad702b00168242ab061929845085af4c2d000f824181e448102b860480885c25168d0884c76e1b100000000100"_hex_v_u8, + "8514fd1a00851480d02801903183803e028368e154038368e154048368e63c058368e63c068368e154078e4b82da4e08903181ec440983688187580a83688185240b83688185240c903181db100d8e4b8391180c0d8e4b84b4760c078361e566090b8361e5660b0a8363e1020d098363e1020f088363e1020c0c8e4b81be5614058e4b81dd6e0c0d835f8184200c0d835f8181700c0d835f8181700c0d8c6686a8100b24d63b93ea661205128e4b8391180d1d937d82d740071d8e4b81dd6e071c90318dce1604338e4b848d78042c8e4b82e234042c8d1e83f106052b8e4b81dd6e05258e4b85a9700333921785880c03328e4482e03c04308e4b8687380237920f85ab68020317178e4b86dd1a001c16d77ed4c56c020e0700000000068e4b8687380100318e4b868f1e00328e4b86ae3600328f2385f62a01181a836880fe100132845a81ab1e0033836881a93001338e438ffd30031c836881a9300135835f81a5400235835f81a5400137921790ba2c023697498dfd280800180019903189f81a003900"_hex_v_u8, + "836280c06000836180c06001836280c06002836180c06003834280b1600303836180c0600403834280b1600403836180c0600503834280b1600503836280c0600603834280b1600603877e81fc20060c833a80b160060c836280c060070c877f81fc20070b834180b160070b877e81fc20070a836280c060070a834180b160080a880381fc200809836180c060081a834280b1600719836280c0600716836180c0600614834380b1600612836180c0600511834180b1600527834380b160041f834280b160041c836180c0600617836280c0600618834080b1600516877f81fc200531880281f960140414836280c060041e836280c060041f834280b160051f834580b160071e836280c060053b834280b1600422834880b1600423836180c0600622834280b1600343836180c0600443880281fc200f091d833e80b1600328834280b1600249836280c0600345877f81fc20140b23833b80b1600149877e81fc20020345877f81fc2011081f877e81fd400e0a12880281fb00110316877e81fc2003024a877f81fc20061b1c877f81fc200e0420877f81fc20040c1d877e81fc20021e20877f81fc20041d1f877e81fd40030e1c877f81fb00030a20877e81f9600c033200"_hex_v_u8 +}; -/* 2023-05-05T23:12:21Z 71, 521780, 543141,*/ -static constexpr auto BENCH_EXAMPLE_00 = "801081a5360092239efc6201810982ab58029b6b98c86803800eed7804800ecb7e058f2f878778068030d43407853e81902a08962a81d176098010b6620a8010b2280b8010da3a0c9f069da9580d800db11e0e9d719ad37a0f967897ed5210990e99fc0e11812c81982012804685823e0f0a893982b6040a10804682c146110a6e80db5c120a8010819806130a8079858f0c140a8054829a120c12803483a1760c116f81843c0d11718189000e11800d81ac2c0f11800d81e50e10117181c77c1111822e87f2601012815983d17211127180f2121212811584a21e1312800e80d1781412813c83e81815126f80ef5016126f80ff6c16126f80f66017126e80fd541812800d81942a1912800e80dd781a12800d81f96c1b12805282e7581b127180fd721c1271a918230b805fc11a220d8118a15a2d036f80e5002011817684d8241e346f80e1181c37805082fc04260024800d81f8621734803382b354270b12805182ca2e162f800e80d52e0d32803dc360201b850e818c400b318c49808a5a290210805181d65823142a800d81a34e0850800e81fb3c0851886994fc0a280b00082c805482d208032e28805e83ba380059801081cd4a0159811884f770002e0015e17280e49024300a0000000000000031803dcb48014200"_hex_u8; -/* 2023-12-06T09:30:01Z 81, 141675, 647053,*/ -static constexpr auto BENCH_EXAMPLE_01 = "b348f1fc4000f365818a9e2c01b44cf7ca0002b004f0b02003b33ef8ae3004b334f9e87005800d81c85e06b368fae26007b05ef2e14208be1a8093a50409b15cf5ee500a802c80a1420b802dea440c802ce50a0d802cdc320e802cd7220f802dd72210805380f74a118174f370126e96b32812127182c4701312817389d26414128035848c221512800e82bf3816126f81e4341712801082b228181280518af57418128040859a0019127182d0401a12803e858b641b127182c4421c126f82b3481d12811486b6301e12821d89e7281f126e8a8b421f127182d6642012806284c12021126e81d34822126e86a76222126e86d8102212805187b6542312800d82fc002412803d848e0e2512801082d27a26126e8589642612800e83a9602712800e83bd0028126e81ef1a29116e858d7228126f82db5e2912801083843c2a127181c93c2b126e85d0162b127181c5622c126e84f8262c12800f8392202d12800e82b66c2e126e81d0082f12803282d50430126e84f9003012805f84be6c3112846e88df0e2b12804080d44c340a8b31898808350a800ed760350b801083a1182b517182817e2a51800e82b6582951803583cb52420030806284cb6c204f7181d300204f82688ce0303e001d800e82bb200f488010808a182822a3289cd63041000a6fcd100a408a7caaa7024800002f803584e0741e27288f3386dd783b001000802683f27e004b8c44bcd0763f0000000000000000000100000e00"_hex_u8; -/* 2023-04-04T00:26:50Z 90, 99930, 529375,*/ -static constexpr auto BENCH_EXAMPLE_02 = "815b80b61e00800da63001cd378da70e028010991a03800e9d3e0480109708058010991a068010973a07da738fa72408de7491831009b35b88f0080a9d4485de180b71974e0c71974e0d80108e500eb27988a75a0f719632108061a56c11801087761280108a1413807893441480538c1415a606828806168010893e1780548c40188e4b80bb2c196eab3e1718805ed60e18188051c97a19188010cf781a1871b11e1b1871c5281c1880508080581d186e80b13c1e188035cf421f18805fe0482018804caa661f198035a9001f156e80cb701d1871a2281e1871ad281f18817380a16020186f98642118805ee04821198010b6702219800ea12623196eb67024198035808b0025196fa65c26198054ba1c2719807680bf7c28198053cd782919803d80b80429198051db5a2a198040d3742b19976584bb1c28196efc1c281971b21a29198052bc762a1971a2502b196eb73c2c19976381ab0c2a18806290543409862081c3423b00336fbc70224d80109e7c1c52805ebd5c1942800eb57016468034ba423405158118da28350416927480f4743000159f6a81c9462e00188051ec5e380e00800e9e420775800d9e26007c906c82f754251d0025870480f12c14280023800d9e26027e9e1385ed08102900001a804fac7a018001719856028001800da87e0180039b1a868b60064102246e9f42018005800da87e028005850d81d600026d862381a2200e0008230015831480a5480342000524803eeb32006e873582a4700a0100351300"_hex_u8; -/* 2023-05-08T15:51:59Z 87, 76869, 505222,*/ -static constexpr auto BENCH_EXAMPLE_03 = "c040b9e15a00b10eac842601805f85931802c104bae17403ae50aaa336049d76a9bf7005c55bbeab6606ae2aa9c72c07805e81992e08af7dab817a096e80a7e4520909803e92bd780a097185c76c0b096e98e7380b09850bb9953c0c09803389f6260d096f859d620e09803f88d3000f0971829c6e1009837690f6481109806285931811097181f56814076ea09b74120980408eb73213096f87853214096f86e2701509803f8c860016098a6fe6c3721709814f92a204180980628a8a441909803285df681a0980348498661b096e8290781c096e978e081c097187da1a1d097186c05c1e097185893c1f09805f8ad9002009800d84e74e21097183a67a22097182e23423097184b53a23096ea393062309840faddd46240980618eb732250980548bee6a2609807986883c2709718298402809815388b6582909805384ec742a097181b9142b096e97b5262b096e85e14e2c0980518abb5c2d09805489e75a2e09803187e3382f097180eb1c34046f87c34a2f098309a5c54430097186911831098054899c083209801083bc1033097081e02a3409805f848f0c35096e80d4343a057180c37040006f80a22438097180a0503f03816f8381444003803f80ef003f05800580a4283f066ef72845016efb91663e09923d808d8216470041803584837c46012f9247dc86684501268267a09610450222862184db68440712803585ea40440113835d97887805800b8723c7a40a4b00022f81529ae2143c0c1f80548b8f381b311980408e955c055e802589dc10037e801083b54602658010848130006700"_hex_u8; -/* 2023-05-01T19:32:10Z 35, 55747, 504128,*/ -static constexpr auto BENCH_EXAMPLE_04 = "801af95c00801af72801801af95c02873e85f2180202873e85f2180202873e85f21802028018fb2802068018fb2803068018fb2804068018fb2805068018fb2806068018fb2807068018fb2808068018fb2809068018fb280a068018fb280a058018fb280b058018fb280c058018fb280d058018fb280e058018fb280f058018fb2810058018fb2811058018fb2812058018fb2813058018fb2814058018fb2815058018fb2815048018fb2816048018fb2817048018fb2818048018fb2819048018fb281a048018fb281b04810d80d9481f00000100"_hex_u8; -/* 2023-02-27T17:06:38Z 60, 55680, 502749,*/ -static constexpr auto BENCH_EXAMPLE_05 = "b5108ab56600b26d89f85601b07383b01602b22683c96003b34a83d82e04b12f83b53a05b20e83c75a066e80840a06068040be0007066fb10608066fb2120906800eba320a06842b80b05a0a066eff420b067199300b068124c3140c0680618085180d066faa1c0e068010b4440f068051af541006800da1781106857881946812066eee1613068052b31014068324808d361506806180885c150671b03216066ef11017068052b63218066ef3521806803f80865419066e93441a068035a13e1b0680628085181c06806ec4481d068117e72c1e06719c721f068077c42420068159808d1821066eef0c21058010b90022056f9908230571993024058010b00a25058010b00a260580608087402705803fc10027068032b42828068051b6322906800db11e212a8324808d361933803ff400192f826381a7141a2f8032ac08152a800db54c044e8323808d3630010002018158d84000042d821cea12002807853580d462002d01891181d022002e00"_hex_u8; -/* 2023-04-20T22:25:49Z 99, 49100, 578622,*/ -static constexpr auto BENCH_EXAMPLE_06 = "bf3c87c14c008010955a01b21d85e07002800d946c036e8e3404b77f86c26605b33c85f55e06bd06879852078010970a08bd4b87cf00098123a7720ab2158687680b8054d4440b0a8062fa4c0c0a71ac400d0a80628081540e0a8010a2580f0a8054b676100a8032b85c110a6e9a40120a6e809012130a817f80c31e140a8175808674150a719d46160a8172d86415098033c1481609800da4181709800ada2e1809803dc85219098034b4041a096ef5501b098052d67c1c098051d3281d09800ebc4a1e098175808c641f098061c55020098078c85021096e8081141f0b6faf1e200b8061da68210b8062f000220b800ebc20230b8035d058240b8053de32250b8050b610250b6fad32260b803dc276270b803d80a610280b6ef812290b8052b6322a0b800eb57e2b0b8052bd062c0b719e522d0b71a3762e0b8010bb1e2f0b80109a78310a80109962320a8051a60c330a6f9f3e320b6e808b24330b719e40340b8117cc50350b803d80971a360b8051b930370b6f9e0a380b719b10390b8052a6003a0b6e808c76390a7195603a0a6f935c3b0a8054a31a3c0a803ce30c3b0b803fa3003c0b800dbe2a3d0b8f3480a84244058005851a44069d1bf824400b83098f284507719c723d4f6f9c1c3449719c722f4f6eb23c304f8061c5502e528061da682b4e8118bb724e022a8054b35028476e941c1d51815be02c4f01148557808e3a4f070e8104af464e001180329d364e010d805f9f6a421b9c3387aa744c0d4d71ac400b800881748098444710338173809b780b80008054d444292c12821dc040550403078b4682b4664517003f00"_hex_u8; -/* 2023-06-05T19:56:12Z 52, 44896, 540514,*/ -static constexpr auto BENCH_EXAMPLE_07 = "b317998a4000b40098d53e01b45b99814802b7289b940003b3699a9d1204b6619a807a05814682cb78050571d854060571d8540705800e808d7a0805803480c06a09056e8189280a056ffd060b05800d80ea7a0c05803c80b80c0c03803e80d86e0d036ed2280e03811581804a0f036fd34e1003805380eb6811036e81f60e12038010ec101204805f80e83a13048033809534140471e00a15048010f95816046e81fa301704805180a74c1705800d808f1018056fd55c1905800e8091481a056e80a76e1b05805f80e2741c0571809b021c05826382c8401d0571df201e05800e809d2c1f05850083e87c1f05811580af68200571f20a21056ff9042205803e80df1e23056e81956c24056e9f542604805180e83829000e800e8080621325803380b0402a020d6ef8100e2c8c4889a96a2c000f803580ce4c2c000b6e9f54062a803480c96406260500"_hex_u8; -/* 2023-12-05T23:48:44Z 69, 44283, 586734,*/ -static constexpr auto BENCH_EXAMPLE_08 = "83728ce80000b90befca1001806083b24002b40de6da3203b545e9c35c04b34beede3005b068e8883006d41c80b1e14c07b337e7841208b26beadb2e096e83892e090980518487380a096e82815c0a096e81ce3c0b097181db200c097181d4020d09810084ed600e096e96b0100f0971819a0210086e93da2e0f09803583ee5e1009803583c66c1109800d82bb6e1209800d81d56a1309803c82e622140971819f521509803d84a55c15057181d6161605806283ac5217056e949c5a18056e89e8641806815889e23419067181de321a066e8af2641a076e82a70a1b07803583f2081c076f81e76e1d076e81d33e1e07800d83b8761e086e82a5541f087181de302008805f84ad0021086e81c74022086e81bd3e23086e9288182408806184b3102409803283816025096e91ed662609830a88e70827096e81d14a27097181ce6028096e8cf03829097181883832016f81835c3103806181e0103203804180b8103204863584fe183304800de66434046e9e4c34056e81d6742f429213c0eb602e3d6483b06c283a6e81d73c263d6e82f9581831805485ab360e37805080c62609398b3189880838010603916db1f3583a03000110873199f8623c000000011100"_hex_u8; -/* 2023-04-14T19:36:52Z 77, 20418, 501117,*/ -static constexpr auto BENCH_EXAMPLE_09 = "bf2989d00400815bca5c01af1e86f97602800d9d6c03800d8a3404b47988866e05b36287f92e0680109f68078010991a08805ecf1208076e80933e09078062d01c0a078054b6760b078053b6760c076f9c1c0d078054b6760e0771af260f0771b17e10078032f57011078035d56812078054e1581307886b83dc301407817480d13013068005a6001406803d80821a15066ef3201606800ea2181706800da628180671ab1219068054db0c1a06719b001b06815b80a11c1c068050b9301d066fac2a1e068033ab481f06719b1020068035ab721e07803dc2761f0771ae3c20078040f60e210771ce282207800ea4322307882a81a66024078035ad4625076efe7e26078162808e1827078118bb7228076eac7428088010bf58290871a04c2a0871bc722b086fa8382c08803d80a0142d088035d6282e088051c30c2f086efc623008800d9f6231086f986432088117bb7237028010a63034068010c84e2740800ea64c2237832c80933e1f3b830880c454390208813c80955c3905068032c73611348010a03c093c837a808a101b278050ac34093a8051ac34291b8f3b8187401d28881a82cb3a3a0a37977b86d20843000028996686a7083f030f8078d3761b27106e995a08499070839b5a1131000b00"_hex_u8; -/* 2023-11-07T17:59:35Z 48, 4792, 498995,*/ -static constexpr auto BENCH_EXAMPLE_10 = "875f89aa1000b51ec09d7201c55cc7a72e02a11aa1fb3203b233a7f95204800ef56205b33ea9d13006803e80b26e07d90ec9dd4008b45eabbe6c09806080ca000a815984e8680a0a6f80925e0a0a803f80e1660c09937c94b7420d086e82f5640a086e80997e0b086f808d320c08800580a5640d086f8089100e08804080c9060f088115819a1c10086e82961a0f0a805f81bc0a100a6ff826110a6ef53e120a807584c60c110a6e818f32120a803c81c246130a805481d508140a8159838410150a7180a55c160a6f80821c170a6fe6101c066fe6101d06805080f854190a6e81b27c1a0a8155819c701e06805180ae0c21046e8b9a222501805180f53422001680f26880f8a62a220116803580da582007058153838e6e21000c800d80a712033a807681ae1c23000308834a82d36023020205815981e03a051a08001700"_hex_u8; -/* 2023-11-16T10:47:08Z 77, 473962, 486863,*/ -static constexpr auto BENCH_EXAMPLE_11 = "801980c06000801980c06001801980c06002801980c06003801980c06004801980c06005801980c06006801980c06007801980c06008801980c06009801980c0600a801980c0600b801980c0600c801980c0600d801980c0600e801980c0600f801980c060108019d12c11800f80b1601111800f80b1601111801080b1601111800f80b160100e800f80b160100f801980c060110f800f80b160140d801180b1601111801180b160100d801180b160120c801180b1600f10801180b1600f11801980c0601011800f80b160140e800f80b160110f801980c060170a801180b1601210801980c060140f800f80b1601311801980c0602005801180b1601f07800f80b1601b0c800fca7c1611812081f9601638812081f9601637812081fb001636801080b160142f801980c0600e2a801080b1600f2a801180b1600d25801980c0600e25800f80b1600d27801980c0600e27801980c0600d27801180b1600e26812080b1500c27812081f960201025812081f960200f27812081fc201d101c812081fc201d101d812081fc201d0f1f812081fc201d0f20812081f9601b1016800f80b1600a35800f80b1600a36800f80b1600e32801080b160122f812081f960280040812081fc20121d1b812081f960112713812081f960160d37812081fc20140d2b812081f960130d2d812081fc20130c2c812081fb001b0157812081fb001a0245812081fc20140030812081fc20092747812081fb000b152500"_hex_u8; -/* 2023-10-06T20:44:09Z 40, 341438, 341438,*/ -static constexpr auto BENCH_EXAMPLE_12 = "80318f4c0080318f4c0180318f4c0280318f4c0380318f4c0480318f4c0580318f4c0680318f4c078033a57807078033a57807078033a57807078033a57807078033a57807078033a57807078033a57807078033a578070780318f4c0e0180318f4c0d0380318f4c0c0580318f4c0b078033a57803128033a57803128033a57803128033a578031280318f4c0412810b9c28140300810c9c281303028033a57802188033a57802188033a5780218810c9c280b01108033a578001c810c9c2807050f8033a578001b810c98040700158033a578001c810c98040301158033a5780019806ca1240101118033a578001300"_hex_u8; -/* 2023-11-15T21:40:46Z 96, 23608, 138286,*/ -static constexpr auto BENCH_EXAMPLE_13 = "8060829f4000b157bab07a01b27cc2b16802b22fbce54603826480a95804803da81a05bc7bcac93806800de55207800daf0608805bc71809805bc7180a800d9d4a0b805bbc700c8152d7180d805bb9380e850a8886260f800d80d33410bf38d3d55011b41dc4eb6012bd70d2ce2e138d3596af7812137180cd501313805e81f7281413718092001513803d81f90016136e8b916c1713801081861a17106e80cd2a18106f80cc3c19106e80cf161911800d80fe781b107180d87c1c106e80fb081d10803e8286701d11800d81c4781f10804082a6002010801081912e21107180ff0021116e81da4a2310850b8b864023116e89db3224116e84ff7e2610897c95993427106f80bb1a240b803581c272250b8032828c10260b6e80d42a270b804082b35a280b800d80fe3e290b805cc0282312821d8697022b0b6e8add562c0b805281c8063007811883f1082313800d80fe3e24137180c9142513800d8380102613803382c00e2713805eb32228136e8494542913800e8186742913806082b74c2a1380528285782b13800d818f7a2c136e84a5562d1380508286702e136f80a46e3e04803f8191364102805481ad4c3d076e809a5a3e077180fe4032136e838b7233138c4790cf384106853584ab624206805b80932a4801806280966c48028168ef04400b7181bd524903806282db5c375b9316acbf703a599c68c5a454385c6e81d63e364a6f80ff64334e817485a6784f023171819536234e800d81826e1e498053829a12420018834c87cb14291d2e840e8bc94c1d2825800d81b7220368811783fe0e271f1f811783e758380f001ecd55809edf6e56000000003a815984ba76008010d54d80aebb4e2c22000000000000002c807682f150007a00"_hex_u8; -/* 2023-12-06T09:18:20Z 93, 68130, 122830,*/ -static constexpr auto BENCH_EXAMPLE_14 = "b26beadb2e00800d80ca0a01d41c80b1e14c02b068e8883003800d81af1604b34beede30056e80b14006b151f5d46c07b93e8085b02608b30cf98b1009b14ef6b3040ab176f6ab480bb7078082b8640c800d81c6460d802c80a8080e802c80a8080f802c80a14210802ce50a11802cd722127181ce6012126e81d14a13126e9b8b00141282428dd42c15128051828408150e6e81bd3e150f805f84ad00160f7181de30170f6e81c740180f800d83b876190f6e82a5541a0f6e81d33e1a106e82a70a1b106f81e76e1c10803583f2081d106e82d9401e106e96e4441f107181de321e12815889e2341f127182d60c20126e979d4e21126e8282262410800d82972c25106f838a5822126f82842a23127182d24a2412803e84bc2a2512800d83c81a26126e84f8142712805085a22c27126e889e6a2812801083aa50281280348598102912801082d5522a126e85865c2b127182c7602b1282468c82042c126e84972c2d12805485d93a2d12801083c7322e12815386e1582f126e84fb0c30126f82eb6c3011813a85b47a3111803f869f5c3211805181ed30370d6e84bf0a3411804180e1383809815883aa183a08815a8392203e05807681f140380c6e9e4c4005805485ab363255805183856030406e82f9582c45805185c1001b4f82418df1001a4e803283c50e430026800d83a6201a4b836886be3044010b8b318988084c0101803183a6120776800d828a1e087682338ae050301c33873199f8624d010032813986bc663c1034800d83a5220a6f800d82be52048000805183e364084907800d83cc4a018005815987b41e1832000017884b9dce72035035803284c11e00800885769d9538192f0000000002001000"_hex_u8; -/* 2023-12-14T02:02:29Z 55, 247754, 247754,*/ -static constexpr auto BENCH_EXAMPLE_15 = "801980c06000801980c06001801980c06002801980c06003801980c06004801980c06005801980c06006801980c06007801980c06008801980c06009801980c0600a801980c0600b801980c0600c801980c0600d801980c0600e801180b1600e0e801180b1600e0e801180b1600e0e801180b1600e0e801180b1600e0e801180b1600e0e801180b1600d07801180b1600f06801180b1600c0a801180b1600f08801180b1600c0c801180b1600c0d801180b1600c0e801180b160100b801180b1601309812081fc200e2a812081fc200e29812081fc200e28812081fc200e0e18812081fc200e0e17801980c060042e812081fc200e0d07812081fc200e0d08812081fc200e0c0a812081fc200e0d0a801980c060081e812081fc200f0c0c812081fc200f0c0d812081fc200f0c0e801180b160083a801180b1600426801980c0600b20801980c0600a22812081fc200f0b30801180b160022b801180b160022b812081fc20062422812081fc2006220b812081fc200c0a1e812081fc2012041a00"_hex_u8; -/* 2023-12-14T15:17:20Z 76, 102600, 103935,*/ -static constexpr auto BENCH_EXAMPLE_16 = "801980c06000801980c06001801980c06002801980c06003801980c06004801180b1600404801180b1600404801180b1600404801980c0600504801980c0600802801980c0600803801180b1600704801980c0600804801280b1600804812081fc200810812081fc20080f812081fc20080e801180b160080c800f80b160080d801980c060090d801180b160090e801980c0600a0e812181fc200a0c801180b1600a0d812181fd400a0c801980c0600a1c801980c0600916801180b1600719801180b160061b801980c0600d15801980c0600717812081fc200718801980c0600716801180b160072d801180b1600722801180b1600525801980c060091b801980c060071e801080b160071f801280b160061d812081fc20063a812181f960160815801280b1600525801980c0600625801180b1600626801980c0600726801980c0600536801180b160032b801980c060042b801280b160032d801980c060033e801180b160043e812181fc20100c27801080b160042f801980c0600342801180b1600442812081fc20150d25800f80b1600245812081fd40120619812081fc20040243812081fc20120c2c812081fd40120a1d812181fb00100623812081fc20030347812081fc20072126801980c0600236812081fc20040d2b812081fc20120328801980c0600237801180b1600337812081fc20052230801180b1600239812081fc2008242c812081fd4005112d812081fb00070b32812081f96011034700"_hex_u8; -/* 2023-12-15T07:12:29Z 98, 112693, 112730,*/ -static constexpr auto BENCH_EXAMPLE_17 = "801980c06000801980c06001801980c06002801980c06003801980c06004801980c06005801980c06006801180b1600606801180b1600606801180b1600606801180b1600606801280b1600606801180b1600606801180b1600606801980c0600d00801980c0600b03801980c0600b04801980c0600f01812081fc200a16812081fc200a15812081fc200a14812081fc200a13812081fd400a12812181fc200a11812181fc200a0f801180b1600a10801180b1600a10801980c0600a10801180b1600b10801180b1600b10801980c0600621801980c0600915801980c060041b801180b160051b801980c0600f12801980c0600f13801980c0600d15801980c0600c17801980c060072e800f80b160082e812181fc200d150e801980c0600922801180b1600923801980c0600823801180b1600623801180b1600a20801180b1600e1c801180b1600b20801180b1600b21801980c0600a3e800f80b1600b3e801980c0600931801180b1600a31812181fc20140325801180b1600a30801180b160054c801180b160043b801980c0600336812181fc200253812081f960090944812081fc2007003c801980c0600339801180b1600433801980c0600453801980c0600340801980c060033d801080b160043d812081f960070854801980c060045a801180b160055a801180b1600545801980c0600643801980c0600641801280b1600739801180b1600562812081fc20121f27812181fc20210137812181fc2016112f801980c0600259801980c0600156812181fc20053a31801180b160025c801180b1600257801980c0600357812081fc200d2d1e812181fc20102444812181fc20035a801180b160035b801980c0600751812181fc2007392a812181fc20025f801980c060045e801180b1600350812081fc20070f6f801180b1600263812181fc201b1322812181fc2011283b812081fc2002442100"_hex_u8; -/* 2023-12-16T02:25:33Z 99, 112399, 112399,*/ -static constexpr auto BENCH_EXAMPLE_18 = "801980c06000801980c06001801980c06002801980c06003801980c06004801980c06005801980c06006801980c06007801180b16008801180b16009801180b1600a801180b1600a0a801180b1600a0a801180b1600a0a801180b1600a0a801980c0600d06801180b1600b09801980c0601005801180b1600c0a801980c0600d0a801980c0601106801180b1600e0a801980c0601207801980c0601207801180b160100a812081e668100a812081e668100a812081e668100a801980c0601407801980c0601606812081fc201226812081fc201225812081fc201224812081fc201223801180b1600e21801980c0600b1e801180b1600c1e801180b1601316801980c060091b801980c0601312801980c0600a1c801180b160190e801180b1601315801180b1600e1b801180b1601713801180b1600f1c801980c0600d34801980c0600d30801980c060102e801980c060122d801980c0600b2a801980c0600b2a801980c0600b2b801180b1601122801180b1600e26801180b1601025801180b1600f26812081fc20280032812081fc20270034812081fc20250034801180b1600d4b801980c0600d457a809a000d46801980c0601044801980c0600e46801180b1600f43801180b160123f801180b160123e801180b1601130801180b1601131801180b1601131812081fc20230a36801980c0600a5a801180b1600a5b801980c0600a5b801180b1600b5b801980c0600b5a801180b1600f57801180b1600d3f801980c0600669801980c0600568801980c0600466801180b1600945801180b1600649801180b1600945812081fc2018234b812081fc20142534812081fc20142532812081fc20142530801180b160074d801180b1600a4b801180b1600a4a812081fc20221662812081fc200c0472812081fc20072e42812081fc20062c23812081fc20100572812081fc200f036c812081fc2001345100"_hex_u8; -/* 2023-03-31T19:24:02Z 78, 90393, 152832,*/ -static constexpr auto BENCH_EXAMPLE_19 = "800dd042008028b13c018028b13c028028b13c038029b13c048029b13c058029b13c0680299948078029b13c088029b13c09802899480a802899480b8028b13c0c80299e700d802899480e802999480f8029b13c10802999481180299948128028b13c138029b13c1480289e701580289948168028b13c1780289948188028994819802899481a802999481b802999481c802899481d802999481e8028b13c1f8029b13c20802999482180299948228028b13c2380298c242480289948258029b13c2680288c242780298c242880299e70298f5a80ea762a824780aa00292a82038090402429813fcf00152a8203809040142a813ff700112982038090402d002d813ff70028002c8203809040270024824780aa00270025820380904025002882038090401e022a82038090401d042782038090401c01298203809040190029813ff700170028813ff700140128807b9258120128841280f6402c01002e82038090402b00062b820380904027000031813ff70011192d82038090401d000129851981a9403a0000003b82038090400c182e813ff7000b0f2982038090401314141b807b925805192b84568190001121000334807bdd400149824780aa00001f2a813ff700003d0b8203809040050d1915807bdd4001498728828f400b010004050501000a050c851981a9400104050b061a0400"_hex_u8; +// Randomly generated clusters, selected for slow linearization, from 28 to 64 transactions. +// The first 10 are selected for long total runtime, the last 10 for high runtime per cost. +static const std::vector> CLUSTERS_SYNTHETIC = { + "85058a7c008473b22d018706c70102873bd55303805e857b048732973005811487690686739032078260950508866b52098603a056000000000000000000000a8305986b0100000000000000000008852fa1260200000000000000000005855d8a0c03000000000000000000048468a9260400000000000000000004811f8449050000000000000000000483529f3e06000000000000000000048645962d07000000000000000000048333802a0800000000000000000001807187380900000000000000000001832f98220a000000000000000000018635a6380b00000000000000000001827c812c0c00000000000000000001844382700d00000000000000000000837d80410e00000000000000000000834ca16d0f00000000000000000000872d932c10000000000000000000008619b159110000000000000000000000"_hex_v_u8, + "8040810400864384650184599d6902806c802903853fa502048332a25b058627cb6b068024897307861080790883469c7a098458b10f0a846bab240b8406a1770c8526b2290d870c710e840d8c630f8608926f108646af49118426a123128747c74213813b837d0000000000000000000000000000000000000000148629c81b010000000000000000000000000000000000000012865a857b020000000000000000000000000000000000000011866c8d3d03000000000000000000000000000000000000000f71871404000000000000000000000000000000000000000a86509a1805000000000000000000000000000000000000000a863e8c4b060000000000000000000000000000000000000009871db86a070000000000000000000000000000000000000006807b930a08000000000000000000000000000000000000000585779a2a090000000000000000000000000000000000000002831584430a00000000000000000000000000000000000000018156804a0b000000000000000000000000000000000000000100"_hex_v_u8, + "8630852c008029804601864fb81d028602ae03038633af710481358b7a058559ad75068461a43b07865dd10a088516a42809810c8a250a821fa3150b812093470c805a85450d8708c27d0e8477aa770f8152850210806b892711810e805112821a904b13820582160000000000000000000000000000000000000000128114935f0100000000000000000000000000000000000000128179863902000000000000000000000000000000000000000e8064861103000000000000000000000000000000000000000d856a816c04000000000000000000000000000000000000000d8671b17405000000000000000000000000000000000000000d80368b0806000000000000000000000000000000000000000c847e881507000000000000000000000000000000000000000c8713811808000000000000000000000000000000000000000c801b89620900000000000000000000000000000000000000098139802b0a0000000000000000000000000000000000000009801d85440b00000000000000000000000000000000000000098161994d0c000000000000000000000000000000000000000882448c4b0d0000000000000000000000000000000000000008862b9c040e00000000000000000000000000000000000000068738e1580f000000000000000000000000000000000000000100"_hex_v_u8, + "8345803600823fa12101862acf45028611882c038115953e0482748b51058606af0f068619a55f078436b76f086482440983358a380a846abb680b8164991f0c8142931b0d8535b1590e846d8d6d0f8400977b10811f8d6211857bc24f12857daf3300000000000000000000000000000000000000138262a8700100000000000000000000000000000000000011821d91240200000000000000000000000000000000000010870aa538030000000000000000000000000000000000001080788d68040000000000000000000000000000000000000b8240940e0500000000000000000000000000000000000009872f863c060000000000000000000000000000000000000781568460070000000000000000000000000000000000000282138e350800000000000000000000000000000000000002871c991609000000000000000000000000000000000000028072942a0a00000000000000000000000000000000000002835d0a0b00000000000000000000000000000000000002830b824f0c0000000000000000000000000000000000000282608e110d000000000000000000000000000000000000028071842f0e00000000000000000000000000000000000002837287410f00000000000000000000000000000000000001810e88671000000000000000000000000000000000000001867c977f11000000000000000000000000000000000000018704932612000000000000000000000000000000000000018724b61b13000000000000000000000000000000000000018339a072140000000000000000000000000000000000000100"_hex_v_u8, + "8240952700865eb9710184219a7202863d8b2503833c9855048748b66705817ea16106814f8b1b07841f9349088717c90c09800d8d2d0a815190220b860bb4680c866d93250d81109a490e8075813e0f83388b3b1083268a7d118640c00f128727ae2813807b87781480428d5315833aaf6d168213a71d178623cc63188542a9531981796e1a855db8390000000000000000000000000000000000000000000000000000001b64883e0100000000000000000000000000000000000000000000000000001686638627020000000000000000000000000000000000000000000000000000168370871a03000000000000000000000000000000000000000000000000000015871b990104000000000000000000000000000000000000000000000000000014810e99080500000000000000000000000000000000000000000000000000000f80668e720600000000000000000000000000000000000000000000000000000f85738c240700000000000000000000000000000000000000000000000000000e803c8f660800000000000000000000000000000000000000000000000000000e873cb8200900000000000000000000000000000000000000000000000000000d8606cc3c0a00000000000000000000000000000000000000000000000000000b826f99050b000000000000000000000000000000000000000000000000000006815597400c000000000000000000000000000000000000000000000000000005860b955a0d00000000000000000000000000000000000000000000000000000581018f6b0e0000000000000000000000000000000000000000000000000000028644882e0f00000000000000000000000000000000000000000000000000000282048e0b1000000000000000000000000000000000000000000000000000000200"_hex_v_u8, + "8041853f008115953c01866ea90e02816ba407038101870b04854eda0505841d995a06854ad1690783528b5e0886208a3709873de6580a8454af370b8732d21e0c846ecd410d822d9b190e8335b0440f80348137108278866d11874bd81d1284788d741385389e20148501cf0c15813b81251681108971000000000000000000000000000000000000000000000017813a9d7201000000000000000000000000000000000000000000001782448a6b020000000000000000000000000000000000000000000017855cb361030000000000000000000000000000000000000000000013833da46c04000000000000000000000000000000000000000000001281349c230500000000000000000000000000000000000000000000108367a120060000000000000000000000000000000000000000000010803b8b7c07000000000000000000000000000000000000000000000e8375864508000000000000000000000000000000000000000000000e874b901609000000000000000000000000000000000000000000000e8209942e0a000000000000000000000000000000000000000000000d83248e700b000000000000000000000000000000000000000000000d840d8b040c000000000000000000000000000000000000000000000d8619c2360d000000000000000000000000000000000000000000000d8027824f0e000000000000000000000000000000000000000000000d8135841e0f000000000000000000000000000000000000000000000d85349f0510000000000000000000000000000000000000000000000c8476a00611000000000000000000000000000000000000000000000b831caa4f12000000000000000000000000000000000000000000000b84318b7e13000000000000000000000000000000000000000000000b83319f3b1400000000000000000000000000000000000000000000048701b76515000000000000000000000000000000000000000000000481558c461600000000000000000000000000000000000000000000038529be4e170000000000000000000000000000000000000000000003835da02018000000000000000000000000000000000000000000000200"_hex_v_u8, + "8639bf23008029863b0180539159028614c6330387408e22046782250582129117068365ad3a078075912708866e8a090982799c050a8474bf220b8548ba060c840eac020d850186460e815c85170f81628a301082369942118250a13b128644b74d13832da43314835c874f15800a8d5a00000000000000000000000000000000000000000000148707af630100000000000000000000000000000000000000000014806c993f020000000000000000000000000000000000000000001480288572030000000000000000000000000000000000000000001281449d7b04000000000000000000000000000000000000000000108505c536050000000000000000000000000000000000000000000f813e924b060000000000000000000000000000000000000000000f8510a24a070000000000000000000000000000000000000000000f8533875a080000000000000000000000000000000000000000000b8056800f090000000000000000000000000000000000000000000b870f8b0b0a0000000000000000000000000000000000000000000a87188b7a0b000000000000000000000000000000000000000000098417a87c0c00000000000000000000000000000000000000000009803d832c0d000000000000000000000000000000000000000000078663dd330e000000000000000000000000000000000000000000078511b21d0f000000000000000000000000000000000000000000058168992610000000000000000000000000000000000000000000058473bc4c110000000000000000000000000000000000000000000580428925120000000000000000000000000000000000000000000580408b61130000000000000000000000000000000000000000000581138d2814000000000000000000000000000000000000000000048647de091500000000000000000000000000000000000000000004817a85161600000000000000000000000000000000000000000004832d9d431700000000000000000000000000000000000000000003830b9512180000000000000000000000000000000000000000000382499e001900000000000000000000000000000000000000000003852d865e1a0000000000000000000000000000000000000000000380708f341b000000000000000000000000000000000000000000018555c1001c00000000000000000000000000000000000000000001822c886f1d0000000000000000000000000000000000000000000100"_hex_v_u8, + "82689142008207a3430182548a5902846f99510382049942048226976105833884760685499138078241a81b08801608098100854b0a820c85360b87319d4e0c821e89430d8565867d0e815a8e6c0f80478c3e108571894011836fb6291283148756138650dc4f14826ca204158726b01116833d8c4117855d9f4018866f934d19856b8e1a000000000000000000000000000000000000000000000000000019857ede10010000000000000000000000000000000000000000000000000019815881400200000000000000000000000000000000000000000000000000188277a914030000000000000000000000000000000000000000000000000017861ee9650400000000000000000000000000000000000000000000000000158656e824050000000000000000000000000000000000000000000000000015874ac43206000000000000000000000000000000000000000000000000001580449118070000000000000000000000000000000000000000000000000014812c873d08000000000000000000000000000000000000000000000000001083069a7109000000000000000000000000000000000000000000000000000e800482710a000000000000000000000000000000000000000000000000000e821a80590b0000000000000000000000000000000000000000000000000009854f8e640c0000000000000000000000000000000000000000000000000009833f9a040d00000000000000000000000000000000000000000000000000088002836f0e0000000000000000000000000000000000000000000000000008843d8d6e0f0000000000000000000000000000000000000000000000000007850ba7601000000000000000000000000000000000000000000000000000078172a62211000000000000000000000000000000000000000000000000000680008879120000000000000000000000000000000000000000000000000005861f83531300000000000000000000000000000000000000000000000000048155a44c140000000000000000000000000000000000000000000000000003810199501500000000000000000000000000000000000000000000000000038571d0061600000000000000000000000000000000000000000000000000038578e624170000000000000000000000000000000000000000000000000003872d8327180000000000000000000000000000000000000000000000000002871cae031900000000000000000000000000000000000000000000000000028733844e1a00000000000000000000000000000000000000000000000000028268a4681b0000000000000000000000000000000000000000000000000001806b95541c00000000000000000000000000000000000000000000000000008457af231d000000000000000000000000000000000000000000000000000000"_hex_v_u8, + "83159265008420be1c018635ba4702851ac26b0383559347048472943f058666ae7f068725d472078077901b08851fb31b098352bc560a84689c740b867ff24a0c807390100d80588f6f0e8670dc4a0f820aa604108747f803118340b41812841e9f5213830f9f27148557c3461583618b6916803a844d17811d9a5218863eac0e198465d8471a8578bf371b851bc6051c813e9d6e00000000000000000000000000000000000000000000000000000000001d810a9c7a01000000000000000000000000000000000000000000000000000000001d8532930402000000000000000000000000000000000000000000000000000000001c860dd80903000000000000000000000000000000000000000000000000000000001c84595c04000000000000000000000000000000000000000000000000000000001c8567930205000000000000000000000000000000000000000000000000000000001b8634ef3506000000000000000000000000000000000000000000000000000000001a873dc47007000000000000000000000000000000000000000000000000000000001a871b9543080000000000000000000000000000000000000000000000000000000019825d917f090000000000000000000000000000000000000000000000000000000019815c926f0a0000000000000000000000000000000000000000000000000000000018823c840b0b00000000000000000000000000000000000000000000000000000000178647952a0c00000000000000000000000000000000000000000000000000000000177b8d4c0d0000000000000000000000000000000000000000000000000000000014821e99240e000000000000000000000000000000000000000000000000000000001281628d230f0000000000000000000000000000000000000000000000000000000010835d8e2710000000000000000000000000000000000000000000000000000000000e84629c3611000000000000000000000000000000000000000000000000000000000d8264af3c12000000000000000000000000000000000000000000000000000000000a8300a36a13000000000000000000000000000000000000000000000000000000000a8567bf1f14000000000000000000000000000000000000000000000000000000000a8372b21815000000000000000000000000000000000000000000000000000000000a805b876e160000000000000000000000000000000000000000000000000000000008866eaa1f1700000000000000000000000000000000000000000000000000000000068018771800000000000000000000000000000000000000000000000000000000058357a23a190000000000000000000000000000000000000000000000000000000005841aa37b1a00000000000000000000000000000000000000000000000000000000048536981a1b0000000000000000000000000000000000000000000000000000000004811a8a2a1c0000000000000000000000000000000000000000000000000000000000805986361d00000000000000000000000000000000000000000000000000000000008626c9491e000000000000000000000000000000000000000000000000000000000000"_hex_v_u8, + "802d8a6400827caa630184598938028515c01903768805048416b02d058520b716068607ee1b078668ef6a08851c897109822182100a7f8a7e0b8514df270c8608b3070d843ab13a0e8512bb210f852dab13108152996d1180518a29128662d841138121971514812a9a48158574d25b168075864a17845cbf6e188261b54119822c8a711a824a9d4b1b8505a7251c8303b3551d833dc47d1e8130a0611f8352820920870cc005218463cf0222802b9102238176884600000000000000000000000000000000000000000000000000000000000000000000000023827b65010000000000000000000000000000000000000000000000000000000000000000000000237e8313020000000000000000000000000000000000000000000000000000000000000000000000228548dc5303000000000000000000000000000000000000000000000000000000000000000000000022810f8e1f040000000000000000000000000000000000000000000000000000000000000000000000228710f61a0500000000000000000000000000000000000000000000000000000000000000000000002182518f22060000000000000000000000000000000000000000000000000000000000000000000000218636f0490700000000000000000000000000000000000000000000000000000000000000000000001f8442cc610800000000000000000000000000000000000000000000000000000000000000000000001d805b921f0900000000000000000000000000000000000000000000000000000000000000000000001d861d820d0a00000000000000000000000000000000000000000000000000000000000000000000001d827d88580b00000000000000000000000000000000000000000000000000000000000000000000001d8254952b0c00000000000000000000000000000000000000000000000000000000000000000000001d822088610d00000000000000000000000000000000000000000000000000000000000000000000001d846a060e00000000000000000000000000000000000000000000000000000000000000000000001d873c935a0f0000000000000000000000000000000000000000000000000000000000000000000000198450bc44100000000000000000000000000000000000000000000000000000000000000000000000178250b646110000000000000000000000000000000000000000000000000000000000000000000000178350c1081200000000000000000000000000000000000000000000000000000000000000000000000f8414bb381300000000000000000000000000000000000000000000000000000000000000000000000e811c84181400000000000000000000000000000000000000000000000000000000000000000000000d8246aa101500000000000000000000000000000000000000000000000000000000000000000000000b81149b15160000000000000000000000000000000000000000000000000000000000000000000000078247af1f17000000000000000000000000000000000000000000000000000000000000000000000007816e863818000000000000000000000000000000000000000000000000000000000000000000000006853b8e02190000000000000000000000000000000000000000000000000000000000000000000000068161a32e1a000000000000000000000000000000000000000000000000000000000000000000000006811d87041b00000000000000000000000000000000000000000000000000000000000000000000000200"_hex_v_u8, + "80578c04008734c974018654a51d02856aae0d038719973c0106807f904f04018430913d000a871a985d0500018374965f030109867466000e86479a690203038735c80d0109836997010700000003840ea121080000000082699354030310803a520702000483019f2709000000108159881f0703000e847d8f10060400128128901604070e851e8f1109020100801c820f01090009803d884a040101000000010f850e8f610202000106800b880606000200000100832e826f040100000000000009856fba6002020100000008865f9a0e06000000000000000100"_hex_v_u8, + "81088137008624a1750180398f1602807a892b0102866bd12302028049450301803282010300076e880205000280068133050002810880170401088306997c05000002852e9e020401000282239424060001000166863504010000000d8135804d0601000000098227952105000100000a851a855e06000100000a8628a34b07000100000180038015070000010008801c8a7307000000010585168d210702000000000a821a6c03000202058329981f000200098507a21a000d8723bd13000417845cab5804020000000000000f83028f260201030380106e02010315842dac580501010012873bc37e040101001a8214973b060001000a80368d7902010100001900"_hex_v_u8, + "820ea20c0083308407018237a67a02821fa37900048320ab4a0202844db6210401811b98490008830b931701068676ba5d030100058317997504000386259140020402857cc2410402000a8001832d010c81078d35060200068149960708010004863ea83505040d83498656070200038320a77503050111830b822f070200010681018041060302008232863b090200000085619c03030502088524a00c030002078545a04602050002028100921a0405000200008473c1530900000101000e845a8d080500020100000017867087460502010000000f7489310401010102018216a16a0104000001078427af750501000000010585458b43040202000002872cda4f0304010000068209851d010200000000138321827a01000000000f8356a908010000002200"_hex_v_u8, + "833a8843008449995c01810a873000038066934d02018616c04d0202843a87600401826a864d0402855f8779050007863d936903038572d02d040204867cc61604030180098b69020509832792510601010581798e06040504833b9439070101000d851f946f01128147927d02030014866baa5d070201000c805f854f070300010b81579177090201000483209f7a0404020283678e420400050d8340981e080101000100048070816d07000101020c85578f010b01000000000004821a972605010204000010831c94280a01000000010001812d906c0303060882599e120505000200000a80638c5707020103844d9f04080004000000000e802b8c3107010300001b83189062020703000d872cb306080202000000098370a61e0600040000010d800a870f01010401000982369a1e030204000000058747c40c0102000317854e9e1c010300000000001a873eb04001020000002500"_hex_v_u8, + "83778b4c00834c932901853ed03202834a844c00048716a22d02038529c429020103843ebe7f0300048135944b040002810f9704040005850b9463010980628e700501000a80238d380502078739843b080000008405896304000f80094c04010d807d9739070000000c8218a32304030109853a9d460500020f817b9412030600000987498636010014850e8104060400000b80378b5407030001000b8659d87f05040001000b831e8f740a000100000009844c9f7d040501188203964806040101038201946806050000188406bd48060600000014860e9c4f0b010000000f8549c114040500030f813b82510406000218842da63e0800030115811d9b5a0501010003001c84208029070004000d832a95220500010101010022852b9b24030401010000001a857f8107050104000000000f853e9525080100000000000109821f9507050101000104827aa04602030220834f803405000201001c8268aa1603030000000001008475b43202000300000005827b93560202000100002900"_hex_v_u8, + "812d9c7200852fd301018170943702824a9e630387272402048353b33102038556b159020481599b00010883528735030381718370030107842cbf77050103816c8a1c0600048422c55d04078321a409090000000982688622050508806f8c090017873c5b08010004817fa04a050201048567c15f0505011082219f00080201118135892e07060000078360af6d0309138031824c0608001185739b080a020101000582018d0a0a000311813d860109010f8452bc6400060411825a8e2b050702098246a769012484049d3c07070100138313853a06080000158553b84b0b0203000000000010817da176030d1883388476040314852381580405050020830e966a0100042585128a3e08000001000121836f870403000700010000000025862cad000207020001128742d7180701010200010001088606da7c06000400010001000b84699b2d06030101000000010000138418b6250602030000001f84155c0401050013846a81610101030106873fd029020101000001000e82508521010300010d862ec12700000000001500"_hex_v_u8, + "7d836f008521bb6b018061934b0285118b7903802285670203850f8b5201058221a327030287369342060080188c340601802d8f68060003840fb2400700028227a713070100028747c93d06010007874b99630503010281779f3103060380228e000602000f802f831a09000106857781300803000000108707a45d080001010011830a9132030811821aa471080301000010864aca7c0602021185758d640702000110843c8a2306000214850ec3040a01020100000082568231030a00000d810e927503000a0d8001803e070001021980209041010c0005822f9c6205070005835cbf50030b000011852bb20f0a0400000000001d83179f210c010101000000001a806a967c05030403208212980d0404030302832ca5660902020000001f852e9e39080303020000028572ca5208010203020013816192320e010000010000228672ef1d06060100011082419e6a050701021f8424b26e05040104010c8420bf780406030000000000855ac63c0b00010100000000000c8426ae25030501030016874a9330030104030019871be924060200010000000000118644cd2103010302010f8345ad03000401010d81379c2102000100010030837f810901010000000e832bb24f0100000600"_hex_v_u8, + "816ba22900836f867101873c8056028511815803815497480205804283330105807e1d020383028a6100058533cc7b0100086a893d040003822493090302000b86108056020a854cbe4b05010483128c2d090000000182359815010c7281540800010002810889440602000e8518d60f09000000108563e05b0602020e8544ba220b000001018655da2105041382438a0c080202000381138a2b0702031085508f2904080004867ecb6709020002048375b142080103011a867bcc450508011983368a0b0c0200000a8420a84a0b03020000098312af7d0606020000088443c4550502050200068524b179090300000002001981188579030a00000217806d8f0a0c000005000d8076851a0800020003021581359369060101060103846eae34070203000200010022832a86670d0100000000000102830891620a00010100030000000001810592140900020001000003278712eb5d090500000100011f813f921a05020402128212ac1607010000090a870fc33001041981708e7a000903010a835ba272020900010001001884618f680207000101010118850bb168070301020000000680779a6f0406000002001f8670843605010500011e842e904709010003010000000d8222ae010205020128866086120702020000000000001082029c35010101030b82639c7802010300000100328138801001020102002000"_hex_v_u8, + "815e9a23008324a10d01825bb246028470af1301048737ba3202048264aa000304843383590403857ba37c05028351be6805028123965a0700098567c53f0307854fde3e04000e825aa8790305098259b529020f8365985d0505068246ac510800010e83448c410215801b8464011c85548a540705010f840c9d10050800118472bb480211821aa7510d010000000010823b9d50010c108533a8030607020007804e90030d0000000a80799051060701078717900106070000168511962d0209000209806c801c060603001a8347a86607060102000f8245924309030001031a8177a513070504001b81348064080a0000000002856ce16106010902000f850088600701090200048044847b09040301010020842189140d04010000010000168210a91b02000609208618ae420609020000000b8257951b0b00070100000009855d8b7b0902030520810190300a0701000000000000028254ae7b0700060001031f8748bf21040902108264af760f0004000001000a813587660c0005000100000000002d8256a04a080a00000000246a8526030a06001780068011050a000403866ecf5a080a03000000008603c30509020000050f8250916201070306000025825385380e00000200000000348210836504090200010100048334b97a060502000201012c84088e520705020002010105814a8c65050507011a8178a15e05010101000000003680448c190200020300010100003280388f2c03000000010000000000002600"_hex_v_u8, + "824da926008527804e01871bca36028604b04f038558d62c04804a960f02048513d87301068229897103058675a07e03000b843c8311050201814e5d02030c87029e020109841dc65a0601038227a325070101088334801c06030102853ee538070005688b0d080200010d8406a94d0b000000000b852bbf050a020000000a811380750d0000000005825c935d0b000200000a871bcb700902020109824187140f00000000098614ec7f0d000003078621942b080800058454bb1c0e030000000a847e630d030001000b8601e07e0c0002030c8655bf0b030f00018306bb5705010c1487038e071200000000001b856f816f1000000100020018820a9a530a0208000a81398a74001321853a944a060a03000117835aaf1c09030701118529b8690709020201248621bd330e010001010202148022800104100981428234090800030100188517cd2007060100020305836dc031020209022782449b69040c0100000001108651ac0a0607010101000000022a8337c0610505040202000001228547cd7c0d02000102020000000d843eba2a0b0501000000020025810f94010b0002030101000000010e803c921006000008041b8453bb3a0a0600030000000000000017841bae620708030100000016813f9924110100000200000000000000000c8637f36205060200050006857eb53f08020900000000002d810b9e580c0005000201000000000786459b700207060101188536b7790a0601000000010000000006854d905f08070201000000001680578f6a09030200030031837c8419080006000000022e8518a8500500040501000028861dd07e0801010300003b8743b97202000002000105003b8545b730010200010000000000001f00"_hex_v_u8 +}; -static void LinearizeOptimallyExample00(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_00); } -static void LinearizeOptimallyExample01(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_01); } -static void LinearizeOptimallyExample02(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_02); } -static void LinearizeOptimallyExample03(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_03); } -static void LinearizeOptimallyExample04(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_04); } -static void LinearizeOptimallyExample05(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_05); } -static void LinearizeOptimallyExample06(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_06); } -static void LinearizeOptimallyExample07(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_07); } -static void LinearizeOptimallyExample08(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_08); } -static void LinearizeOptimallyExample09(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_09); } -static void LinearizeOptimallyExample10(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_10); } -static void LinearizeOptimallyExample11(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_11); } -static void LinearizeOptimallyExample12(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_12); } -static void LinearizeOptimallyExample13(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_13); } -static void LinearizeOptimallyExample14(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_14); } -static void LinearizeOptimallyExample15(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_15); } -static void LinearizeOptimallyExample16(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_16); } -static void LinearizeOptimallyExample17(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_17); } -static void LinearizeOptimallyExample18(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_18); } -static void LinearizeOptimallyExample19(benchmark::Bench& bench) { BenchLinearizeOptimally(bench, BENCH_EXAMPLE_19); } +static void LinearizeOptimallyTotal(benchmark::Bench& bench) +{ + BenchLinearizeOptimallyTotal(bench, "LinearizeOptimallyHistoricalTotal", CLUSTERS_HISTORICAL); + BenchLinearizeOptimallyTotal(bench, "LinearizeOptimallySyntheticTotal", CLUSTERS_SYNTHETIC); +} + +static void LinearizeOptimallyPerCost(benchmark::Bench& bench) +{ + BenchLinearizeOptimallyPerCost(bench, "LinearizeOptimallyHistoricalPerCost", CLUSTERS_HISTORICAL); + BenchLinearizeOptimallyPerCost(bench, "LinearizeOptimallySyntheticPerCost", CLUSTERS_SYNTHETIC); +} BENCHMARK(Linearize16TxWorstCase20Iters, benchmark::PriorityLevel::HIGH); BENCHMARK(Linearize16TxWorstCase120Iters, benchmark::PriorityLevel::HIGH); @@ -397,23 +391,5 @@ BENCHMARK(MergeLinearizations64TxWorstCase, benchmark::PriorityLevel::HIGH); BENCHMARK(MergeLinearizations75TxWorstCase, benchmark::PriorityLevel::HIGH); BENCHMARK(MergeLinearizations99TxWorstCase, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample00, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample01, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample02, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample03, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample04, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample05, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample06, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample07, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample08, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample09, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample10, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample11, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample12, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample13, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample14, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample15, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample16, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample17, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample18, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeOptimallyExample19, benchmark::PriorityLevel::HIGH); +BENCHMARK(LinearizeOptimallyTotal, benchmark::PriorityLevel::HIGH); +BENCHMARK(LinearizeOptimallyPerCost, benchmark::PriorityLevel::HIGH); diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 73c8a0378fc..64bf9cd587c 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -334,6 +334,15 @@ public: return true; } + unsigned CountDependencies() const noexcept + { + unsigned ret = 0; + for (auto i : Positions()) { + ret += GetReducedParents(i).Count(); + } + return ret; + } + /** Reduce memory usage if possible. No observable effect. */ void Compact() noexcept { From c461259fb62982fd054cdf8e8fac8016e8dd9a04 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 10 Dec 2025 14:22:10 -0500 Subject: [PATCH 03/11] clusterlin: add class implementing SFL state (preparation) This adds a data structure representing the optimization state for the spanning-forest linearization algorithm (SFL), plus a fuzz test for its correctness. This is preparation for switching over Linearize() to use this algorithm. See https://delvingbitcoin.org/t/spanning-forest-cluster-linearization/1419 for a description of the algorithm. --- src/cluster_linearize.h | 770 ++++++++++++++++++++++++++++ src/test/fuzz/cluster_linearize.cpp | 106 +++- 2 files changed, 860 insertions(+), 16 deletions(-) diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 64bf9cd587c..0ea9de092d8 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -395,6 +395,22 @@ struct SetInfo return *this; } + /** Remove the transactions of other from this SetInfo (which must be a subset). */ + SetInfo& operator-=(const SetInfo& other) noexcept + { + Assume(other.transactions.IsSubsetOf(transactions)); + transactions -= other.transactions; + feerate -= other.feerate; + return *this; + } + + /** Compute the difference between this and other SetInfo (which must be a subset). */ + SetInfo operator-(const SetInfo& other) const noexcept + { + Assume(other.transactions.IsSubsetOf(transactions)); + return {transactions - other.transactions, feerate - other.feerate}; + } + /** Construct a new SetInfo equal to this, with more transactions added (which may overlap * with the existing transactions in the SetInfo). */ [[nodiscard]] SetInfo Add(const DepGraph& depgraph, const SetType& txn) const noexcept @@ -662,6 +678,760 @@ public: } }; +/** Class to represent the internal state of the spanning-forest linearization (SFL) algorithm. + * + * At all times, each dependency is marked as either "active" or "inactive". The subset of active + * dependencies is the state of the SFL algorithm. The implementation maintains several other + * values to speed up operations, but everything is ultimately a function of what that subset of + * active dependencies is. + * + * Given such a subset, define a chunk as the set of transactions that are connected through active + * dependencies (ignoring their parent/child direction). Thus, every state implies a particular + * partitioning of the graph into chunks (including potential singletons). In the extreme, each + * transaction may be in its own chunk, or in the other extreme all transactions may form a single + * chunk. A chunk's feerate is its total fee divided by its total size. + * + * The algorithm consists of switching dependencies between active and inactive. The final + * linearization that is produced at the end consists of these chunks, sorted from high to low + * feerate, each individually sorted in an arbitrary but topological (= no child before parent) + * way. + * + * We define three quality properties the state can have, each being stronger than the previous: + * + * - acyclic: The state is acyclic whenever no cycle of active dependencies exists within the + * graph, ignoring the parent/child direction. This is equivalent to saying that within + * each chunk the set of active dependencies form a tree, and thus the overall set of + * active dependencies in the graph form a spanning forest, giving the algorithm its + * name. Being acyclic is also equivalent to every chunk of N transactions having + * exactly N-1 active dependencies. + * + * For example in a diamond graph, D->{B,C}->A, the 4 dependencies cannot be + * simultaneously active. If at least one is inactive, the state is acyclic. + * + * The algorithm maintains an acyclic state at *all* times as an invariant. This implies + * that activating a dependency always corresponds to merging two chunks, and that + * deactivating one always corresponds to splitting two chunks. + * + * - topological: We say the state is topological whenever it is acyclic and no inactive dependency + * exists between two distinct chunks such that the child chunk has higher or equal + * feerate than the parent chunk. + * + * The relevance is that whenever the state is topological, the produced output + * linearization will be topological too (i.e., not have children before parents). + * Note that the "or equal" part of the definition matters: if not, one can end up + * in a situation with mutually-dependent equal-feerate chunks that cannot be + * linearized. For example C->{A,B} and D->{A,B}, with C->A and D->B active. The AC + * chunk depends on DB through C->B, and the BD chunk depends on AC through D->A. + * Merging them into a single ABCD chunk fixes this. + * + * The algorithm attempts to keep the state topological as much as possible, so it + * can be interrupted to produce an output whenever, but will sometimes need to + * temporarily deviate from it when improving the state. + * + * - optimal: For every active dependency, define its top and bottom set as the set of transactions + * in the chunks that would result if the dependency were deactivated; the top being the + * one with the dependency's parent, and the bottom being the one with the child. Note + * that due to acyclicity, every deactivation splits a chunk exactly in two. + * + * We say the state is optimal whenever it is topological and it has no active + * dependency whose top feerate is strictly higher than its bottom feerate. The + * relevance is that it can be proven that whenever the state is optimal, the produced + * linearization will also be optimal (in the convexified feerate diagram sense). It can + * also be proven that for every graph at least one optimal state exists. + * + * Note that it is possible for the SFL state to not be optimal, but the produced + * linearization to still be optimal. This happens when the chunks of a state are + * identical to those of an optimal state, but the exact set of active dependencies + * within a chunk differ in such a way that the state optimality condition is not + * satisfied. Thus, the state being optimal is more a "the eventual output is *known* + * to be optimal". + * + * The algorithm terminates whenever an optimal state is reached. + * + * + * This leads to the following high-level algorithm: + * - Start with all dependencies inactive, and thus all transactions in their own chunk. This is + * definitely acyclic. + * - Activate dependencies (merging chunks) until the state is topological. + * - Loop until optimal (no dependencies with higher-feerate top than bottom), or time runs out: + * - Deactivate a violating dependency, potentially making the state non-topological. + * - Activate other dependencies to make the state topological again. + * - Output the chunks from high to low feerate, each internally sorted topologically. + * + * When merging, we always either: + * - Merge upwards: merge a chunk with the lowest-feerate other chunk it depends on, among those + * with lower or equal feerate than itself. + * - Merge downwards: merge a chunk with the highest-feerate other chunk that depends on it, among + * those with higher or equal feerate than itself. + * + * Using these strategies in the improvement loop above guarantees that the output linearization + * after a deactivate + merge step is never worse or incomparable (in the convexified feerate + * diagram sense) than the output linearization that would be produced before the step. With that, + * we can refine the high-level algorithm to: + * - Start with all dependencies inactive. + * - Perform merges as described until none are possible anymore, making the state topological. + * - Loop until optimal or time runs out: + * - Pick a dependency D to deactivate among those with higher feerate top than bottom. + * - Deactivate D, causing the chunk it is in to split into top T and bottom B. + * - Do an upwards merge of T, if possible. If so, repeat the same with the merged result. + * - Do a downwards merge of B, if possible. If so, repeat the same with the merged result. + * - Output the chunks from high to low feerate, each internally sorted topologically. + * + * What remains to be specified are a number of heuristics: + * + * - How to decide which chunks to merge: + * - The merge upwards and downward rules specify that the lowest-feerate respectively + * highest-feerate candidate chunk is merged with, but if there are multiple equal-feerate + * candidates, the chunk with the highest-index transaction involving a relevant dependency is + * picked (this will be changed in a later commit). + * + * - How to decide what dependency to activate (when merging chunks): + * - After picking two chunks to be merged (see above), the dependency with the lowest-index + * transaction in the other chunk is activated (this will be changed in a later commit). + * + * - How to decide which chunk to find a dependency to split in: + * - The chunk with the lowest-index representative (an implementation detail) that can be split + * is picked (this will be changed in a later commit). + * + * - How to decide what dependency to deactivate (when splitting chunks): + * - Inside the selected chunk (see above), among the dependencies whose top feerate is strictly + * higher than its bottom feerate in the selected chunk, if any, the one with the lowest-index + * child is deactivated (this will be changed in a later commit). + */ +template +class SpanningForestState +{ +private: + /** Data type to represent indexing into m_tx_data. */ + using TxIdx = uint32_t; + /** Data type to represent indexing into m_dep_data. */ + using DepIdx = uint32_t; + + /** Structure with information about a single transaction. For transactions that are the + * representative for the chunk they are in, this also stores chunk information. */ + struct TxData { + /** The dependencies to children of this transaction. Immutable after construction. */ + std::vector child_deps; + /** The set of parent transactions of this transaction. Immutable after construction. */ + SetType parents; + /** The set of child transactions of this transaction. Immutable after construction. */ + SetType children; + /** Which transaction holds the chunk_setinfo for the chunk this transaction is in + * (the representative for the chunk). */ + TxIdx chunk_rep; + /** (Only if this transaction is the representative for the chunk it is in) the total + * chunk set and feerate. */ + SetInfo chunk_setinfo; + }; + + /** Structure with information about a single dependency. */ + struct DepData { + /** Whether this dependency is active. */ + bool active; + /** What the parent and child transactions are. Immutable after construction. */ + TxIdx parent, child; + /** (Only if this dependency is active) the would-be top chunk and its feerate that would + * be formed if this dependency were to be deactivated. */ + SetInfo top_setinfo; + }; + + /** The set of all TxIdx's of transactions in the cluster indexing into m_tx_data. */ + SetType m_transaction_idxs; + /** Information about each transaction (and chunks). Keeps the "holes" from DepGraph during + * construction. Indexed by TxIdx. */ + std::vector m_tx_data; + /** Information about each dependency. Indexed by DepIdx. */ + std::vector m_dep_data; + + /** The number of updated transactions in activations/deactivations. */ + uint64_t m_cost{0}; + + /** Update a chunk: + * - All transactions have their chunk representative set to `chunk_rep`. + * - All dependencies which have `query` in their top_setinfo get `dep_change` added to it + * (if `!Subtract`) or removed from it (if `Subtract`). + */ + template + void UpdateChunk(const SetType& chunk, TxIdx query, TxIdx chunk_rep, const SetInfo& dep_change) noexcept + { + // Iterate over all the chunk's transactions. + for (auto tx_idx : chunk) { + auto& tx_data = m_tx_data[tx_idx]; + // Update the chunk representative. + tx_data.chunk_rep = chunk_rep; + // Iterate over all active dependencies with tx_idx as parent. Combined with the outer + // loop this iterates over all internal active dependencies of the chunk. + auto child_deps = std::span{tx_data.child_deps}; + for (auto dep_idx : child_deps) { + auto& dep_entry = m_dep_data[dep_idx]; + Assume(dep_entry.parent == tx_idx); + // Skip inactive dependencies. + if (!dep_entry.active) continue; + // If this dependency's top_setinfo contains query, update it to add/remove + // dep_change. + if (dep_entry.top_setinfo.transactions[query]) { + if constexpr (Subtract) { + dep_entry.top_setinfo -= dep_change; + } else { + dep_entry.top_setinfo |= dep_change; + } + } + } + } + } + + /** Make a specified inactive dependency active. Returns the merged chunk representative. */ + TxIdx Activate(DepIdx dep_idx) noexcept + { + auto& dep_data = m_dep_data[dep_idx]; + Assume(!dep_data.active); + auto& child_tx_data = m_tx_data[dep_data.child]; + auto& parent_tx_data = m_tx_data[dep_data.parent]; + + // Gather information about the parent and child chunks. + Assume(parent_tx_data.chunk_rep != child_tx_data.chunk_rep); + auto& par_chunk_data = m_tx_data[parent_tx_data.chunk_rep]; + auto& chl_chunk_data = m_tx_data[child_tx_data.chunk_rep]; + TxIdx top_rep = parent_tx_data.chunk_rep; + auto top_part = par_chunk_data.chunk_setinfo; + auto bottom_part = chl_chunk_data.chunk_setinfo; + // Update the parent chunk to also contain the child. + par_chunk_data.chunk_setinfo |= bottom_part; + m_cost += par_chunk_data.chunk_setinfo.transactions.Count(); + + // Consider the following example: + // + // A A There are two chunks, ABC and DEF, and the inactive E->C dependency + // / \ / \ is activated, resulting in a single chunk ABCDEF. + // B C B C + // : ==> | Dependency | top set before | top set after | change + // D E D E B->A | AC | ACDEF | +DEF + // \ / \ / C->A | AB | AB | + // F F F->D | D | D | + // F->E | E | ABCE | +ABC + // + // The common pattern here is that any dependency which has the parent or child of the + // dependency being activated (E->C here) in its top set, will have the opposite part added + // to it. This is true for B->A and F->E, but not for C->A and F->D. + // + // Let UpdateChunk traverse the old parent chunk top_part (ABC in example), and add + // bottom_part (DEF) to every dependency's top_set which has the parent (C) in it. The + // representative of each of these transactions was already top_rep, so that is not being + // changed here. + UpdateChunk(/*chunk=*/top_part.transactions, /*query=*/dep_data.parent, + /*chunk_rep=*/top_rep, /*dep_change=*/bottom_part); + // Let UpdateChunk traverse the old child chunk bottom_part (DEF in example), and add + // top_part (ABC) to every dependency's top_set which has the child (E) in it. At the same + // time, change the representative of each of these transactions to be top_rep, which + // becomes the representative for the merged chunk. + UpdateChunk(/*chunk=*/bottom_part.transactions, /*query=*/dep_data.child, + /*chunk_rep=*/top_rep, /*dep_change=*/top_part); + // Make active. + dep_data.active = true; + dep_data.top_setinfo = top_part; + return top_rep; + } + + /** Make a specified active dependency inactive. */ + void Deactivate(DepIdx dep_idx) noexcept + { + auto& dep_data = m_dep_data[dep_idx]; + Assume(dep_data.active); + auto& parent_tx_data = m_tx_data[dep_data.parent]; + // Make inactive. + dep_data.active = false; + // Update representatives. + auto& chunk_data = m_tx_data[parent_tx_data.chunk_rep]; + m_cost += chunk_data.chunk_setinfo.transactions.Count(); + auto top_part = dep_data.top_setinfo; + auto bottom_part = chunk_data.chunk_setinfo - top_part; + TxIdx bottom_rep = dep_data.child; + auto& bottom_chunk_data = m_tx_data[bottom_rep]; + bottom_chunk_data.chunk_setinfo = bottom_part; + TxIdx top_rep = dep_data.parent; + auto& top_chunk_data = m_tx_data[top_rep]; + top_chunk_data.chunk_setinfo = top_part; + + // See the comment above in Activate(). We perform the opposite operations here, + // removing instead of adding. + // + // Let UpdateChunk traverse the old parent chunk top_part, and remove bottom_part from + // every dependency's top_set which has the parent in it. At the same time, change the + // representative of each of these transactions to be top_rep. + UpdateChunk(/*chunk=*/top_part.transactions, /*query=*/dep_data.parent, + /*chunk_rep=*/top_rep, /*dep_change=*/bottom_part); + // Let UpdateChunk traverse the old child chunk bottom_part, and remove top_part from every + // dependency's top_set which has the child in it. At the same time, change the + // representative of each of these transactions to be bottom_rep. + UpdateChunk(/*chunk=*/bottom_part.transactions, /*query=*/dep_data.child, + /*chunk_rep=*/bottom_rep, /*dep_change=*/top_part); + } + + /** Activate a dependency from the chunk represented by bottom_rep to the chunk represented by + * top_rep, which must exist. Return the representative of the merged chunk. */ + TxIdx MergeChunks(TxIdx top_rep, TxIdx bottom_rep) noexcept + { + auto& top_chunk = m_tx_data[top_rep]; + Assume(top_chunk.chunk_rep == top_rep); + auto& bottom_chunk = m_tx_data[bottom_rep]; + Assume(bottom_chunk.chunk_rep == bottom_rep); + // Activate the first dependency between bottom_chunk and top_chunk. + for (auto tx : top_chunk.chunk_setinfo.transactions) { + auto& tx_data = m_tx_data[tx]; + // As an optimization, only iterate over transactions which have dependencies in the + // bottom chunk. + if (tx_data.children.Overlaps(bottom_chunk.chunk_setinfo.transactions)) { + for (auto dep : tx_data.child_deps) { + auto& dep_data = m_dep_data[dep]; + if (bottom_chunk.chunk_setinfo.transactions[dep_data.child]) { + return Activate(dep); + } + } + break; + } + } + Assume(false); + return TxIdx(-1); + } + + /** Perform an upward or downward merge step, on the specified chunk representative. Returns + * the representative of the merged chunk, or TxIdx(-1) if no merge took place. */ + template + TxIdx MergeStep(TxIdx chunk_rep) noexcept + { + /** Information about the chunk that tx_idx is currently in. */ + auto& chunk_data = m_tx_data[chunk_rep]; + SetType chunk_txn = chunk_data.chunk_setinfo.transactions; + // Iterate over all transactions in the chunk, figuring out which other chunk each + // depends on, but only testing each other chunk once. For those depended-on chunks, + // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one. + // If multiple equal-feerate candidate chunks to merge with exist, pick the last one + // among them. + + /** Which transactions have been reached from this chunk already. Initialize with the + * chunk itself, so internal dependencies within the chunk are ignored. */ + SetType explored = chunk_txn; + /** The minimum feerate (if downward) or maximum feerate (if upward) to consider when + * looking for candidate chunks to merge with. Initially, this is the original chunk's + * feerate, but is updated to be the current best candidate whenever one is found. */ + FeeFrac best_other_chunk_feerate = chunk_data.chunk_setinfo.feerate; + /** The representative for the best candidate chunk to merge with. -1 if none. */ + TxIdx best_other_chunk_rep = TxIdx(-1); + for (auto tx : chunk_txn) { + auto& tx_data = m_tx_data[tx]; + /** The transactions reached by following dependencies from tx that have not been + * explored before. */ + auto newly_reached = (DownWard ? tx_data.children : tx_data.parents) - explored; + explored |= newly_reached; + while (newly_reached.Any()) { + // Find a chunk inside newly_reached, and remove it from newly_reached. + auto reached_chunk_rep = m_tx_data[newly_reached.First()].chunk_rep; + auto& reached_chunk = m_tx_data[reached_chunk_rep].chunk_setinfo; + newly_reached -= reached_chunk.transactions; + // See if it has an acceptable feerate. + auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk.feerate) + : FeeRateCompare(reached_chunk.feerate, best_other_chunk_feerate); + if (cmp <= 0) { + best_other_chunk_feerate = reached_chunk.feerate; + best_other_chunk_rep = reached_chunk_rep; + } + } + } + // Stop if there are no candidate chunks to merge with. + if (best_other_chunk_rep == TxIdx(-1)) return TxIdx(-1); + if constexpr (DownWard) { + chunk_rep = MergeChunks(chunk_rep, best_other_chunk_rep); + } else { + chunk_rep = MergeChunks(best_other_chunk_rep, chunk_rep); + } + Assume(chunk_rep != TxIdx(-1)); + return chunk_rep; + } + + + /** Perform an upward or downward merge sequence on the specified transaction. */ + template + void MergeSequence(TxIdx tx_idx) noexcept + { + auto chunk_rep = m_tx_data[tx_idx].chunk_rep; + while (true) { + auto merged_rep = MergeStep(chunk_rep); + if (merged_rep == TxIdx(-1)) break; + chunk_rep = merged_rep; + } + } + + /** Split a chunk, and then merge the resulting two chunks to make the graph topological + * again. */ + void Improve(DepIdx dep_idx) noexcept + { + auto& dep_data = m_dep_data[dep_idx]; + Assume(dep_data.active); + // Deactivate the specified dependency, splitting it into two new chunks: a top containing + // the parent, and a bottom containing the child. The top should have a higher feerate. + Deactivate(dep_idx); + + // At this point we have exactly two chunks which may violate topology constraints (the + // parent chunk and child chunk that were produced by deactivating dep_idx). We can fix + // these using just merge sequences, one upwards and one downwards, avoiding the need for a + // full MakeTopological. + + // Merge the top chunk with lower-feerate chunks it depends on (which may be the bottom it + // was just split from, or other pre-existing chunks). + MergeSequence(dep_data.parent); + // Merge the bottom chunk with higher-feerate chunks that depend on it. + MergeSequence(dep_data.child); + } + +public: + /** Construct a spanning forest for the given DepGraph, with every transaction in its own chunk + * (not topological). */ + explicit SpanningForestState(const DepGraph& depgraph) noexcept + { + m_transaction_idxs = depgraph.Positions(); + auto num_transactions = m_transaction_idxs.Count(); + m_tx_data.resize(depgraph.PositionRange()); + // Reserve the maximum number of (reserved) dependencies the cluster can have, so + // m_dep_data won't need any reallocations during construction. For a cluster with N + // transactions, the worst case consists of two sets of transactions, the parents and the + // children, where each child depends on each parent and nothing else. For even N, both + // sets can be sized N/2, which means N^2/4 dependencies. For odd N, one can be (N + 1)/2 + // and the other can be (N - 1)/2, meaning (N^2 - 1)/4 dependencies. Because N^2 is odd in + // this case, N^2/4 (with rounding-down division) is the correct value in both cases. + m_dep_data.reserve((num_transactions * num_transactions) / 4); + for (auto tx : m_transaction_idxs) { + // Fill in transaction data. + auto& tx_data = m_tx_data[tx]; + tx_data.chunk_rep = tx; + tx_data.chunk_setinfo.transactions = SetType::Singleton(tx); + tx_data.chunk_setinfo.feerate = depgraph.FeeRate(tx); + // Add its dependencies. + SetType parents = depgraph.GetReducedParents(tx); + for (auto par : parents) { + auto& par_tx_data = m_tx_data[par]; + auto dep_idx = m_dep_data.size(); + // Construct new dependency. + auto& dep = m_dep_data.emplace_back(); + dep.active = false; + dep.parent = par; + dep.child = tx; + // Add it as parent of the child. + tx_data.parents.Set(par); + // Add it as child of the parent. + par_tx_data.child_deps.push_back(dep_idx); + par_tx_data.children.Set(tx); + } + } + } + + /** Make state topological. Can be called after constructing. */ + void MakeTopological() noexcept + { + while (true) { + bool done = true; + // Iterate over all transactions (only processing those which are chunk representatives). + for (auto chunk : m_transaction_idxs) { + auto& chunk_data = m_tx_data[chunk]; + // If this is not a chunk representative, skip. + if (chunk_data.chunk_rep != chunk) continue; + // Attempt to merge the chunk upwards. + auto result_up = MergeStep(chunk); + if (result_up != TxIdx(-1)) { + done = false; + continue; + } + // Attempt to merge the chunk downwards. + auto result_down = MergeStep(chunk); + if (result_down != TxIdx(-1)) { + done = false; + continue; + } + } + // Stop if no changes were made anymore. + if (done) break; + } + } + + /** Try to improve the forest. Returns false if it is optimal, true otherwise. */ + bool OptimizeStep() noexcept + { + // Iterate over all transactions (only processing those which are chunk representatives). + for (auto chunk : m_transaction_idxs) { + auto& chunk_data = m_tx_data[chunk]; + // If this is not a chunk representative, skip. + if (chunk_data.chunk_rep != chunk) continue; + // Iterate over all transactions of the chunk. + for (auto tx : chunk_data.chunk_setinfo.transactions) { + const auto& tx_data = m_tx_data[tx]; + // Iterate over all active child dependencies of the transaction. + const auto children = std::span{tx_data.child_deps}; + for (DepIdx dep_idx : children) { + const auto& dep_data = m_dep_data[dep_idx]; + if (!dep_data.active) continue; + // Skip if this dependency is ineligible (the top chunk that would be created + // does not have higher feerate than the chunk it is currently part of). + if (!(dep_data.top_setinfo.feerate >> chunk_data.chunk_setinfo.feerate)) continue; + // Otherwise, deactivate it and then make the state topological again with a + // sequence of merges. + Improve(dep_idx); + return true; + } + } + } + // No improvable chunk was found, we are done. + return false; + } + + /** Construct a topologically-valid linearization from the current forest state. Must be + * topological. */ + std::vector GetLinearization() noexcept + { + /** The output linearization. */ + std::vector ret; + ret.reserve(m_transaction_idxs.Count()); + /** A heap with all chunks (by representative) that can currently be included, sorted by + * chunk feerate. */ + std::vector ready_chunks; + /** Information about chunks: + * - The first value is only used for chunk representatives, and counts the number of + * unmet dependencies this chunk has on other chunks (not including dependencies within + * the chunk itself). + * - The second value is the number of unmet dependencies overall. + */ + std::vector> chunk_deps(m_tx_data.size(), {0, 0}); + /** The set of all chunk representatives. */ + SetType chunk_reps; + /** A list with all transactions within the current chunk that can be included. */ + std::vector ready_tx; + // Populate chunk_deps[c] with the number of {out-of-chunk dependencies, dependencies} the + // child has. + for (TxIdx chl_idx : m_transaction_idxs) { + const auto& chl_data = m_tx_data[chl_idx]; + chunk_deps[chl_idx].second = chl_data.parents.Count(); + auto chl_chunk_rep = chl_data.chunk_rep; + chunk_reps.Set(chl_chunk_rep); + for (auto par_idx : chl_data.parents) { + auto par_chunk_rep = m_tx_data[par_idx].chunk_rep; + chunk_deps[chl_chunk_rep].first += (par_chunk_rep != chl_chunk_rep); + } + } + // Construct a heap with all chunks that have no out-of-chunk dependencies. + /** Comparison function for the heap. */ + auto chunk_cmp_fn = [&](TxIdx a, TxIdx b) noexcept { + auto& chunk_a = m_tx_data[a]; + auto& chunk_b = m_tx_data[b]; + Assume(chunk_a.chunk_rep == a); + Assume(chunk_b.chunk_rep == b); + // First sort by chunk feerate. + if (chunk_a.chunk_setinfo.feerate != chunk_b.chunk_setinfo.feerate) { + return chunk_a.chunk_setinfo.feerate < chunk_b.chunk_setinfo.feerate; + } + // Tie-break by chunk representative. + return a < b; + }; + for (TxIdx chunk_rep : chunk_reps) { + if (chunk_deps[chunk_rep].first == 0) ready_chunks.push_back(chunk_rep); + } + std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); + // Pop chunks off the heap, highest-feerate ones first. + while (!ready_chunks.empty()) { + auto chunk_rep = ready_chunks.front(); + std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); + ready_chunks.pop_back(); + Assume(m_tx_data[chunk_rep].chunk_rep == chunk_rep); + Assume(chunk_deps[chunk_rep].first == 0); + const auto& chunk_txn = m_tx_data[chunk_rep].chunk_setinfo.transactions; + // Build heap of all includable transactions in chunk. + for (TxIdx tx_idx : chunk_txn) { + if (chunk_deps[tx_idx].second == 0) { + ready_tx.push_back(tx_idx); + } + } + Assume(!ready_tx.empty()); + // Pick transactions from the ready queue, append them to linearization, and decrement + // dependency counts. + while (!ready_tx.empty()) { + auto tx_idx = ready_tx.back(); + Assume(chunk_txn[tx_idx]); + ready_tx.pop_back(); + // Append to linearization. + ret.push_back(tx_idx); + // Decrement dependency counts. + auto& tx_data = m_tx_data[tx_idx]; + for (TxIdx chl_idx : tx_data.children) { + auto& chl_data = m_tx_data[chl_idx]; + // Decrement tx dependency count. + Assume(chunk_deps[chl_idx].second > 0); + if (--chunk_deps[chl_idx].second == 0 && chunk_txn[chl_idx]) { + // Child tx has no dependencies left, and is in this chunk. Add it to the tx queue. + ready_tx.push_back(chl_idx); + } + // Decrement chunk dependency count if this is out-of-chunk dependency. + if (chl_data.chunk_rep != chunk_rep) { + Assume(chunk_deps[chl_data.chunk_rep].first > 0); + if (--chunk_deps[chl_data.chunk_rep].first == 0) { + // Child chunk has no dependencies left. Add it to the chunk heap. + ready_chunks.push_back(chl_data.chunk_rep); + std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); + } + } + } + } + } + Assume(ret.size() == m_transaction_idxs.Count()); + return ret; + } + + /** Get the diagram for the current state, which must be topological. Test-only. + * + * The linearization produced by GetLinearization() is always at least as good (in the + * CompareChunks() sense) as this diagram, but may be better. + * + * After an OptimizeStep(), the diagram will always be at least as good as before. Once + * OptimizeStep() returns false, the diagram will be equivalent to that produced by + * GetLinearization(), and optimal. + */ + std::vector GetDiagram() const noexcept + { + std::vector ret; + for (auto tx : m_transaction_idxs) { + if (m_tx_data[tx].chunk_rep == tx) { + ret.push_back(m_tx_data[tx].chunk_setinfo.feerate); + } + } + std::sort(ret.begin(), ret.end(), std::greater{}); + return ret; + } + + /** Determine how much work was performed so far. */ + uint64_t GetCost() const noexcept { return m_cost; } + + /** Verify internal consistency of the data structure. */ + void SanityCheck(const DepGraph& depgraph) const + { + // + // Verify dependency parent/child information, and build list of (active) dependencies. + // + std::vector> expected_dependencies; + std::vector> all_dependencies; + std::vector> active_dependencies; + for (auto parent_idx : depgraph.Positions()) { + for (auto child_idx : depgraph.GetReducedChildren(parent_idx)) { + expected_dependencies.emplace_back(parent_idx, child_idx); + } + } + for (DepIdx dep_idx = 0; dep_idx < m_dep_data.size(); ++dep_idx) { + const auto& dep_data = m_dep_data[dep_idx]; + all_dependencies.emplace_back(dep_data.parent, dep_data.child, dep_idx); + // Also add to active_dependencies if it is active. + if (m_dep_data[dep_idx].active) { + active_dependencies.emplace_back(dep_data.parent, dep_data.child, dep_idx); + } + } + std::sort(expected_dependencies.begin(), expected_dependencies.end()); + std::sort(all_dependencies.begin(), all_dependencies.end()); + assert(expected_dependencies.size() == all_dependencies.size()); + for (size_t i = 0; i < expected_dependencies.size(); ++i) { + assert(expected_dependencies[i] == + std::make_pair(std::get<0>(all_dependencies[i]), + std::get<1>(all_dependencies[i]))); + } + + // + // Verify the chunks against the list of active dependencies + // + for (auto tx_idx: depgraph.Positions()) { + // Only process chunks for now. + if (m_tx_data[tx_idx].chunk_rep == tx_idx) { + const auto& chunk_data = m_tx_data[tx_idx]; + // Verify that transactions in the chunk point back to it. This guarantees + // that chunks are non-overlapping. + for (auto chunk_tx : chunk_data.chunk_setinfo.transactions) { + assert(m_tx_data[chunk_tx].chunk_rep == tx_idx); + } + // Verify the chunk's transaction set: it must contain the representative, and for + // every active dependency, if it contains the parent or child, it must contain + // both. It must have exactly N-1 active dependencies in it, guaranteeing it is + // acyclic. + SetType expected_chunk = SetType::Singleton(tx_idx); + while (true) { + auto old = expected_chunk; + size_t active_dep_count{0}; + for (const auto& [par, chl, _dep] : active_dependencies) { + if (expected_chunk[par] || expected_chunk[chl]) { + expected_chunk.Set(par); + expected_chunk.Set(chl); + ++active_dep_count; + } + } + if (old == expected_chunk) { + assert(expected_chunk.Count() == active_dep_count + 1); + break; + } + } + assert(chunk_data.chunk_setinfo.transactions == expected_chunk); + // Verify the chunk's feerate. + assert(chunk_data.chunk_setinfo.feerate == + depgraph.FeeRate(chunk_data.chunk_setinfo.transactions)); + } + } + + // + // Verify other transaction data. + // + assert(m_transaction_idxs == depgraph.Positions()); + for (auto tx_idx : m_transaction_idxs) { + const auto& tx_data = m_tx_data[tx_idx]; + // Verify it has a valid chunk representative, and that chunk includes this + // transaction. + assert(m_tx_data[tx_data.chunk_rep].chunk_rep == tx_data.chunk_rep); + assert(m_tx_data[tx_data.chunk_rep].chunk_setinfo.transactions[tx_idx]); + // Verify parents/children. + assert(tx_data.parents == depgraph.GetReducedParents(tx_idx)); + assert(tx_data.children == depgraph.GetReducedChildren(tx_idx)); + // Verify list of child dependencies. + std::vector expected_child_deps; + for (const auto& [par_idx, chl_idx, dep_idx] : all_dependencies) { + if (tx_idx == par_idx) { + assert(tx_data.children[chl_idx]); + expected_child_deps.push_back(dep_idx); + } + } + std::sort(expected_child_deps.begin(), expected_child_deps.end()); + auto child_deps_copy = tx_data.child_deps; + std::sort(child_deps_copy.begin(), child_deps_copy.end()); + assert(expected_child_deps == child_deps_copy); + } + + // + // Verify active dependencies' top_setinfo. + // + for (const auto& [par_idx, chl_idx, dep_idx] : active_dependencies) { + const auto& dep_data = m_dep_data[dep_idx]; + // Verify the top_info's transactions: it must contain the parent, and for every + // active dependency, except dep_idx itself, if it contains the parent or child, it + // must contain both. + SetType expected_top = SetType::Singleton(par_idx); + while (true) { + auto old = expected_top; + for (const auto& [par2_idx, chl2_idx, dep2_idx] : active_dependencies) { + if (dep2_idx != dep_idx && (expected_top[par2_idx] || expected_top[chl2_idx])) { + expected_top.Set(par2_idx); + expected_top.Set(chl2_idx); + } + } + if (old == expected_top) break; + } + assert(!expected_top[chl_idx]); + assert(dep_data.top_setinfo.transactions == expected_top); + // Verify the top_info's feerate. + assert(dep_data.top_setinfo.feerate == + depgraph.FeeRate(dep_data.top_setinfo.transactions)); + } + } +}; + + /** Class encapsulating the state needed to perform search for good candidate sets. * * It is initialized for an entire DepGraph, and parts of the graph can be dropped by calling diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index c1ee78028d2..6f472442e94 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -27,23 +27,23 @@ * +-----------------------+ * | SearchCandidateFinder | <<---------------------\ * +-----------------------+ | - * | +-----------+ - * | | Linearize | - * | +-----------+ - * | +-------------------------+ | | - * | | AncestorCandidateFinder | <<--------/ | - * | +-------------------------+ | - * | | ^ | ^^ PRODUCTION CODE - * | | | | || + * | +-----------+ +---------------------+ + * | | Linearize | | SpanningForestState | + * | +-----------+ +---------------------+ + * | +-------------------------+ | | | + * | | AncestorCandidateFinder | <<--------/ | | + * | +-------------------------+ | | + * | | ^ | ^^ PRODUCTION CODE | + * | | | | || | * ============================================================================================== - * | | | | || - * | clusterlin_ancestor_finder* | | vv TEST CODE - * | | | - * |-clusterlin_search_finder* | |-clusterlin_linearize* - * | | | - * v | v - * +-----------------------+ | +-----------------+ - * | SimpleCandidateFinder | <<-------------------| SimpleLinearize | + * | | | | || | + * | clusterlin_ancestor_finder* | | vv TEST CODE | + * | | | | + * |-clusterlin_search_finder* | |-clusterlin_linearize* | + * | | | | + * v | v clusterlin_sfl--| + * +-----------------------+ | +-----------------+ | + * | SimpleCandidateFinder | <<-------------------| SimpleLinearize |<----------------/ * +-----------------------+ | +-----------------+ * | | | * +-------------------/ | @@ -1169,6 +1169,80 @@ FUZZ_TARGET(clusterlin_simple_linearize) } } +FUZZ_TARGET(clusterlin_sfl) +{ + // Verify the individual steps of the SFL algorithm. + + SpanReader reader(buffer); + DepGraph depgraph; + uint8_t flags{1}; + uint64_t rng_seed{0}; + try { + reader >> rng_seed >> flags >> Using(depgraph); + } catch (const std::ios_base::failure&) {} + if (depgraph.TxCount() <= 1) return; + InsecureRandomContext rng(rng_seed); + /** Whether to make the depgraph connected. */ + const bool make_connected = flags & 1; + + // Initialize SFL state. + if (make_connected) MakeConnected(depgraph); + SpanningForestState sfl(depgraph); + + // Function to test the state. + std::vector last_diagram; + auto test_fn = [&](bool is_optimal = false) { + if (rng.randbits(4) == 0) { + // Perform sanity checks from time to time (too computationally expensive to do after + // every step). + sfl.SanityCheck(depgraph); + } + auto diagram = sfl.GetDiagram(); + if (rng.randbits(4) == 0) { + // Verify that the diagram of GetLinearization() is at least as good as GetDiagram(), + // from time to time. + auto lin = sfl.GetLinearization(); + auto lin_diagram = ChunkLinearization(depgraph, lin); + auto cmp_lin = CompareChunks(lin_diagram, diagram); + assert(cmp_lin >= 0); + // If we're in an allegedly optimal state, they must match. + if (is_optimal) assert(cmp_lin == 0); + } + // Verify that subsequent calls to GetDiagram() never get worse/incomparable. + if (!last_diagram.empty()) { + auto cmp = CompareChunks(diagram, last_diagram); + assert(cmp >= 0); + } + last_diagram = std::move(diagram); + }; + + // Make SFL state topological. + sfl.MakeTopological(); + + // Loop until optimal. + while (true) { + test_fn(); + if (!sfl.OptimizeStep()) break; + } + test_fn(/*is_optimal=*/true); + + // The result must be as good as SimpleLinearize. + auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS / 10); + auto simple_diagram = ChunkLinearization(depgraph, simple_linearization); + auto simple_cmp = CompareChunks(last_diagram, simple_diagram); + assert(simple_cmp >= 0); + if (simple_optimal) assert(simple_cmp == 0); + + // We can compare with any arbitrary linearization, and the diagram must be at least as good as + // each. + for (int i = 0; i < 10; ++i) { + auto read_lin = ReadLinearization(depgraph, reader); + auto read_diagram = ChunkLinearization(depgraph, read_lin); + auto cmp = CompareChunks(last_diagram, read_diagram); + assert(cmp >= 0); + } +} + FUZZ_TARGET(clusterlin_linearize) { // Verify the behavior of Linearize(). From da48ed9f348a8a93679fd623838059f04ac9e53c Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 10 Dec 2025 14:22:08 -0500 Subject: [PATCH 04/11] clusterlin: ReadLinearization for non-topological (tests) Rather than using an ad-hoc no-dependency copy of the graph when a potentially non-topological linearization is needed in the clusterlin fuzz test, add this directly as a feature in ReadLinearization(). This is preparation for a later commit where another use for such a function is added. --- src/test/fuzz/cluster_linearize.cpp | 41 ++++++++++------------------- 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index 6f472442e94..fa3e49b5ef7 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -328,18 +328,24 @@ SetType ReadTopologicalSet(const DepGraph& depgraph, const SetType& tod /** Given a dependency graph, construct any valid linearization for it, reading from a SpanReader. */ template -std::vector ReadLinearization(const DepGraph& depgraph, SpanReader& reader) +std::vector ReadLinearization(const DepGraph& depgraph, SpanReader& reader, bool topological=true) { std::vector linearization; TestBitSet todo = depgraph.Positions(); - // In every iteration one topologically-valid transaction is appended to linearization. + // In every iteration one transaction is appended to linearization. while (todo.Any()) { - // Compute the set of transactions with no not-yet-included ancestors. + // Compute the set of transactions to select from. TestBitSet potential_next; - for (auto j : todo) { - if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) { - potential_next.Set(j); + if (topological) { + // Find all transactions with no not-yet-included ancestors. + for (auto j : todo) { + if ((depgraph.Ancestors(j) & todo) == TestBitSet::Singleton(j)) { + potential_next.Set(j); + } } + } else { + // Allow any element to be selected next, regardless of topology. + potential_next = todo; } // There must always be one (otherwise there is a cycle in the graph). assert(potential_next.Any()); @@ -1489,33 +1495,14 @@ FUZZ_TARGET(clusterlin_fix_linearization) } catch (const std::ios_base::failure&) {} // Construct an arbitrary linearization (not necessarily topological for depgraph). - std::vector linearization; - /** Which transactions of depgraph are yet to be included in linearization. */ - TestBitSet todo = depgraph.Positions(); - while (todo.Any()) { - // Read a number from the fuzz input in range [0, todo.Count()). - uint64_t val{0}; - try { - reader >> VARINT(val); - } catch (const std::ios_base::failure&) {} - val %= todo.Count(); - // Find the val'th element in todo, remove it from todo, and append it to linearization. - for (auto idx : todo) { - if (val == 0) { - linearization.push_back(idx); - todo.Reset(idx); - break; - } - --val; - } - } + std::vector linearization = ReadLinearization(depgraph, reader, /*topological=*/false); assert(linearization.size() == depgraph.TxCount()); // Determine what prefix of linearization is topological, i.e., the position of the first entry // in linearization which corresponds to a transaction that is not preceded by all its // ancestors. size_t topo_prefix = 0; - todo = depgraph.Positions(); + auto todo = depgraph.Positions(); while (topo_prefix < linearization.size()) { DepGraphIndex idx = linearization[topo_prefix]; todo.Reset(idx); From 6a8fa821b80cf71e199b085c60d77f6ca533f6ee Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 10 Dec 2025 15:03:23 -0500 Subject: [PATCH 05/11] clusterlin: add support for loading existing linearization (feature) --- src/cluster_linearize.h | 27 ++++++++++++++++++++++++++- src/test/fuzz/cluster_linearize.cpp | 22 ++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 0ea9de092d8..c01fb8644cf 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -777,6 +777,15 @@ public: * - Do a downwards merge of B, if possible. If so, repeat the same with the merged result. * - Output the chunks from high to low feerate, each internally sorted topologically. * + * Instead of performing merges arbitrarily to make the initial state topological, it is possible + * to do so guided by an existing linearization. This has the advantage that the state's would-be + * output linearization is immediately as good as the existing linearization it was based on: + * - Start with all dependencies inactive. + * - For each transaction t in the existing linearization: + * - Find the chunk C that transaction is in (which will be singleton). + * - Do an upwards merge of C, if possible. If so, repeat the same with the merged result. + * No downwards merges are needed in this case. + * * What remains to be specified are a number of heuristics: * * - How to decide which chunks to merge: @@ -1124,7 +1133,23 @@ public: } } - /** Make state topological. Can be called after constructing. */ + /** Load an existing linearization. Must be called immediately after constructor. The result is + * topological if the linearization is valid. Otherwise, MakeTopological still needs to be + * called. */ + void LoadLinearization(std::span old_linearization) noexcept + { + // Add transactions one by one, in order of existing linearization. + for (DepGraphIndex tx : old_linearization) { + auto chunk_rep = m_tx_data[tx].chunk_rep; + // Merge the chunk upwards, as long as merging succeeds. + while (true) { + chunk_rep = MergeStep(chunk_rep); + if (chunk_rep == TxIdx(-1)) break; + } + } + } + + /** Make state topological. Can be called after constructing, or after LoadLinearization. */ void MakeTopological() noexcept { while (true) { diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index fa3e49b5ef7..86e85d1c505 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -1190,6 +1190,10 @@ FUZZ_TARGET(clusterlin_sfl) InsecureRandomContext rng(rng_seed); /** Whether to make the depgraph connected. */ const bool make_connected = flags & 1; + /** Whether to load some input linearization into the state. */ + const bool load_linearization = flags & 2; + /** Whether that input linearization is topological. */ + const bool load_topological = load_linearization && (flags & 4); // Initialize SFL state. if (make_connected) MakeConnected(depgraph); @@ -1222,8 +1226,22 @@ FUZZ_TARGET(clusterlin_sfl) last_diagram = std::move(diagram); }; - // Make SFL state topological. - sfl.MakeTopological(); + if (load_linearization) { + auto input_lin = ReadLinearization(depgraph, reader, load_topological); + sfl.LoadLinearization(input_lin); + if (load_topological) { + // The diagram of the loaded linearization forms an initial lower bound on future + // diagrams. + last_diagram = ChunkLinearization(depgraph, input_lin); + } else { + // The input linearization may have been non-topological, so invoke MakeTopological to + // fix it still. + sfl.MakeTopological(); + } + } else { + // Invoke MakeTopological to create an initial from-scratch topological state. + sfl.MakeTopological(); + } // Loop until optimal. while (true) { From 3efc94d6564deca4e5aaf5219adb71302f522657 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 23 Oct 2025 19:15:21 -0400 Subject: [PATCH 06/11] clusterlin: replace cluster linearization with SFL (feature) This replaces the existing LIMO linearization algorithm (which internally uses ancestor set finding and candidate set finding) with the much more performant spanning-forest linearization algorithm. This removes the old candidate-set search algorithm, and several of its tests, benchmarks, and needed utility code. The worst case time per cost is similar to the previous algorithm, so ACCEPTABLE_ITERS is unchanged. --- src/bench/cluster_linearize.cpp | 199 --------- src/cluster_linearize.h | 579 +-------------------------- src/test/cluster_linearize_tests.cpp | 6 +- src/test/fuzz/cluster_linearize.cpp | 245 ++---------- src/test/util/cluster_linearize.h | 35 +- src/txgraph.cpp | 6 +- test/functional/mempool_packages.py | 5 +- 7 files changed, 81 insertions(+), 994 deletions(-) diff --git a/src/bench/cluster_linearize.cpp b/src/bench/cluster_linearize.cpp index a56566c19f0..4c3203804c7 100644 --- a/src/bench/cluster_linearize.cpp +++ b/src/bench/cluster_linearize.cpp @@ -18,21 +18,6 @@ using namespace util::hex_literals; namespace { -/** Construct a linear graph. These are pessimal for AncestorCandidateFinder, as they maximize - * the number of ancestor set feerate updates. The best ancestor set is always the topmost - * remaining transaction, whose removal requires updating all remaining transactions' ancestor - * set feerates. */ -template -DepGraph MakeLinearGraph(DepGraphIndex ntx) -{ - DepGraph depgraph; - for (DepGraphIndex i = 0; i < ntx; ++i) { - depgraph.AddTransaction({-int32_t(i), 1}); - if (i > 0) depgraph.AddDependencies(SetType::Singleton(i - 1), i); - } - return depgraph; -} - /** Construct a wide graph (one root, with N-1 children that are otherwise unrelated, with * increasing feerates). These graphs are pessimal for the LIMO step in Linearize, because * rechunking is needed after every candidate (the last transaction gets picked every time). @@ -48,136 +33,6 @@ DepGraph MakeWideGraph(DepGraphIndex ntx) return depgraph; } -// Construct a difficult graph. These need at least sqrt(2^(n-1)) iterations in the implemented -// algorithm (purely empirically determined). -template -DepGraph MakeHardGraph(DepGraphIndex ntx) -{ - DepGraph depgraph; - for (DepGraphIndex i = 0; i < ntx; ++i) { - if (ntx & 1) { - // Odd cluster size. - // - // Mermaid diagram code for the resulting cluster for 11 transactions: - // ```mermaid - // graph BT - // T0["T0: 1/2"];T1["T1: 14/2"];T2["T2: 6/1"];T3["T3: 5/1"];T4["T4: 7/1"]; - // T5["T5: 5/1"];T6["T6: 7/1"];T7["T7: 5/1"];T8["T8: 7/1"];T9["T9: 5/1"]; - // T10["T10: 7/1"]; - // T1-->T0;T1-->T2;T3-->T2;T4-->T3;T4-->T5;T6-->T5;T4-->T7;T8-->T7;T4-->T9;T10-->T9; - // ``` - if (i == 0) { - depgraph.AddTransaction({1, 2}); - } else if (i == 1) { - depgraph.AddTransaction({14, 2}); - depgraph.AddDependencies(SetType::Singleton(0), 1); - } else if (i == 2) { - depgraph.AddTransaction({6, 1}); - depgraph.AddDependencies(SetType::Singleton(2), 1); - } else if (i == 3) { - depgraph.AddTransaction({5, 1}); - depgraph.AddDependencies(SetType::Singleton(2), 3); - } else if ((i & 1) == 0) { - depgraph.AddTransaction({7, 1}); - depgraph.AddDependencies(SetType::Singleton(i - 1), i); - } else { - depgraph.AddTransaction({5, 1}); - depgraph.AddDependencies(SetType::Singleton(i), 4); - } - } else { - // Even cluster size. - // - // Mermaid diagram code for the resulting cluster for 10 transactions: - // ```mermaid - // graph BT - // T0["T0: 1"];T1["T1: 3"];T2["T2: 1"];T3["T3: 4"];T4["T4: 0"];T5["T5: 4"];T6["T6: 0"]; - // T7["T7: 4"];T8["T8: 0"];T9["T9: 4"]; - // T1-->T0;T2-->T0;T3-->T2;T3-->T4;T5-->T4;T3-->T6;T7-->T6;T3-->T8;T9-->T8; - // ``` - if (i == 0) { - depgraph.AddTransaction({1, 1}); - } else if (i == 1) { - depgraph.AddTransaction({3, 1}); - depgraph.AddDependencies(SetType::Singleton(0), 1); - } else if (i == 2) { - depgraph.AddTransaction({1, 1}); - depgraph.AddDependencies(SetType::Singleton(0), 2); - } else if (i & 1) { - depgraph.AddTransaction({4, 1}); - depgraph.AddDependencies(SetType::Singleton(i - 1), i); - } else { - depgraph.AddTransaction({0, 1}); - depgraph.AddDependencies(SetType::Singleton(i), 3); - } - } - } - return depgraph; -} - -/** Benchmark that does search-based candidate finding with a specified number of iterations. - * - * Its goal is measuring how much time every additional search iteration in linearization costs, - * by running with a low and a high count, subtracting the results, and divided by the number - * iterations difference. - */ -template -void BenchLinearizeWorstCase(DepGraphIndex ntx, benchmark::Bench& bench, uint64_t iter_limit) -{ - const auto depgraph = MakeHardGraph(ntx); - uint64_t rng_seed = 0; - bench.run([&] { - SearchCandidateFinder finder(depgraph, rng_seed++); - auto [candidate, iters_performed] = finder.FindCandidateSet(iter_limit, {}); - assert(iters_performed == iter_limit); - }); -} - -/** Benchmark for linearization improvement of a trivial linear graph using just ancestor sort. - * - * Its goal is measuring how much time linearization may take without any search iterations. - * - * If P is the benchmarked per-iteration count (obtained by running BenchLinearizeWorstCase for a - * high and a low iteration count, subtracting them, and dividing by the difference in count), and - * N is the resulting time of BenchLinearizeNoItersWorstCase*, then an invocation of Linearize with - * max_iterations=m should take no more than roughly N+m*P time. This may however be an - * overestimate, as the worst cases do not coincide (the ones that are worst for linearization - * without any search happen to be ones that do not need many search iterations). - * - * This benchmark exercises a worst case for AncestorCandidateFinder, but for which improvement is - * cheap. - */ -template -void BenchLinearizeNoItersWorstCaseAnc(DepGraphIndex ntx, benchmark::Bench& bench) -{ - const auto depgraph = MakeLinearGraph(ntx); - uint64_t rng_seed = 0; - std::vector old_lin(ntx); - for (DepGraphIndex i = 0; i < ntx; ++i) old_lin[i] = i; - bench.run([&] { - Linearize(depgraph, /*max_iterations=*/0, rng_seed++, old_lin); - }); -} - -/** Benchmark for linearization improvement of a trivial wide graph using just ancestor sort. - * - * Its goal is measuring how much time improving a linearization may take without any search - * iterations, similar to the previous function. - * - * This benchmark exercises a worst case for improving an existing linearization, but for which - * AncestorCandidateFinder is cheap. - */ -template -void BenchLinearizeNoItersWorstCaseLIMO(DepGraphIndex ntx, benchmark::Bench& bench) -{ - const auto depgraph = MakeWideGraph(ntx); - uint64_t rng_seed = 0; - std::vector old_lin(ntx); - for (DepGraphIndex i = 0; i < ntx; ++i) old_lin[i] = i; - bench.run([&] { - Linearize(depgraph, /*max_iterations=*/0, rng_seed++, old_lin); - }); -} - template void BenchPostLinearizeWorstCase(DepGraphIndex ntx, benchmark::Bench& bench) { @@ -257,33 +112,6 @@ void BenchLinearizeOptimallyPerCost(benchmark::Bench& bench, const std::string& } // namespace -static void Linearize16TxWorstCase20Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(16, bench, 20); } -static void Linearize16TxWorstCase120Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(16, bench, 120); } -static void Linearize32TxWorstCase5000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(32, bench, 5000); } -static void Linearize32TxWorstCase15000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(32, bench, 15000); } -static void Linearize48TxWorstCase5000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(48, bench, 5000); } -static void Linearize48TxWorstCase15000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(48, bench, 15000); } -static void Linearize64TxWorstCase5000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(64, bench, 5000); } -static void Linearize64TxWorstCase15000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(64, bench, 15000); } -static void Linearize75TxWorstCase5000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(75, bench, 5000); } -static void Linearize75TxWorstCase15000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(75, bench, 15000); } -static void Linearize99TxWorstCase5000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(99, bench, 5000); } -static void Linearize99TxWorstCase15000Iters(benchmark::Bench& bench) { BenchLinearizeWorstCase>(99, bench, 15000); } - -static void LinearizeNoIters16TxWorstCaseAnc(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseAnc>(16, bench); } -static void LinearizeNoIters32TxWorstCaseAnc(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseAnc>(32, bench); } -static void LinearizeNoIters48TxWorstCaseAnc(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseAnc>(48, bench); } -static void LinearizeNoIters64TxWorstCaseAnc(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseAnc>(64, bench); } -static void LinearizeNoIters75TxWorstCaseAnc(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseAnc>(75, bench); } -static void LinearizeNoIters99TxWorstCaseAnc(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseAnc>(99, bench); } - -static void LinearizeNoIters16TxWorstCaseLIMO(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseLIMO>(16, bench); } -static void LinearizeNoIters32TxWorstCaseLIMO(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseLIMO>(32, bench); } -static void LinearizeNoIters48TxWorstCaseLIMO(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseLIMO>(48, bench); } -static void LinearizeNoIters64TxWorstCaseLIMO(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseLIMO>(64, bench); } -static void LinearizeNoIters75TxWorstCaseLIMO(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseLIMO>(75, bench); } -static void LinearizeNoIters99TxWorstCaseLIMO(benchmark::Bench& bench) { BenchLinearizeNoItersWorstCaseLIMO>(99, bench); } - static void PostLinearize16TxWorstCase(benchmark::Bench& bench) { BenchPostLinearizeWorstCase>(16, bench); } static void PostLinearize32TxWorstCase(benchmark::Bench& bench) { BenchPostLinearizeWorstCase>(32, bench); } static void PostLinearize48TxWorstCase(benchmark::Bench& bench) { BenchPostLinearizeWorstCase>(48, bench); } @@ -350,33 +178,6 @@ static void LinearizeOptimallyPerCost(benchmark::Bench& bench) BenchLinearizeOptimallyPerCost(bench, "LinearizeOptimallySyntheticPerCost", CLUSTERS_SYNTHETIC); } -BENCHMARK(Linearize16TxWorstCase20Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize16TxWorstCase120Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize32TxWorstCase5000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize32TxWorstCase15000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize48TxWorstCase5000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize48TxWorstCase15000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize64TxWorstCase5000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize64TxWorstCase15000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize75TxWorstCase5000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize75TxWorstCase15000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize99TxWorstCase5000Iters, benchmark::PriorityLevel::HIGH); -BENCHMARK(Linearize99TxWorstCase15000Iters, benchmark::PriorityLevel::HIGH); - -BENCHMARK(LinearizeNoIters16TxWorstCaseAnc, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters32TxWorstCaseAnc, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters48TxWorstCaseAnc, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters64TxWorstCaseAnc, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters75TxWorstCaseAnc, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters99TxWorstCaseAnc, benchmark::PriorityLevel::HIGH); - -BENCHMARK(LinearizeNoIters16TxWorstCaseLIMO, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters32TxWorstCaseLIMO, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters48TxWorstCaseLIMO, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters64TxWorstCaseLIMO, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters75TxWorstCaseLIMO, benchmark::PriorityLevel::HIGH); -BENCHMARK(LinearizeNoIters99TxWorstCaseLIMO, benchmark::PriorityLevel::HIGH); - BENCHMARK(PostLinearize16TxWorstCase, benchmark::PriorityLevel::HIGH); BENCHMARK(PostLinearize32TxWorstCase, benchmark::PriorityLevel::HIGH); BENCHMARK(PostLinearize48TxWorstCase, benchmark::PriorityLevel::HIGH); diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index c01fb8644cf..64610ee751f 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -411,13 +411,6 @@ struct SetInfo return {transactions - other.transactions, feerate - other.feerate}; } - /** Construct a new SetInfo equal to this, with more transactions added (which may overlap - * with the existing transactions in the SetInfo). */ - [[nodiscard]] SetInfo Add(const DepGraph& depgraph, const SetType& txn) const noexcept - { - return {transactions | txn, feerate + depgraph.FeeRate(txn - transactions)}; - } - /** Swap two SetInfo objects. */ friend void swap(SetInfo& a, SetInfo& b) noexcept { @@ -576,108 +569,6 @@ public: } }; -/** Class encapsulating the state needed to find the best remaining ancestor set. - * - * It is initialized for an entire DepGraph, and parts of the graph can be dropped by calling - * MarkDone. - * - * As long as any part of the graph remains, FindCandidateSet() can be called which will return a - * SetInfo with the highest-feerate ancestor set that remains (an ancestor set is a single - * transaction together with all its remaining ancestors). - */ -template -class AncestorCandidateFinder -{ - /** Internal dependency graph. */ - const DepGraph& m_depgraph; - /** Which transaction are left to include. */ - SetType m_todo; - /** Precomputed ancestor-set feerates (only kept up-to-date for indices in m_todo). */ - std::vector m_ancestor_set_feerates; - -public: - /** Construct an AncestorCandidateFinder for a given cluster. - * - * Complexity: O(N^2) where N=depgraph.TxCount(). - */ - AncestorCandidateFinder(const DepGraph& depgraph LIFETIMEBOUND) noexcept : - m_depgraph(depgraph), - m_todo{depgraph.Positions()}, - m_ancestor_set_feerates(depgraph.PositionRange()) - { - // Precompute ancestor-set feerates. - for (DepGraphIndex i : m_depgraph.Positions()) { - /** The remaining ancestors for transaction i. */ - SetType anc_to_add = m_depgraph.Ancestors(i); - FeeFrac anc_feerate; - // Reuse accumulated feerate from first ancestor, if usable. - Assume(anc_to_add.Any()); - DepGraphIndex first = anc_to_add.First(); - if (first < i) { - anc_feerate = m_ancestor_set_feerates[first]; - Assume(!anc_feerate.IsEmpty()); - anc_to_add -= m_depgraph.Ancestors(first); - } - // Add in other ancestors (which necessarily include i itself). - Assume(anc_to_add[i]); - anc_feerate += m_depgraph.FeeRate(anc_to_add); - // Store the result. - m_ancestor_set_feerates[i] = anc_feerate; - } - } - - /** Remove a set of transactions from the set of to-be-linearized ones. - * - * The same transaction may not be MarkDone()'d twice. - * - * Complexity: O(N*M) where N=depgraph.TxCount(), M=select.Count(). - */ - void MarkDone(SetType select) noexcept - { - Assume(select.Any()); - Assume(select.IsSubsetOf(m_todo)); - m_todo -= select; - for (auto i : select) { - auto feerate = m_depgraph.FeeRate(i); - for (auto j : m_depgraph.Descendants(i) & m_todo) { - m_ancestor_set_feerates[j] -= feerate; - } - } - } - - /** Check whether any unlinearized transactions remain. */ - bool AllDone() const noexcept - { - return m_todo.None(); - } - - /** Count the number of remaining unlinearized transactions. */ - DepGraphIndex NumRemaining() const noexcept - { - return m_todo.Count(); - } - - /** Find the best (highest-feerate, smallest among those in case of a tie) ancestor set - * among the remaining transactions. Requires !AllDone(). - * - * Complexity: O(N) where N=depgraph.TxCount(); - */ - SetInfo FindCandidateSet() const noexcept - { - Assume(!AllDone()); - std::optional best; - for (auto i : m_todo) { - if (best.has_value()) { - Assume(!m_ancestor_set_feerates[i].IsEmpty()); - if (!(m_ancestor_set_feerates[i] > m_ancestor_set_feerates[*best])) continue; - } - best = i; - } - Assume(best.has_value()); - return {m_depgraph.Ancestors(*best) & m_todo, m_ancestor_set_feerates[*best]}; - } -}; - /** Class to represent the internal state of the spanning-forest linearization (SFL) algorithm. * * At all times, each dependency is marked as either "active" or "inactive". The subset of active @@ -1456,391 +1347,10 @@ public: } }; - -/** Class encapsulating the state needed to perform search for good candidate sets. - * - * It is initialized for an entire DepGraph, and parts of the graph can be dropped by calling - * MarkDone(). - * - * As long as any part of the graph remains, FindCandidateSet() can be called to perform a search - * over the set of topologically-valid subsets of that remainder, with a limit on how many - * combinations are tried. - */ -template -class SearchCandidateFinder -{ - /** Internal RNG. */ - InsecureRandomContext m_rng; - /** m_sorted_to_original[i] is the original position that sorted transaction position i had. */ - std::vector m_sorted_to_original; - /** m_original_to_sorted[i] is the sorted position original transaction position i has. */ - std::vector m_original_to_sorted; - /** Internal dependency graph for the cluster (with transactions in decreasing individual - * feerate order). */ - DepGraph m_sorted_depgraph; - /** Which transactions are left to do (indices in m_sorted_depgraph's order). */ - SetType m_todo; - - /** Given a set of transactions with sorted indices, get their original indices. */ - SetType SortedToOriginal(const SetType& arg) const noexcept - { - SetType ret; - for (auto pos : arg) ret.Set(m_sorted_to_original[pos]); - return ret; - } - - /** Given a set of transactions with original indices, get their sorted indices. */ - SetType OriginalToSorted(const SetType& arg) const noexcept - { - SetType ret; - for (auto pos : arg) ret.Set(m_original_to_sorted[pos]); - return ret; - } - -public: - /** Construct a candidate finder for a graph. - * - * @param[in] depgraph Dependency graph for the to-be-linearized cluster. - * @param[in] rng_seed A random seed to control the search order. - * - * Complexity: O(N^2) where N=depgraph.Count(). - */ - SearchCandidateFinder(const DepGraph& depgraph, uint64_t rng_seed) noexcept : - m_rng(rng_seed), - m_sorted_to_original(depgraph.TxCount()), - m_original_to_sorted(depgraph.PositionRange()) - { - // Determine reordering mapping, by sorting by decreasing feerate. Unused positions are - // not included, as they will never be looked up anyway. - DepGraphIndex sorted_pos{0}; - for (auto i : depgraph.Positions()) { - m_sorted_to_original[sorted_pos++] = i; - } - std::sort(m_sorted_to_original.begin(), m_sorted_to_original.end(), [&](auto a, auto b) { - auto feerate_cmp = depgraph.FeeRate(a) <=> depgraph.FeeRate(b); - if (feerate_cmp == 0) return a < b; - return feerate_cmp > 0; - }); - // Compute reverse mapping. - for (DepGraphIndex i = 0; i < m_sorted_to_original.size(); ++i) { - m_original_to_sorted[m_sorted_to_original[i]] = i; - } - // Compute reordered dependency graph. - m_sorted_depgraph = DepGraph(depgraph, m_original_to_sorted, m_sorted_to_original.size()); - m_todo = m_sorted_depgraph.Positions(); - } - - /** Check whether any unlinearized transactions remain. */ - bool AllDone() const noexcept - { - return m_todo.None(); - } - - /** Find a high-feerate topologically-valid subset of what remains of the cluster. - * Requires !AllDone(). - * - * @param[in] max_iterations The maximum number of optimization steps that will be performed. - * @param[in] best A set/feerate pair with an already-known good candidate. This may - * be empty. - * @return A pair of: - * - The best (highest feerate, smallest size as tiebreaker) - * topologically valid subset (and its feerate) that was - * encountered during search. It will be at least as good as the - * best passed in (if not empty). - * - The number of optimization steps that were performed. This will - * be <= max_iterations. If strictly < max_iterations, the - * returned subset is optimal. - * - * Complexity: possibly O(N * min(max_iterations, sqrt(2^N))) where N=depgraph.TxCount(). - */ - std::pair, uint64_t> FindCandidateSet(uint64_t max_iterations, SetInfo best) noexcept - { - Assume(!AllDone()); - - // Convert the provided best to internal sorted indices. - best.transactions = OriginalToSorted(best.transactions); - - /** Type for work queue items. */ - struct WorkItem - { - /** Set of transactions definitely included (and its feerate). This must be a subset - * of m_todo, and be topologically valid (includes all in-m_todo ancestors of - * itself). */ - SetInfo inc; - /** Set of undecided transactions. This must be a subset of m_todo, and have no overlap - * with inc. The set (inc | und) must be topologically valid. */ - SetType und; - /** (Only when inc is not empty) The best feerate of any superset of inc that is also a - * subset of (inc | und), without requiring it to be topologically valid. It forms a - * conservative upper bound on how good a set this work item can give rise to. - * Transactions whose feerate is below best's are ignored when determining this value, - * which means it may technically be an underestimate, but if so, this work item - * cannot result in something that beats best anyway. */ - FeeFrac pot_feerate; - - /** Construct a new work item. */ - WorkItem(SetInfo&& i, SetType&& u, FeeFrac&& p_f) noexcept : - inc(std::move(i)), und(std::move(u)), pot_feerate(std::move(p_f)) - { - Assume(pot_feerate.IsEmpty() == inc.feerate.IsEmpty()); - } - - /** Swap two WorkItems. */ - void Swap(WorkItem& other) noexcept - { - swap(inc, other.inc); - swap(und, other.und); - swap(pot_feerate, other.pot_feerate); - } - }; - - /** The queue of work items. */ - VecDeque queue; - queue.reserve(std::max(256, 2 * m_todo.Count())); - - // Create initial entries per connected component of m_todo. While clusters themselves are - // generally connected, this is not necessarily true after some parts have already been - // removed from m_todo. Without this, effort can be wasted on searching "inc" sets that - // span multiple components. - auto to_cover = m_todo; - do { - auto component = m_sorted_depgraph.FindConnectedComponent(to_cover); - to_cover -= component; - // If best is not provided, set it to the first component, so that during the work - // processing loop below, and during the add_fn/split_fn calls, we do not need to deal - // with the best=empty case. - if (best.feerate.IsEmpty()) best = SetInfo(m_sorted_depgraph, component); - queue.emplace_back(/*inc=*/SetInfo{}, - /*und=*/std::move(component), - /*pot_feerate=*/FeeFrac{}); - } while (to_cover.Any()); - - /** Local copy of the iteration limit. */ - uint64_t iterations_left = max_iterations; - - /** The set of transactions in m_todo which have feerate > best's. */ - SetType imp = m_todo; - while (imp.Any()) { - DepGraphIndex check = imp.Last(); - if (m_sorted_depgraph.FeeRate(check) >> best.feerate) break; - imp.Reset(check); - } - - /** Internal function to add an item to the queue of elements to explore if there are any - * transactions left to split on, possibly improving it before doing so, and to update - * best/imp. - * - * - inc: the "inc" value for the new work item (must be topological). - * - und: the "und" value for the new work item ((inc | und) must be topological). - */ - auto add_fn = [&](SetInfo inc, SetType und) noexcept { - /** SetInfo object with the set whose feerate will become the new work item's - * pot_feerate. It starts off equal to inc. */ - auto pot = inc; - if (!inc.feerate.IsEmpty()) { - // Add entries to pot. We iterate over all undecided transactions whose feerate is - // higher than best. While undecided transactions of lower feerate may improve pot, - // the resulting pot feerate cannot possibly exceed best's (and this item will be - // skipped in split_fn anyway). - for (auto pos : imp & und) { - // Determine if adding transaction pos to pot (ignoring topology) would improve - // it. If not, we're done updating pot. This relies on the fact that - // m_sorted_depgraph, and thus the transactions iterated over, are in decreasing - // individual feerate order. - if (!(m_sorted_depgraph.FeeRate(pos) >> pot.feerate)) break; - pot.Set(m_sorted_depgraph, pos); - } - - // The "jump ahead" optimization: whenever pot has a topologically-valid subset, - // that subset can be added to inc. Any subset of (pot - inc) has the property that - // its feerate exceeds that of any set compatible with this work item (superset of - // inc, subset of (inc | und)). Thus, if T is a topological subset of pot, and B is - // the best topologically-valid set compatible with this work item, and (T - B) is - // non-empty, then (T | B) is better than B and also topological. This is in - // contradiction with the assumption that B is best. Thus, (T - B) must be empty, - // or T must be a subset of B. - // - // See https://delvingbitcoin.org/t/how-to-linearize-your-cluster/303 section 2.4. - const auto init_inc = inc.transactions; - for (auto pos : pot.transactions - inc.transactions) { - // If the transaction's ancestors are a subset of pot, we can add it together - // with its ancestors to inc. Just update the transactions here; the feerate - // update happens below. - auto anc_todo = m_sorted_depgraph.Ancestors(pos) & m_todo; - if (anc_todo.IsSubsetOf(pot.transactions)) inc.transactions |= anc_todo; - } - // Finally update und and inc's feerate to account for the added transactions. - und -= inc.transactions; - inc.feerate += m_sorted_depgraph.FeeRate(inc.transactions - init_inc); - - // If inc's feerate is better than best's, remember it as our new best. - if (inc.feerate > best.feerate) { - best = inc; - // See if we can remove any entries from imp now. - while (imp.Any()) { - DepGraphIndex check = imp.Last(); - if (m_sorted_depgraph.FeeRate(check) >> best.feerate) break; - imp.Reset(check); - } - } - - // If no potential transactions exist beyond the already included ones, no - // improvement is possible anymore. - if (pot.feerate.size == inc.feerate.size) return; - // At this point und must be non-empty. If it were empty then pot would equal inc. - Assume(und.Any()); - } else { - Assume(inc.transactions.None()); - // If inc is empty, we just make sure there are undecided transactions left to - // split on. - if (und.None()) return; - } - - // Actually construct a new work item on the queue. Due to the switch to DFS when queue - // space runs out (see below), we know that no reallocation of the queue should ever - // occur. - Assume(queue.size() < queue.capacity()); - queue.emplace_back(/*inc=*/std::move(inc), - /*und=*/std::move(und), - /*pot_feerate=*/std::move(pot.feerate)); - }; - - /** Internal process function. It takes an existing work item, and splits it in two: one - * with a particular transaction (and its ancestors) included, and one with that - * transaction (and its descendants) excluded. */ - auto split_fn = [&](WorkItem&& elem) noexcept { - // Any queue element must have undecided transactions left, otherwise there is nothing - // to explore anymore. - Assume(elem.und.Any()); - // The included and undecided set are all subsets of m_todo. - Assume(elem.inc.transactions.IsSubsetOf(m_todo) && elem.und.IsSubsetOf(m_todo)); - // Included transactions cannot be undecided. - Assume(!elem.inc.transactions.Overlaps(elem.und)); - // If pot is empty, then so is inc. - Assume(elem.inc.feerate.IsEmpty() == elem.pot_feerate.IsEmpty()); - - const DepGraphIndex first = elem.und.First(); - if (!elem.inc.feerate.IsEmpty()) { - // If no undecided transactions remain with feerate higher than best, this entry - // cannot be improved beyond best. - if (!elem.und.Overlaps(imp)) return; - // We can ignore any queue item whose potential feerate isn't better than the best - // seen so far. - if (elem.pot_feerate <= best.feerate) return; - } else { - // In case inc is empty use a simpler alternative check. - if (m_sorted_depgraph.FeeRate(first) <= best.feerate) return; - } - - // Decide which transaction to split on. Splitting is how new work items are added, and - // how progress is made. One split transaction is chosen among the queue item's - // undecided ones, and: - // - A work item is (potentially) added with that transaction plus its remaining - // descendants excluded (removed from the und set). - // - A work item is (potentially) added with that transaction plus its remaining - // ancestors included (added to the inc set). - // - // To decide what to split on, consider the undecided ancestors of the highest - // individual feerate undecided transaction. Pick the one which reduces the search space - // most. Let I(t) be the size of the undecided set after including t, and E(t) the size - // of the undecided set after excluding t. Then choose the split transaction t such - // that 2^I(t) + 2^E(t) is minimal, tie-breaking by highest individual feerate for t. - DepGraphIndex split = 0; - const auto select = elem.und & m_sorted_depgraph.Ancestors(first); - Assume(select.Any()); - std::optional> split_counts; - for (auto t : select) { - // Call max = max(I(t), E(t)) and min = min(I(t), E(t)). Let counts = {max,min}. - // Sorting by the tuple counts is equivalent to sorting by 2^I(t) + 2^E(t). This - // expression is equal to 2^max + 2^min = 2^max * (1 + 1/2^(max - min)). The second - // factor (1 + 1/2^(max - min)) there is in (1,2]. Thus increasing max will always - // increase it, even when min decreases. Because of this, we can first sort by max. - std::pair counts{ - (elem.und - m_sorted_depgraph.Ancestors(t)).Count(), - (elem.und - m_sorted_depgraph.Descendants(t)).Count()}; - if (counts.first < counts.second) std::swap(counts.first, counts.second); - // Remember the t with the lowest counts. - if (!split_counts.has_value() || counts < *split_counts) { - split = t; - split_counts = counts; - } - } - // Since there was at least one transaction in select, we must always find one. - Assume(split_counts.has_value()); - - // Add a work item corresponding to exclusion of the split transaction. - const auto& desc = m_sorted_depgraph.Descendants(split); - add_fn(/*inc=*/elem.inc, - /*und=*/elem.und - desc); - - // Add a work item corresponding to inclusion of the split transaction. - const auto anc = m_sorted_depgraph.Ancestors(split) & m_todo; - add_fn(/*inc=*/elem.inc.Add(m_sorted_depgraph, anc), - /*und=*/elem.und - anc); - - // Account for the performed split. - --iterations_left; - }; - - // Work processing loop. - // - // New work items are always added at the back of the queue, but items to process use a - // hybrid approach where they can be taken from the front or the back. - // - // Depth-first search (DFS) corresponds to always taking from the back of the queue. This - // is very memory-efficient (linear in the number of transactions). Breadth-first search - // (BFS) corresponds to always taking from the front, which potentially uses more memory - // (up to exponential in the transaction count), but seems to work better in practice. - // - // The approach here combines the two: use BFS (plus random swapping) until the queue grows - // too large, at which point we temporarily switch to DFS until the size shrinks again. - while (!queue.empty()) { - // Randomly swap the first two items to randomize the search order. - if (queue.size() > 1 && m_rng.randbool()) { - queue[0].Swap(queue[1]); - } - - // Processing the first queue item, and then using DFS for everything it gives rise to, - // may increase the queue size by the number of undecided elements in there, minus 1 - // for the first queue item being removed. Thus, only when that pushes the queue over - // its capacity can we not process from the front (BFS), and should we use DFS. - while (queue.size() - 1 + queue.front().und.Count() > queue.capacity()) { - if (!iterations_left) break; - auto elem = queue.back(); - queue.pop_back(); - split_fn(std::move(elem)); - } - - // Process one entry from the front of the queue (BFS exploration) - if (!iterations_left) break; - auto elem = queue.front(); - queue.pop_front(); - split_fn(std::move(elem)); - } - - // Return the found best set (converted to the original transaction indices), and the - // number of iterations performed. - best.transactions = SortedToOriginal(best.transactions); - return {std::move(best), max_iterations - iterations_left}; - } - - /** Remove a subset of transactions from the cluster being linearized. - * - * Complexity: O(N) where N=done.Count(). - */ - void MarkDone(const SetType& done) noexcept - { - const auto done_sorted = OriginalToSorted(done); - Assume(done_sorted.Any()); - Assume(done_sorted.IsSubsetOf(m_todo)); - m_todo -= done_sorted; - } -}; - /** Find or improve a linearization for a cluster. * * @param[in] depgraph Dependency graph of the cluster to be linearized. - * @param[in] max_iterations Upper bound on the number of optimization steps that will be done. + * @param[in] max_iterations Upper bound on the amount of work that will be done. * @param[in] rng_seed A random number seed to control search order. This prevents peers * from predicting exactly which clusters would be hard for us to * linearize. @@ -1852,85 +1362,28 @@ public: * - A boolean indicating whether the result is guaranteed to be * optimal. * - How many optimization steps were actually performed. - * - * Complexity: possibly O(N * min(max_iterations + N, sqrt(2^N))) where N=depgraph.TxCount(). */ template std::tuple, bool, uint64_t> Linearize(const DepGraph& depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span old_linearization = {}) noexcept { - Assume(old_linearization.empty() || old_linearization.size() == depgraph.TxCount()); - if (depgraph.TxCount() == 0) return {{}, true, 0}; - - uint64_t iterations_left = max_iterations; - std::vector linearization; - - AncestorCandidateFinder anc_finder(depgraph); - std::optional> src_finder; - linearization.reserve(depgraph.TxCount()); - bool optimal = true; - - // Treat the initialization of SearchCandidateFinder as taking N^2/64 (rounded up) iterations - // (largely due to the cost of constructing the internal sorted-by-feerate DepGraph inside - // SearchCandidateFinder), a rough approximation based on benchmark. If we don't have that - // many, don't start it. - uint64_t start_iterations = (uint64_t{depgraph.TxCount()} * depgraph.TxCount() + 63) / 64; - if (iterations_left > start_iterations) { - iterations_left -= start_iterations; - src_finder.emplace(depgraph, rng_seed); + (void)rng_seed; // Unused for now. + /** Initialize a spanning forest data structure for this cluster. */ + SpanningForestState forest(depgraph); + if (!old_linearization.empty()) { + forest.LoadLinearization(old_linearization); + } else { + forest.MakeTopological(); } - - /** Chunking of what remains of the old linearization. */ - LinearizationChunking old_chunking(depgraph, old_linearization); - - while (true) { - // Find the highest-feerate prefix of the remainder of old_linearization. - SetInfo best_prefix; - if (old_chunking.NumChunksLeft()) best_prefix = old_chunking.GetChunk(0); - - // Then initialize best to be either the best remaining ancestor set, or the first chunk. - auto best = anc_finder.FindCandidateSet(); - if (!best_prefix.feerate.IsEmpty() && best_prefix.feerate >= best.feerate) best = best_prefix; - - uint64_t iterations_done_now = 0; - uint64_t max_iterations_now = 0; - if (src_finder) { - // Treat the invocation of SearchCandidateFinder::FindCandidateSet() as costing N/4 - // up-front (rounded up) iterations (largely due to the cost of connected-component - // splitting), a rough approximation based on benchmarks. - uint64_t base_iterations = (anc_finder.NumRemaining() + 3) / 4; - if (iterations_left > base_iterations) { - // Invoke bounded search to update best, with up to half of our remaining - // iterations as limit. - iterations_left -= base_iterations; - max_iterations_now = (iterations_left + 1) / 2; - std::tie(best, iterations_done_now) = src_finder->FindCandidateSet(max_iterations_now, best); - iterations_left -= iterations_done_now; - } - } - - if (iterations_done_now == max_iterations_now) { - optimal = false; - // If the search result is not (guaranteed to be) optimal, run intersections to make - // sure we don't pick something that makes us unable to reach further diagram points - // of the old linearization. - if (old_chunking.NumChunksLeft() > 0) { - best = old_chunking.IntersectPrefixes(best); - } - } - - // Add to output in topological order. - depgraph.AppendTopo(linearization, best.transactions); - - // Update state to reflect best is no longer to be linearized. - anc_finder.MarkDone(best.transactions); - if (anc_finder.AllDone()) break; - if (src_finder) src_finder->MarkDone(best.transactions); - if (old_chunking.NumChunksLeft() > 0) { - old_chunking.MarkDone(best.transactions); + // Make improvement steps to it until we hit the max_iterations limit, or an optimal result + // is found. + bool optimal = false; + while (forest.GetCost() < max_iterations) { + if (!forest.OptimizeStep()) { + optimal = true; + break; } } - - return {std::move(linearization), optimal, max_iterations - iterations_left}; + return {forest.GetLinearization(), optimal, forest.GetCost()}; } /** Improve a given linearization. diff --git a/src/test/cluster_linearize_tests.cpp b/src/test/cluster_linearize_tests.cpp index 4cce9773278..ede17b97813 100644 --- a/src/test/cluster_linearize_tests.cpp +++ b/src/test/cluster_linearize_tests.cpp @@ -88,8 +88,10 @@ void TestOptimalLinearization(const std::vector& enc, const std::vector SanityCheck(depgraph, lin); auto chunking = ChunkLinearization(depgraph, lin); BOOST_CHECK(std::is_eq(CompareChunks(chunking, optimal_diagram))); - // Verify that the chunks are minimal. - BOOST_CHECK(chunking.size() == optimal_diagram.size()); + // TODO: temporarily disabled; SFL does not guarantee minimal chunks. This will be + // reinstated in a future commit. + // // Verify that the chunks are minimal. + // BOOST_CHECK(chunking.size() == optimal_diagram.size()); } tx_count = depgraph.PositionRange(); }; diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index 86e85d1c505..b4d5234421b 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -24,29 +24,22 @@ * possibly by comparison with other implementations (at the end of the line ->). * <<---: The right side is implemented using the left side. * - * +-----------------------+ - * | SearchCandidateFinder | <<---------------------\ - * +-----------------------+ | - * | +-----------+ +---------------------+ - * | | Linearize | | SpanningForestState | - * | +-----------+ +---------------------+ - * | +-------------------------+ | | | - * | | AncestorCandidateFinder | <<--------/ | | - * | +-------------------------+ | | - * | | ^ | ^^ PRODUCTION CODE | - * | | | | || | + * +---------------------+ +-----------+ + * | SpanningForestState | <<-------------------- | Linearize | + * +---------------------+ +-----------+ + * | | + * | | ^^ PRODUCTION CODE + * | | || * ============================================================================================== - * | | | | || | - * | clusterlin_ancestor_finder* | | vv TEST CODE | - * | | | | - * |-clusterlin_search_finder* | |-clusterlin_linearize* | - * | | | | - * v | v clusterlin_sfl--| - * +-----------------------+ | +-----------------+ | - * | SimpleCandidateFinder | <<-------------------| SimpleLinearize |<----------------/ - * +-----------------------+ | +-----------------+ - * | | | - * +-------------------/ | + * | | || + * |-clusterlin_sfl* | vv TEST CODE + * | | + * \------------------------------------\ |-clusterlin_linearize* + * | | + * v v + * +-----------------------+ +-----------------+ + * | SimpleCandidateFinder | <<-------------------| SimpleLinearize | + * +-----------------------+ +-----------------+ * | | * |-clusterlin_simple_finder* |-clusterlin_simple_linearize* * v v @@ -78,11 +71,8 @@ using namespace cluster_linearize; namespace { -/** A simple finder class for candidate sets. - * - * This class matches SearchCandidateFinder in interface and behavior, though with fewer - * optimizations. - */ +/** A simple finder class for candidate sets (topologically-valid subsets with high feerate), only + * used by SimpleLinearize below. */ template class SimpleCandidateFinder { @@ -153,7 +143,8 @@ public: /** A very simple finder class for optimal candidate sets, which tries every subset. * * It is even simpler than SimpleCandidateFinder, and exists just to help test the correctness of - * SimpleCandidateFinder, which is then used to test the correctness of SearchCandidateFinder. + * SimpleCandidateFinder, so that it can be used in SimpleLinearize, which is then used to test the + * correctness of Linearize. */ template class ExhaustiveCandidateFinder @@ -204,8 +195,8 @@ public: /** A simple linearization algorithm. * * This matches Linearize() in interface and behavior, though with fewer optimizations, lacking - * the ability to pass in an existing linearization, and using just SimpleCandidateFinder rather - * than AncestorCandidateFinder and SearchCandidateFinder. + * the ability to pass in an existing linearization, and linearizing by simply finding the + * consecutive remaining highest-feerate topological subset using SimpleCandidateFinder. */ template std::pair, bool> SimpleLinearize(const DepGraph& depgraph, uint64_t max_iterations) @@ -766,68 +757,17 @@ FUZZ_TARGET(clusterlin_chunking) assert(todo.None()); } -FUZZ_TARGET(clusterlin_ancestor_finder) -{ - // Verify that AncestorCandidateFinder works as expected. - - // Retrieve a depgraph from the fuzz input. - SpanReader reader(buffer); - DepGraph depgraph; - try { - reader >> Using(depgraph); - } catch (const std::ios_base::failure&) {} - - AncestorCandidateFinder anc_finder(depgraph); - auto todo = depgraph.Positions(); - while (todo.Any()) { - // Call the ancestor finder's FindCandidateSet for what remains of the graph. - assert(!anc_finder.AllDone()); - assert(todo.Count() == anc_finder.NumRemaining()); - auto best_anc = anc_finder.FindCandidateSet(); - // Sanity check the result. - assert(best_anc.transactions.Any()); - assert(best_anc.transactions.IsSubsetOf(todo)); - assert(depgraph.FeeRate(best_anc.transactions) == best_anc.feerate); - assert(depgraph.IsConnected(best_anc.transactions)); - // Check that it is topologically valid. - for (auto i : best_anc.transactions) { - assert((depgraph.Ancestors(i) & todo).IsSubsetOf(best_anc.transactions)); - } - - // Compute all remaining ancestor sets. - std::optional> real_best_anc; - for (auto i : todo) { - SetInfo info(depgraph, todo & depgraph.Ancestors(i)); - if (!real_best_anc.has_value() || info.feerate > real_best_anc->feerate) { - real_best_anc = info; - } - } - // The set returned by anc_finder must equal the real best ancestor sets. - assert(real_best_anc.has_value()); - assert(*real_best_anc == best_anc); - - // Find a non-empty topologically valid subset of transactions to remove from the graph. - // Using an empty set would mean the next iteration is identical to the current one, and - // could cause an infinite loop. - auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true); - todo -= del_set; - anc_finder.MarkDone(del_set); - } - assert(anc_finder.AllDone()); - assert(anc_finder.NumRemaining() == 0); -} - static constexpr auto MAX_SIMPLE_ITERATIONS = 300000; FUZZ_TARGET(clusterlin_simple_finder) { // Verify that SimpleCandidateFinder works as expected by sanity checking the results // and comparing them (if claimed to be optimal) against the sets found by - // ExhaustiveCandidateFinder and AncestorCandidateFinder. + // ExhaustiveCandidateFinder. // // Note that SimpleCandidateFinder is only used in tests; the purpose of this fuzz test is to - // establish confidence in SimpleCandidateFinder, so that it can be used to test - // SearchCandidateFinder below. + // establish confidence in SimpleCandidateFinder, so that it can be used in SimpleLinearize, + // which is then used to test Linearize below. // Retrieve a depgraph from the fuzz input. SpanReader reader(buffer); @@ -836,18 +776,15 @@ FUZZ_TARGET(clusterlin_simple_finder) reader >> Using(depgraph); } catch (const std::ios_base::failure&) {} - // Instantiate the SimpleCandidateFinder to be tested, and the ExhaustiveCandidateFinder and - // AncestorCandidateFinder it is being tested against. + // Instantiate the SimpleCandidateFinder to be tested, and the ExhaustiveCandidateFinder it is + // being tested against. SimpleCandidateFinder smp_finder(depgraph); ExhaustiveCandidateFinder exh_finder(depgraph); - AncestorCandidateFinder anc_finder(depgraph); auto todo = depgraph.Positions(); while (todo.Any()) { assert(!smp_finder.AllDone()); assert(!exh_finder.AllDone()); - assert(!anc_finder.AllDone()); - assert(anc_finder.NumRemaining() == todo.Count()); // Call SimpleCandidateFinder. auto [found, iterations_done] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS); @@ -874,10 +811,6 @@ FUZZ_TARGET(clusterlin_simple_finder) // Perform further quality checks only if SimpleCandidateFinder claims an optimal result. if (optimal) { - // Compare with AncestorCandidateFinder. - auto anc = anc_finder.FindCandidateSet(); - assert(anc.feerate <= found.feerate); - if (todo.Count() <= 12) { // Compare with ExhaustiveCandidateFinder. This quickly gets computationally // expensive for large clusters (O(2^n)), so only do it for sufficiently small ones. @@ -898,119 +831,10 @@ FUZZ_TARGET(clusterlin_simple_finder) todo -= del_set; smp_finder.MarkDone(del_set); exh_finder.MarkDone(del_set); - anc_finder.MarkDone(del_set); } assert(smp_finder.AllDone()); assert(exh_finder.AllDone()); - assert(anc_finder.AllDone()); - assert(anc_finder.NumRemaining() == 0); -} - -FUZZ_TARGET(clusterlin_search_finder) -{ - // Verify that SearchCandidateFinder works as expected by sanity checking the results - // and comparing with the results from SimpleCandidateFinder and AncestorCandidateFinder, - // if the result is claimed to be optimal. - - // Retrieve an RNG seed, a depgraph, and whether to make it connected, from the fuzz input. - SpanReader reader(buffer); - DepGraph depgraph; - uint64_t rng_seed{0}; - uint8_t make_connected{1}; - try { - reader >> Using(depgraph) >> rng_seed >> make_connected; - } catch (const std::ios_base::failure&) {} - // The most complicated graphs are connected ones (other ones just split up). Optionally force - // the graph to be connected. - if (make_connected) MakeConnected(depgraph); - - // Instantiate the candidate finders. - SearchCandidateFinder src_finder(depgraph, rng_seed); - SimpleCandidateFinder smp_finder(depgraph); - AncestorCandidateFinder anc_finder(depgraph); - - auto todo = depgraph.Positions(); - while (todo.Any()) { - assert(!src_finder.AllDone()); - assert(!smp_finder.AllDone()); - assert(!anc_finder.AllDone()); - assert(anc_finder.NumRemaining() == todo.Count()); - - // For each iteration, read an iteration count limit from the fuzz input. - uint64_t max_iterations = 1; - try { - reader >> VARINT(max_iterations); - } catch (const std::ios_base::failure&) {} - max_iterations &= 0xfffff; - - // Read an initial subset from the fuzz input (allowed to be empty). - auto init_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false); - SetInfo init_best(depgraph, init_set); - - // Call the search finder's FindCandidateSet for what remains of the graph. - auto [found, iterations_done] = src_finder.FindCandidateSet(max_iterations, init_best); - bool optimal = iterations_done < max_iterations; - - // Sanity check the result. - assert(iterations_done <= max_iterations); - assert(found.transactions.Any()); - assert(found.transactions.IsSubsetOf(todo)); - assert(depgraph.FeeRate(found.transactions) == found.feerate); - if (!init_best.feerate.IsEmpty()) assert(found.feerate >= init_best.feerate); - // Check that it is topologically valid. - for (auto i : found.transactions) { - assert(found.transactions.IsSupersetOf(depgraph.Ancestors(i) & todo)); - } - - // At most 2^(N-1) iterations can be required: the maximum number of non-empty topological - // subsets a (connected) cluster with N transactions can have. Even when the cluster is no - // longer connected after removing certain transactions, this holds, because the connected - // components are searched separately. - assert(iterations_done <= (uint64_t{1} << (todo.Count() - 1))); - // Additionally, test that no more than sqrt(2^N)+1 iterations are required. This is just - // an empirical bound that seems to hold, without proof. Still, add a test for it so we - // can learn about counterexamples if they exist. - if (iterations_done >= 1 && todo.Count() <= 63) { - Assume((iterations_done - 1) * (iterations_done - 1) <= uint64_t{1} << todo.Count()); - } - - // Perform quality checks only if SearchCandidateFinder claims an optimal result. - if (optimal) { - // Optimal sets are always connected. - assert(depgraph.IsConnected(found.transactions)); - - // Compare with SimpleCandidateFinder. - auto [simple, simple_iters] = smp_finder.FindCandidateSet(MAX_SIMPLE_ITERATIONS); - assert(found.feerate >= simple.feerate); - if (simple_iters < MAX_SIMPLE_ITERATIONS) { - assert(found.feerate == simple.feerate); - } - - // Compare with AncestorCandidateFinder; - auto anc = anc_finder.FindCandidateSet(); - assert(found.feerate >= anc.feerate); - - // Compare with a non-empty topological set read from the fuzz input (comparing with an - // empty set is not interesting). - auto read_topo = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true); - assert(found.feerate >= depgraph.FeeRate(read_topo)); - } - - // Find a non-empty topologically valid subset of transactions to remove from the graph. - // Using an empty set would mean the next iteration is identical to the current one, and - // could cause an infinite loop. - auto del_set = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true); - todo -= del_set; - src_finder.MarkDone(del_set); - smp_finder.MarkDone(del_set); - anc_finder.MarkDone(del_set); - } - - assert(src_finder.AllDone()); - assert(smp_finder.AllDone()); - assert(anc_finder.AllDone()); - assert(anc_finder.NumRemaining() == 0); } FUZZ_TARGET(clusterlin_linearization_chunking) @@ -1250,6 +1074,10 @@ FUZZ_TARGET(clusterlin_sfl) } test_fn(/*is_optimal=*/true); + // Verify that optimality is reached within an expected amount of work. This protects against + // hypothetical bugs that hugely increase the amount of work needed to reach optimality. + assert(sfl.GetCost() <= MaxOptimalLinearizationIters(depgraph.TxCount())); + // The result must be as good as SimpleLinearize. auto [simple_linearization, simple_optimal] = SimpleLinearize(depgraph, MAX_SIMPLE_ITERATIONS / 10); auto simple_diagram = ChunkLinearization(depgraph, simple_linearization); @@ -1301,7 +1129,6 @@ FUZZ_TARGET(clusterlin_linearize) // Invoke Linearize(). iter_count &= 0x7ffff; auto [linearization, optimal, cost] = Linearize(depgraph, iter_count, rng_seed, old_linearization); - assert(cost <= iter_count); SanityCheck(depgraph, linearization); auto chunking = ChunkLinearization(depgraph, linearization); @@ -1313,7 +1140,7 @@ FUZZ_TARGET(clusterlin_linearize) } // If the iteration count is sufficiently high, an optimal linearization must be found. - if (iter_count >= MaxOptimalLinearizationIters(depgraph.TxCount())) { + if (iter_count > MaxOptimalLinearizationIters(depgraph.TxCount())) { assert(optimal); } @@ -1328,9 +1155,13 @@ FUZZ_TARGET(clusterlin_linearize) // If SimpleLinearize finds the optimal result too, they must be equal (if not, // SimpleLinearize is broken). if (simple_optimal) assert(cmp == 0); - // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as - // chunking is claimed to be optimal, which implies minimal chunks). - if (cmp == 0) assert(chunking.size() >= simple_chunking.size()); + + // Temporarily disabled, as Linearize() currently does not guarantee minimal chunks, even + // when it reports an optimal result. This will be re-introduced in a later commit. + // + // // If simple_chunking is diagram-optimal, it cannot have more chunks than chunking (as + // // chunking is claimed to be optimal, which implies minimal chunks). + // if (cmp == 0) assert(chunking.size() >= simple_chunking.size()); // Compare with a linearization read from the fuzz input. auto read = ReadLinearization(depgraph, reader); diff --git a/src/test/util/cluster_linearize.h b/src/test/util/cluster_linearize.h index 220b5fe3f84..d4126063960 100644 --- a/src/test/util/cluster_linearize.h +++ b/src/test/util/cluster_linearize.h @@ -396,25 +396,24 @@ void SanityCheck(const DepGraph& depgraph, std::span GenericClusterImpl::Relinearize(TxGraphImpl& graph, in // Invoke the actual linearization algorithm (passing in the existing one). uint64_t rng_seed = graph.m_rng.rand64(); auto [linearization, optimal, cost] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization); - // Postlinearize if the result isn't optimal already. This guarantees (among other things) - // that the chunks of the resulting linearization are all connected. - if (!optimal) PostLinearize(m_depgraph, linearization); + // Postlinearize to guarantee that the chunks of the resulting linearization are all connected. + // (SFL currently does not guarantee connected chunks even when optimal). + PostLinearize(m_depgraph, linearization); // Update the linearization. m_linearization = std::move(linearization); // Update the Cluster's quality. diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 4a20d5763cc..e66e49cbce4 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -239,8 +239,9 @@ class MempoolPackagesTest(BitcoinTestFramework): self.generate(self.nodes[0], 1) self.trigger_reorg(fork_blocks, self.nodes[0]) - # Check if the txs are returned to the mempool - assert_equal(self.nodes[0].getrawmempool(), mempool0) + # Check if the txs are returned to the mempool (though the transaction ordering may + # change as it is non-deterministic). + assert_equal(set(self.nodes[0].getrawmempool()), set(mempool0)) # Clean-up the mempool self.generate(self.nodes[0], 1) From ddbfa4dfac7b30d0dc462b29bd4c89b9152d0381 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 15 Oct 2025 21:46:50 -0400 Subject: [PATCH 07/11] clusterlin: keep FIFO queue of improvable chunks (preparation) This introduces a queue of chunks that still need processing, in both MakeTopological() and OptimizationStep(). This is simultaneously: * A preparation for introducing randomization, by allowing permuting the queue. * An improvement to the fairness of suboptimal solutions, by distributing the work more fairly over chunks. * An optimization, by avoiding retrying chunks over and over again which are already known to be optimal. --- src/cluster_linearize.h | 96 +++++++++++++++++++---------- src/test/fuzz/cluster_linearize.cpp | 2 + src/test/util/cluster_linearize.h | 12 ++-- 3 files changed, 73 insertions(+), 37 deletions(-) diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 64610ee751f..4d0d7c55d44 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -690,8 +690,7 @@ public: * transaction in the other chunk is activated (this will be changed in a later commit). * * - How to decide which chunk to find a dependency to split in: - * - The chunk with the lowest-index representative (an implementation detail) that can be split - * is picked (this will be changed in a later commit). + * - A round-robin queue of chunks to improve is maintained. * * - How to decide what dependency to deactivate (when splitting chunks): * - Inside the selected chunk (see above), among the dependencies whose top feerate is strictly @@ -742,6 +741,8 @@ private: std::vector m_tx_data; /** Information about each dependency. Indexed by DepIdx. */ std::vector m_dep_data; + /** A FIFO of chunk representatives of chunks that may be improved still. */ + VecDeque m_suboptimal_chunks; /** The number of updated transactions in activations/deactivations. */ uint64_t m_cost{0}; @@ -959,6 +960,8 @@ private: if (merged_rep == TxIdx(-1)) break; chunk_rep = merged_rep; } + // Add the chunk to the queue of improvable chunks. + m_suboptimal_chunks.push_back(chunk_rep); } /** Split a chunk, and then merge the resulting two chunks to make the graph topological @@ -1043,38 +1046,58 @@ public: /** Make state topological. Can be called after constructing, or after LoadLinearization. */ void MakeTopological() noexcept { - while (true) { - bool done = true; - // Iterate over all transactions (only processing those which are chunk representatives). - for (auto chunk : m_transaction_idxs) { - auto& chunk_data = m_tx_data[chunk]; - // If this is not a chunk representative, skip. - if (chunk_data.chunk_rep != chunk) continue; - // Attempt to merge the chunk upwards. - auto result_up = MergeStep(chunk); - if (result_up != TxIdx(-1)) { - done = false; - continue; - } - // Attempt to merge the chunk downwards. - auto result_down = MergeStep(chunk); - if (result_down != TxIdx(-1)) { - done = false; - continue; - } + for (auto tx : m_transaction_idxs) { + auto& tx_data = m_tx_data[tx]; + if (tx_data.chunk_rep == tx) { + m_suboptimal_chunks.emplace_back(tx); + } + } + while (!m_suboptimal_chunks.empty()) { + // Pop an entry from the potentially-suboptimal chunk queue. + TxIdx chunk = m_suboptimal_chunks.front(); + m_suboptimal_chunks.pop_front(); + auto& chunk_data = m_tx_data[chunk]; + // If what was popped is not currently a chunk representative, continue. This may + // happen when it was merged with something else since being added. + if (chunk_data.chunk_rep != chunk) continue; + // Attempt to merge the chunk upwards. + auto result_up = MergeStep(chunk); + if (result_up != TxIdx(-1)) { + m_suboptimal_chunks.push_back(result_up); + continue; + } + // Attempt to merge the chunk downwards. + auto result_down = MergeStep(chunk); + if (result_down != TxIdx(-1)) { + m_suboptimal_chunks.push_back(result_down); + continue; + } + } + } + + /** Initialize the data structure for optimization. It must be topological already. */ + void StartOptimizing() noexcept + { + // Mark chunks suboptimal. + for (auto tx : m_transaction_idxs) { + auto& tx_data = m_tx_data[tx]; + if (tx_data.chunk_rep == tx) { + m_suboptimal_chunks.push_back(tx); } - // Stop if no changes were made anymore. - if (done) break; } } /** Try to improve the forest. Returns false if it is optimal, true otherwise. */ bool OptimizeStep() noexcept { - // Iterate over all transactions (only processing those which are chunk representatives). - for (auto chunk : m_transaction_idxs) { + while (!m_suboptimal_chunks.empty()) { + // Pop an entry from the potentially-suboptimal chunk queue. + TxIdx chunk = m_suboptimal_chunks.front(); + m_suboptimal_chunks.pop_front(); auto& chunk_data = m_tx_data[chunk]; - // If this is not a chunk representative, skip. + // If what was popped is not currently a chunk representative, continue. This may + // happen when a split chunk merges in Improve() with one or more existing chunks that + // are themselves on the suboptimal queue already. if (chunk_data.chunk_rep != chunk) continue; // Iterate over all transactions of the chunk. for (auto tx : chunk_data.chunk_setinfo.transactions) { @@ -1344,6 +1367,14 @@ public: assert(dep_data.top_setinfo.feerate == depgraph.FeeRate(dep_data.top_setinfo.transactions)); } + + // + // Verify m_suboptimal_chunks. + // + for (size_t i = 0; i < m_suboptimal_chunks.size(); ++i) { + auto tx_idx = m_suboptimal_chunks[i]; + assert(m_transaction_idxs[tx_idx]); + } } }; @@ -1377,11 +1408,14 @@ std::tuple, bool, uint64_t> Linearize(const DepGraph< // Make improvement steps to it until we hit the max_iterations limit, or an optimal result // is found. bool optimal = false; - while (forest.GetCost() < max_iterations) { - if (!forest.OptimizeStep()) { - optimal = true; - break; - } + if (forest.GetCost() < max_iterations) { + forest.StartOptimizing(); + do { + if (!forest.OptimizeStep()) { + optimal = true; + break; + } + } while (forest.GetCost() < max_iterations); } return {forest.GetLinearization(), optimal, forest.GetCost()}; } diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index b4d5234421b..fe18407dbb4 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -1068,6 +1068,8 @@ FUZZ_TARGET(clusterlin_sfl) } // Loop until optimal. + test_fn(); + sfl.StartOptimizing(); while (true) { test_fn(); if (!sfl.OptimizeStep()) break; diff --git a/src/test/util/cluster_linearize.h b/src/test/util/cluster_linearize.h index d4126063960..2e73032e2c3 100644 --- a/src/test/util/cluster_linearize.h +++ b/src/test/util/cluster_linearize.h @@ -404,12 +404,12 @@ inline uint64_t MaxOptimalLinearizationIters(DepGraphIndex cluster_count) 0, 0, 2, 8, 21, 51, 99, 162, 208, 300, 349, 489, 627, 776, 867, 982, 1204, - 1414, 1473, 1770, 2045, 2391, 2417, 3669, 3953, - 3816, 5717, 4096, 5933, 5225, 5684, 6205, 6407, - 7671, 12044, 11799, 9577, 9631, 10819, 12277, 15250, - 18609, 14439, 22283, 16461, 22887, 20641, 22009, 22053, - 27068, 22173, 31066, 30848, 31841, 37174, 39701, 35666, - 42728, 43679, 45719, 40217, 51395, 57796, 72739, 60079 + 1414, 1473, 1770, 2045, 2285, 2417, 3669, 3953, + 3816, 5720, 4103, 5934, 5443, 5323, 6338, 6407, + 7671, 11625, 11799, 10104, 9631, 11203, 12487, 15262, + 17800, 14132, 21915, 16495, 23350, 21304, 22221, 22230, + 26119, 22182, 31118, 30848, 32166, 37174, 39708, 36189, + 42747, 43689, 46555, 39818, 51077, 58489, 72633, 59756 }; assert(cluster_count < std::size(ITERS)); // Multiply the table number by two, to account for the fact that they are not absolutes. From 13aad26b78481f2abd6d5e3ae6218e46e4d0060a Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 10 Dec 2025 15:06:21 -0500 Subject: [PATCH 08/11] clusterlin: randomize various decisions in SFL (feature) This introduces a local RNG inside the SFL state, which is used to randomize various decisions inside the algorithm, in order to make it hard to create pathological clusters which predictably have bad performance. The decisions being randomized are: * When deciding what chunk to attempt to split, the queue order is randomized. * When deciding which dependency to split on, a uniformly random one is chosen among those with higher top feerate than bottom feerate within the chosen chunk. * When deciding which chunks to merge, a uniformly random one among those with the higher feerate difference is picked. * When merging two chunks, a uniformly random dependency between them is now activated. * When making the state topological, the queue of chunks to process is randomized. --- src/cluster_linearize.h | 116 ++++++++++++++++++++-------- src/test/fuzz/cluster_linearize.cpp | 2 +- src/test/util/cluster_linearize.h | 16 ++-- 3 files changed, 91 insertions(+), 43 deletions(-) diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 4d0d7c55d44..6639244f35e 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -682,25 +682,28 @@ public: * - How to decide which chunks to merge: * - The merge upwards and downward rules specify that the lowest-feerate respectively * highest-feerate candidate chunk is merged with, but if there are multiple equal-feerate - * candidates, the chunk with the highest-index transaction involving a relevant dependency is - * picked (this will be changed in a later commit). + * candidates, a uniformly random one among them is picked. * * - How to decide what dependency to activate (when merging chunks): - * - After picking two chunks to be merged (see above), the dependency with the lowest-index - * transaction in the other chunk is activated (this will be changed in a later commit). + * - After picking two chunks to be merged (see above), a uniformly random dependency between the + * two chunks is activated. * * - How to decide which chunk to find a dependency to split in: - * - A round-robin queue of chunks to improve is maintained. + * - A round-robin queue of chunks to improve is maintained. The initial ordering of this queue + * is uniformly randomly permuted. * * - How to decide what dependency to deactivate (when splitting chunks): * - Inside the selected chunk (see above), among the dependencies whose top feerate is strictly - * higher than its bottom feerate in the selected chunk, if any, the one with the lowest-index - * child is deactivated (this will be changed in a later commit). + * higher than its bottom feerate in the selected chunk, if any, a uniformly random dependency + * is deactivated. */ template class SpanningForestState { private: + /** Internal RNG. */ + InsecureRandomContext m_rng; + /** Data type to represent indexing into m_tx_data. */ using TxIdx = uint32_t; /** Data type to represent indexing into m_dep_data. */ @@ -876,20 +879,30 @@ private: Assume(top_chunk.chunk_rep == top_rep); auto& bottom_chunk = m_tx_data[bottom_rep]; Assume(bottom_chunk.chunk_rep == bottom_rep); - // Activate the first dependency between bottom_chunk and top_chunk. + // Count the number of dependencies between bottom_chunk and top_chunk. + TxIdx num_deps{0}; for (auto tx : top_chunk.chunk_setinfo.transactions) { auto& tx_data = m_tx_data[tx]; - // As an optimization, only iterate over transactions which have dependencies in the - // bottom chunk. - if (tx_data.children.Overlaps(bottom_chunk.chunk_setinfo.transactions)) { + num_deps += (tx_data.children & bottom_chunk.chunk_setinfo.transactions).Count(); + } + Assume(num_deps > 0); + // Uniformly randomly pick one of them and activate it. + TxIdx pick = m_rng.randrange(num_deps); + for (auto tx : top_chunk.chunk_setinfo.transactions) { + auto& tx_data = m_tx_data[tx]; + auto intersect = tx_data.children & bottom_chunk.chunk_setinfo.transactions; + auto count = intersect.Count(); + if (pick < count) { for (auto dep : tx_data.child_deps) { auto& dep_data = m_dep_data[dep]; if (bottom_chunk.chunk_setinfo.transactions[dep_data.child]) { - return Activate(dep); + if (pick == 0) return Activate(dep); + --pick; } } break; } + pick -= count; } Assume(false); return TxIdx(-1); @@ -906,7 +919,7 @@ private: // Iterate over all transactions in the chunk, figuring out which other chunk each // depends on, but only testing each other chunk once. For those depended-on chunks, // remember the highest-feerate (if DownWard) or lowest-feerate (if !DownWard) one. - // If multiple equal-feerate candidate chunks to merge with exist, pick the last one + // If multiple equal-feerate candidate chunks to merge with exist, pick a random one // among them. /** Which transactions have been reached from this chunk already. Initialize with the @@ -918,6 +931,9 @@ private: FeeFrac best_other_chunk_feerate = chunk_data.chunk_setinfo.feerate; /** The representative for the best candidate chunk to merge with. -1 if none. */ TxIdx best_other_chunk_rep = TxIdx(-1); + /** We generate random tiebreak values to pick between equal-feerate candidate chunks. + * This variable stores the tiebreak of the current best candidate. */ + uint64_t best_other_chunk_tiebreak{0}; for (auto tx : chunk_txn) { auto& tx_data = m_tx_data[tx]; /** The transactions reached by following dependencies from tx that have not been @@ -932,9 +948,12 @@ private: // See if it has an acceptable feerate. auto cmp = DownWard ? FeeRateCompare(best_other_chunk_feerate, reached_chunk.feerate) : FeeRateCompare(reached_chunk.feerate, best_other_chunk_feerate); - if (cmp <= 0) { + if (cmp > 0) continue; + uint64_t tiebreak = m_rng.rand64(); + if (cmp < 0 || tiebreak >= best_other_chunk_tiebreak) { best_other_chunk_feerate = reached_chunk.feerate; best_other_chunk_rep = reached_chunk_rep; + best_other_chunk_tiebreak = tiebreak; } } } @@ -989,7 +1008,7 @@ private: public: /** Construct a spanning forest for the given DepGraph, with every transaction in its own chunk * (not topological). */ - explicit SpanningForestState(const DepGraph& depgraph) noexcept + explicit SpanningForestState(const DepGraph& depgraph, uint64_t rng_seed) noexcept : m_rng(rng_seed) { m_transaction_idxs = depgraph.Positions(); auto num_transactions = m_transaction_idxs.Count(); @@ -1050,6 +1069,11 @@ public: auto& tx_data = m_tx_data[tx]; if (tx_data.chunk_rep == tx) { m_suboptimal_chunks.emplace_back(tx); + // Randomize the initial order of suboptimal chunks in the queue. + TxIdx j = m_rng.randrange(m_suboptimal_chunks.size()); + if (j != m_suboptimal_chunks.size() - 1) { + std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); + } } } while (!m_suboptimal_chunks.empty()) { @@ -1060,17 +1084,23 @@ public: // If what was popped is not currently a chunk representative, continue. This may // happen when it was merged with something else since being added. if (chunk_data.chunk_rep != chunk) continue; - // Attempt to merge the chunk upwards. - auto result_up = MergeStep(chunk); - if (result_up != TxIdx(-1)) { - m_suboptimal_chunks.push_back(result_up); - continue; - } - // Attempt to merge the chunk downwards. - auto result_down = MergeStep(chunk); - if (result_down != TxIdx(-1)) { - m_suboptimal_chunks.push_back(result_down); - continue; + int flip = m_rng.randbool(); + for (int i = 0; i < 2; ++i) { + if (i ^ flip) { + // Attempt to merge the chunk upwards. + auto result_up = MergeStep(chunk); + if (result_up != TxIdx(-1)) { + m_suboptimal_chunks.push_back(result_up); + break; + } + } else { + // Attempt to merge the chunk downwards. + auto result_down = MergeStep(chunk); + if (result_down != TxIdx(-1)) { + m_suboptimal_chunks.push_back(result_down); + break; + } + } } } } @@ -1083,6 +1113,11 @@ public: auto& tx_data = m_tx_data[tx]; if (tx_data.chunk_rep == tx) { m_suboptimal_chunks.push_back(tx); + // Randomize the initial order of suboptimal chunks in the queue. + TxIdx j = m_rng.randrange(m_suboptimal_chunks.size()); + if (j != m_suboptimal_chunks.size() - 1) { + std::swap(m_suboptimal_chunks.back(), m_suboptimal_chunks[j]); + } } } } @@ -1099,7 +1134,10 @@ public: // happen when a split chunk merges in Improve() with one or more existing chunks that // are themselves on the suboptimal queue already. if (chunk_data.chunk_rep != chunk) continue; - // Iterate over all transactions of the chunk. + // Remember the best dependency seen so far. + DepIdx candidate_dep = DepIdx(-1); + uint64_t candidate_tiebreak = 0; + // Iterate over all transactions. for (auto tx : chunk_data.chunk_setinfo.transactions) { const auto& tx_data = m_tx_data[tx]; // Iterate over all active child dependencies of the transaction. @@ -1109,13 +1147,24 @@ public: if (!dep_data.active) continue; // Skip if this dependency is ineligible (the top chunk that would be created // does not have higher feerate than the chunk it is currently part of). - if (!(dep_data.top_setinfo.feerate >> chunk_data.chunk_setinfo.feerate)) continue; - // Otherwise, deactivate it and then make the state topological again with a - // sequence of merges. - Improve(dep_idx); - return true; + auto cmp = FeeRateCompare(dep_data.top_setinfo.feerate, chunk_data.chunk_setinfo.feerate); + if (cmp <= 0) continue; + // Generate a random tiebreak for this dependency, and reject it if its tiebreak + // is worse than the best so far. This means that among all eligible + // dependencies, a uniformly random one will be chosen. + uint64_t tiebreak = m_rng.rand64(); + if (tiebreak < candidate_tiebreak) continue; + // Remember this as our (new) candidate dependency. + candidate_dep = dep_idx; + candidate_tiebreak = tiebreak; } } + // If a candidate with positive gain was found, deactivate it and then make the state + // topological again with a sequence of merges. + if (candidate_dep != DepIdx(-1)) Improve(candidate_dep); + // Stop processing for now, even if nothing was activated, as the loop above may have + // had a nontrivial cost. + return !m_suboptimal_chunks.empty(); } // No improvable chunk was found, we are done. return false; @@ -1397,9 +1446,8 @@ public: template std::tuple, bool, uint64_t> Linearize(const DepGraph& depgraph, uint64_t max_iterations, uint64_t rng_seed, std::span old_linearization = {}) noexcept { - (void)rng_seed; // Unused for now. /** Initialize a spanning forest data structure for this cluster. */ - SpanningForestState forest(depgraph); + SpanningForestState forest(depgraph, rng_seed); if (!old_linearization.empty()) { forest.LoadLinearization(old_linearization); } else { diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index fe18407dbb4..a32fc169b89 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -1021,7 +1021,7 @@ FUZZ_TARGET(clusterlin_sfl) // Initialize SFL state. if (make_connected) MakeConnected(depgraph); - SpanningForestState sfl(depgraph); + SpanningForestState sfl(depgraph, rng.rand64()); // Function to test the state. std::vector last_diagram; diff --git a/src/test/util/cluster_linearize.h b/src/test/util/cluster_linearize.h index 2e73032e2c3..f57c486bd97 100644 --- a/src/test/util/cluster_linearize.h +++ b/src/test/util/cluster_linearize.h @@ -402,14 +402,14 @@ inline uint64_t MaxOptimalLinearizationIters(DepGraphIndex cluster_count) // *some* reasonable cost bound, optimal linearizations are always found. static constexpr uint64_t ITERS[65] = { 0, - 0, 2, 8, 21, 51, 99, 162, 208, - 300, 349, 489, 627, 776, 867, 982, 1204, - 1414, 1473, 1770, 2045, 2285, 2417, 3669, 3953, - 3816, 5720, 4103, 5934, 5443, 5323, 6338, 6407, - 7671, 11625, 11799, 10104, 9631, 11203, 12487, 15262, - 17800, 14132, 21915, 16495, 23350, 21304, 22221, 22230, - 26119, 22182, 31118, 30848, 32166, 37174, 39708, 36189, - 42747, 43689, 46555, 39818, 51077, 58489, 72633, 59756 + 0, 2, 8, 21, 51, 96, 162, 200, + 273, 323, 413, 506, 602, 788, 883, 948, + 1153, 1187, 1367, 1619, 1854, 2271, 2257, 2707, + 2904, 3275, 3342, 4209, 4648, 4146, 4273, 4905, + 5358, 5767, 5977, 6777, 7812, 7689, 8478, 8425, + 9561, 11765, 10743, 11806, 12812, 12838, 15421, 16778, + 16661, 19393, 17995, 23947, 23314, 24564, 26209, 29267, + 24719, 31065, 31794, 29185, 32465, 35432, 39986, 36865 }; assert(cluster_count < std::size(ITERS)); // Multiply the table number by two, to account for the fact that they are not absolutes. From 5ce280074512f79a4b3851d8d453f337fa97be46 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 14 Oct 2025 15:34:42 -0400 Subject: [PATCH 09/11] clusterlin: randomize equal-feerate parts of linearization (privacy) This places equal-feerate chunks (with no dependencies between them) in random order in the linearization output, hiding information about DepGraph insertion order from the output. Likewise, it randomizes the order of transactions within chunks for the same reason. --- src/cluster_linearize.h | 36 ++++++++++++++++++++++++------------ src/txgraph.cpp | 5 +++-- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 6639244f35e..8b4d0d2278f 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -696,6 +696,12 @@ public: * - Inside the selected chunk (see above), among the dependencies whose top feerate is strictly * higher than its bottom feerate in the selected chunk, if any, a uniformly random dependency * is deactivated. + * + * - How to decide the exact output linearization: + * - When there are multiple equal-feerate chunks with no dependencies between them, output a + * uniformly random one among the ones with no missing dependent chunks first. + * - Within chunks, repeatedly pick a uniformly random transaction among those with no missing + * dependencies. */ template class SpanningForestState @@ -1178,8 +1184,8 @@ public: std::vector ret; ret.reserve(m_transaction_idxs.Count()); /** A heap with all chunks (by representative) that can currently be included, sorted by - * chunk feerate. */ - std::vector ready_chunks; + * chunk feerate and a random tie-breaker. */ + std::vector> ready_chunks; /** Information about chunks: * - The first value is only used for chunk representatives, and counts the number of * unmet dependencies this chunk has on other chunks (not including dependencies within @@ -1205,25 +1211,27 @@ public: } // Construct a heap with all chunks that have no out-of-chunk dependencies. /** Comparison function for the heap. */ - auto chunk_cmp_fn = [&](TxIdx a, TxIdx b) noexcept { - auto& chunk_a = m_tx_data[a]; - auto& chunk_b = m_tx_data[b]; - Assume(chunk_a.chunk_rep == a); - Assume(chunk_b.chunk_rep == b); + auto chunk_cmp_fn = [&](const std::pair& a, const std::pair& b) noexcept { + auto& chunk_a = m_tx_data[a.first]; + auto& chunk_b = m_tx_data[b.first]; + Assume(chunk_a.chunk_rep == a.first); + Assume(chunk_b.chunk_rep == b.first); // First sort by chunk feerate. if (chunk_a.chunk_setinfo.feerate != chunk_b.chunk_setinfo.feerate) { return chunk_a.chunk_setinfo.feerate < chunk_b.chunk_setinfo.feerate; } - // Tie-break by chunk representative. - return a < b; + // Tie-break randomly. + if (a.second != b.second) return a.second < b.second; + // Lastly, tie-break by chunk representative. + return a.first < b.first; }; for (TxIdx chunk_rep : chunk_reps) { - if (chunk_deps[chunk_rep].first == 0) ready_chunks.push_back(chunk_rep); + if (chunk_deps[chunk_rep].first == 0) ready_chunks.emplace_back(chunk_rep, m_rng.rand64()); } std::make_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); // Pop chunks off the heap, highest-feerate ones first. while (!ready_chunks.empty()) { - auto chunk_rep = ready_chunks.front(); + auto [chunk_rep, _rnd] = ready_chunks.front(); std::pop_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); ready_chunks.pop_back(); Assume(m_tx_data[chunk_rep].chunk_rep == chunk_rep); @@ -1239,6 +1247,10 @@ public: // Pick transactions from the ready queue, append them to linearization, and decrement // dependency counts. while (!ready_tx.empty()) { + // Move a random queue element to the back. + auto pos = m_rng.randrange(ready_tx.size()); + if (pos != ready_tx.size() - 1) std::swap(ready_tx.back(), ready_tx[pos]); + // Pop from the back. auto tx_idx = ready_tx.back(); Assume(chunk_txn[tx_idx]); ready_tx.pop_back(); @@ -1259,7 +1271,7 @@ public: Assume(chunk_deps[chl_data.chunk_rep].first > 0); if (--chunk_deps[chl_data.chunk_rep].first == 0) { // Child chunk has no dependencies left. Add it to the chunk heap. - ready_chunks.push_back(chl_data.chunk_rep); + ready_chunks.emplace_back(chl_data.chunk_rep, m_rng.rand64()); std::push_heap(ready_chunks.begin(), ready_chunks.end(), chunk_cmp_fn); } } diff --git a/src/txgraph.cpp b/src/txgraph.cpp index 2004705f647..3bc5cae6740 100644 --- a/src/txgraph.cpp +++ b/src/txgraph.cpp @@ -2091,8 +2091,9 @@ std::pair GenericClusterImpl::Relinearize(TxGraphImpl& graph, in // Invoke the actual linearization algorithm (passing in the existing one). uint64_t rng_seed = graph.m_rng.rand64(); auto [linearization, optimal, cost] = Linearize(m_depgraph, max_iters, rng_seed, m_linearization); - // Postlinearize to guarantee that the chunks of the resulting linearization are all connected. - // (SFL currently does not guarantee connected chunks even when optimal). + // Postlinearize to undo some of the non-determinism caused by randomizing the linearization. + // This also guarantees that all chunks are connected (which is not guaranteed by SFL + // currently, even when optimal). PostLinearize(m_depgraph, linearization); // Update the linearization. m_linearization = std::move(linearization); From 91399a79122cb6bb256e634049ac83f53154b64b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 16 Sep 2025 13:38:24 -0400 Subject: [PATCH 10/11] clusterlin: remove unused MergeLinearizations (cleanup) This ended up never being used in txgraph. --- src/bench/cluster_linearize.cpp | 35 ------------------------ src/cluster_linearize.h | 42 ----------------------------- src/test/fuzz/cluster_linearize.cpp | 28 ------------------- 3 files changed, 105 deletions(-) diff --git a/src/bench/cluster_linearize.cpp b/src/bench/cluster_linearize.cpp index 4c3203804c7..a856d2475c0 100644 --- a/src/bench/cluster_linearize.cpp +++ b/src/bench/cluster_linearize.cpp @@ -44,27 +44,6 @@ void BenchPostLinearizeWorstCase(DepGraphIndex ntx, benchmark::Bench& bench) }); } -template -void BenchMergeLinearizationsWorstCase(DepGraphIndex ntx, benchmark::Bench& bench) -{ - DepGraph depgraph; - for (DepGraphIndex i = 0; i < ntx; ++i) { - depgraph.AddTransaction({i, 1}); - if (i) depgraph.AddDependencies(SetType::Singleton(0), i); - } - std::vector lin1; - std::vector lin2; - lin1.push_back(0); - lin2.push_back(0); - for (DepGraphIndex i = 1; i < ntx; ++i) { - lin1.push_back(i); - lin2.push_back(ntx - i); - } - bench.run([&] { - MergeLinearizations(depgraph, lin1, lin2); - }); -} - void BenchLinearizeOptimallyTotal(benchmark::Bench& bench, const std::string& name, const std::vector>& serializeds) { for (const auto& serialized : serializeds) { @@ -119,13 +98,6 @@ static void PostLinearize64TxWorstCase(benchmark::Bench& bench) { BenchPostLinea static void PostLinearize75TxWorstCase(benchmark::Bench& bench) { BenchPostLinearizeWorstCase>(75, bench); } static void PostLinearize99TxWorstCase(benchmark::Bench& bench) { BenchPostLinearizeWorstCase>(99, bench); } -static void MergeLinearizations16TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(16, bench); } -static void MergeLinearizations32TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(32, bench); } -static void MergeLinearizations48TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(48, bench); } -static void MergeLinearizations64TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(64, bench); } -static void MergeLinearizations75TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(75, bench); } -static void MergeLinearizations99TxWorstCase(benchmark::Bench& bench) { BenchMergeLinearizationsWorstCase>(99, bench); } - // Constructed from replayed historical mempool activity, selecting for clusters that are slow // to linearize from scratch, with increasing number of transactions (9 to 63). static const std::vector> CLUSTERS_HISTORICAL = { @@ -185,12 +157,5 @@ BENCHMARK(PostLinearize64TxWorstCase, benchmark::PriorityLevel::HIGH); BENCHMARK(PostLinearize75TxWorstCase, benchmark::PriorityLevel::HIGH); BENCHMARK(PostLinearize99TxWorstCase, benchmark::PriorityLevel::HIGH); -BENCHMARK(MergeLinearizations16TxWorstCase, benchmark::PriorityLevel::HIGH); -BENCHMARK(MergeLinearizations32TxWorstCase, benchmark::PriorityLevel::HIGH); -BENCHMARK(MergeLinearizations48TxWorstCase, benchmark::PriorityLevel::HIGH); -BENCHMARK(MergeLinearizations64TxWorstCase, benchmark::PriorityLevel::HIGH); -BENCHMARK(MergeLinearizations75TxWorstCase, benchmark::PriorityLevel::HIGH); -BENCHMARK(MergeLinearizations99TxWorstCase, benchmark::PriorityLevel::HIGH); - BENCHMARK(LinearizeOptimallyTotal, benchmark::PriorityLevel::HIGH); BENCHMARK(LinearizeOptimallyPerCost, benchmark::PriorityLevel::HIGH); diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 8b4d0d2278f..120bc527f3e 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -1681,48 +1681,6 @@ void PostLinearize(const DepGraph& depgraph, std::span l } } -/** Merge two linearizations for the same cluster into one that is as good as both. - * - * Complexity: O(N^2) where N=depgraph.TxCount(); O(N) if both inputs are identical. - */ -template -std::vector MergeLinearizations(const DepGraph& depgraph, std::span lin1, std::span lin2) -{ - Assume(lin1.size() == depgraph.TxCount()); - Assume(lin2.size() == depgraph.TxCount()); - - /** Chunkings of what remains of both input linearizations. */ - LinearizationChunking chunking1(depgraph, lin1), chunking2(depgraph, lin2); - /** Output linearization. */ - std::vector ret; - if (depgraph.TxCount() == 0) return ret; - ret.reserve(depgraph.TxCount()); - - while (true) { - // As long as we are not done, both linearizations must have chunks left. - Assume(chunking1.NumChunksLeft() > 0); - Assume(chunking2.NumChunksLeft() > 0); - // Find the set to output by taking the best remaining chunk, and then intersecting it with - // prefixes of remaining chunks of the other linearization. - SetInfo best; - const auto& lin1_firstchunk = chunking1.GetChunk(0); - const auto& lin2_firstchunk = chunking2.GetChunk(0); - if (lin2_firstchunk.feerate >> lin1_firstchunk.feerate) { - best = chunking1.IntersectPrefixes(lin2_firstchunk); - } else { - best = chunking2.IntersectPrefixes(lin1_firstchunk); - } - // Append the result to the output and mark it as done. - depgraph.AppendTopo(ret, best.transactions); - chunking1.MarkDone(best.transactions); - if (chunking1.NumChunksLeft() == 0) break; - chunking2.MarkDone(best.transactions); - } - - Assume(ret.size() == depgraph.TxCount()); - return ret; -} - /** Make linearization topological, retaining its ordering where possible. */ template void FixLinearization(const DepGraph& depgraph, std::span linearization) noexcept diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index a32fc169b89..32848179331 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -59,8 +59,6 @@ * - clusterlin_postlinearize * - clusterlin_postlinearize_tree * - clusterlin_postlinearize_moved_leaf - * - MergeLinearization tests: - * - clusterlin_merge * - FixLinearization tests: * - clusterlin_fix_linearization * - MakeConnected tests (a test-only function): @@ -1308,32 +1306,6 @@ FUZZ_TARGET(clusterlin_postlinearize_moved_leaf) assert(cmp >= 0); } -FUZZ_TARGET(clusterlin_merge) -{ - // Construct an arbitrary graph from the fuzz input. - SpanReader reader(buffer); - DepGraph depgraph; - try { - reader >> Using(depgraph); - } catch (const std::ios_base::failure&) {} - - // Retrieve two linearizations from the fuzz input. - auto lin1 = ReadLinearization(depgraph, reader); - auto lin2 = ReadLinearization(depgraph, reader); - - // Merge the two. - auto lin_merged = MergeLinearizations(depgraph, lin1, lin2); - - // Compute chunkings and compare. - auto chunking1 = ChunkLinearization(depgraph, lin1); - auto chunking2 = ChunkLinearization(depgraph, lin2); - auto chunking_merged = ChunkLinearization(depgraph, lin_merged); - auto cmp1 = CompareChunks(chunking_merged, chunking1); - assert(cmp1 >= 0); - auto cmp2 = CompareChunks(chunking_merged, chunking2); - assert(cmp2 >= 0); -} - FUZZ_TARGET(clusterlin_fix_linearization) { // Verify expected properties of FixLinearization() on arbitrary linearizations. From 75bdb925f404f41874adf0fcefca0f1641fcb4e6 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 12 Oct 2025 09:48:19 -0400 Subject: [PATCH 11/11] clusterlin: drop support for improvable chunking (simplification) With MergeLinearizations() gone and the LIMO-based Linearize() replaced by SFL, we do not need a class (LinearizationChunking) that can maintain an incrementally-improving chunk set anymore. Replace it with a function (ChunkLinearizationInfo) that just computes the chunks as SetInfos once, and returns them as a vector. This simplifies several call sites too. --- src/cluster_linearize.h | 150 ++++------------------------ src/test/fuzz/cluster_linearize.cpp | 138 +++---------------------- src/test/fuzz/txgraph.cpp | 5 +- src/txgraph.cpp | 26 ++--- 4 files changed, 51 insertions(+), 268 deletions(-) diff --git a/src/cluster_linearize.h b/src/cluster_linearize.h index 120bc527f3e..d65e61d4458 100644 --- a/src/cluster_linearize.h +++ b/src/cluster_linearize.h @@ -422,7 +422,27 @@ struct SetInfo friend bool operator==(const SetInfo&, const SetInfo&) noexcept = default; }; -/** Compute the feerates of the chunks of linearization. */ +/** Compute the chunks of linearization as SetInfos. */ +template +std::vector> ChunkLinearizationInfo(const DepGraph& depgraph, std::span linearization) noexcept +{ + std::vector> ret; + for (DepGraphIndex i : linearization) { + /** The new chunk to be added, initially a singleton. */ + SetInfo new_chunk(depgraph, i); + // As long as the new chunk has a higher feerate than the last chunk so far, absorb it. + while (!ret.empty() && new_chunk.feerate >> ret.back().feerate) { + new_chunk |= ret.back(); + ret.pop_back(); + } + // Actually move that new chunk into the chunking. + ret.emplace_back(std::move(new_chunk)); + } + return ret; +} + +/** Compute the feerates of the chunks of linearization. Identical to ChunkLinearizationInfo, but + * only returns the chunk feerates, not the corresponding transaction sets. */ template std::vector ChunkLinearization(const DepGraph& depgraph, std::span linearization) noexcept { @@ -441,134 +461,6 @@ std::vector ChunkLinearization(const DepGraph& depgraph, std:: return ret; } -/** Data structure encapsulating the chunking of a linearization, permitting removal of subsets. */ -template -class LinearizationChunking -{ - /** The depgraph this linearization is for. */ - const DepGraph& m_depgraph; - - /** The linearization we started from, possibly with removed prefix stripped. */ - std::span m_linearization; - - /** Chunk sets and their feerates, of what remains of the linearization. */ - std::vector> m_chunks; - - /** How large a prefix of m_chunks corresponds to removed transactions. */ - DepGraphIndex m_chunks_skip{0}; - - /** Which transactions remain in the linearization. */ - SetType m_todo; - - /** Fill the m_chunks variable, and remove the done prefix of m_linearization. */ - void BuildChunks() noexcept - { - // Caller must clear m_chunks. - Assume(m_chunks.empty()); - - // Chop off the initial part of m_linearization that is already done. - while (!m_linearization.empty() && !m_todo[m_linearization.front()]) { - m_linearization = m_linearization.subspan(1); - } - - // Iterate over the remaining entries in m_linearization. This is effectively the same - // algorithm as ChunkLinearization, but supports skipping parts of the linearization and - // keeps track of the sets themselves instead of just their feerates. - for (auto idx : m_linearization) { - if (!m_todo[idx]) continue; - // Start with an initial chunk containing just element idx. - SetInfo add(m_depgraph, idx); - // Absorb existing final chunks into add while they have lower feerate. - while (!m_chunks.empty() && add.feerate >> m_chunks.back().feerate) { - add |= m_chunks.back(); - m_chunks.pop_back(); - } - // Remember new chunk. - m_chunks.push_back(std::move(add)); - } - } - -public: - /** Initialize a LinearizationSubset object for a given length of linearization. */ - explicit LinearizationChunking(const DepGraph& depgraph LIFETIMEBOUND, std::span lin LIFETIMEBOUND) noexcept : - m_depgraph(depgraph), m_linearization(lin) - { - // Mark everything in lin as todo still. - for (auto i : m_linearization) m_todo.Set(i); - // Compute the initial chunking. - m_chunks.reserve(depgraph.TxCount()); - BuildChunks(); - } - - /** Determine how many chunks remain in the linearization. */ - DepGraphIndex NumChunksLeft() const noexcept { return m_chunks.size() - m_chunks_skip; } - - /** Access a chunk. Chunk 0 is the highest-feerate prefix of what remains. */ - const SetInfo& GetChunk(DepGraphIndex n) const noexcept - { - Assume(n + m_chunks_skip < m_chunks.size()); - return m_chunks[n + m_chunks_skip]; - } - - /** Remove some subset of transactions from the linearization. */ - void MarkDone(SetType subset) noexcept - { - Assume(subset.Any()); - Assume(subset.IsSubsetOf(m_todo)); - m_todo -= subset; - if (GetChunk(0).transactions == subset) { - // If the newly done transactions exactly match the first chunk of the remainder of - // the linearization, we do not need to rechunk; just remember to skip one - // additional chunk. - ++m_chunks_skip; - // With subset marked done, some prefix of m_linearization will be done now. How long - // that prefix is depends on how many done elements were interspersed with subset, - // but at least as many transactions as there are in subset. - m_linearization = m_linearization.subspan(subset.Count()); - } else { - // Otherwise rechunk what remains of m_linearization. - m_chunks.clear(); - m_chunks_skip = 0; - BuildChunks(); - } - } - - /** Find the shortest intersection between subset and the prefixes of remaining chunks - * of the linearization that has a feerate not below subset's. - * - * This is a crucial operation in guaranteeing improvements to linearizations. If subset has - * a feerate not below GetChunk(0)'s, then moving IntersectPrefixes(subset) to the front of - * (what remains of) the linearization is guaranteed not to make it worse at any point. - * - * See https://delvingbitcoin.org/t/introduction-to-cluster-linearization/1032 for background. - */ - SetInfo IntersectPrefixes(const SetInfo& subset) const noexcept - { - Assume(subset.transactions.IsSubsetOf(m_todo)); - SetInfo accumulator; - // Iterate over all chunks of the remaining linearization. - for (DepGraphIndex i = 0; i < NumChunksLeft(); ++i) { - // Find what (if any) intersection the chunk has with subset. - const SetType to_add = GetChunk(i).transactions & subset.transactions; - if (to_add.Any()) { - // If adding that to accumulator makes us hit all of subset, we are done as no - // shorter intersection with higher/equal feerate exists. - accumulator.transactions |= to_add; - if (accumulator.transactions == subset.transactions) break; - // Otherwise update the accumulator feerate. - accumulator.feerate += m_depgraph.FeeRate(to_add); - // If that does result in something better, or something with the same feerate but - // smaller, return that. Even if a longer, higher-feerate intersection exists, it - // does not hurt to return the shorter one (the remainder of the longer intersection - // will generally be found in the next call to Intersect, but even if not, it is not - // required for the improvement guarantee this function makes). - if (!(accumulator.feerate << subset.feerate)) return accumulator; - } - } - return subset; - } -}; - /** Class to represent the internal state of the spanning-forest linearization (SFL) algorithm. * * At all times, each dependency is marked as either "active" or "inactive". The subset of active diff --git a/src/test/fuzz/cluster_linearize.cpp b/src/test/fuzz/cluster_linearize.cpp index 32848179331..ccf1be68768 100644 --- a/src/test/fuzz/cluster_linearize.cpp +++ b/src/test/fuzz/cluster_linearize.cpp @@ -52,9 +52,8 @@ * - clusterlin_depgraph_sim * - clusterlin_depgraph_serialization * - clusterlin_components - * - ChunkLinearization and LinearizationChunking tests: + * - ChunkLinearization and ChunkLinearizationInfo tests: * - clusterlin_chunking - * - clusterlin_linearization_chunking * - PostLinearize tests: * - clusterlin_postlinearize * - clusterlin_postlinearize_tree @@ -727,8 +726,16 @@ FUZZ_TARGET(clusterlin_chunking) // Read a valid linearization for depgraph. auto linearization = ReadLinearization(depgraph, reader); - // Invoke the chunking function. + // Invoke the chunking functions. auto chunking = ChunkLinearization(depgraph, linearization); + auto chunking_info = ChunkLinearizationInfo(depgraph, linearization); + + // Verify consistency between the two functions. + assert(chunking.size() == chunking_info.size()); + for (size_t i = 0; i < chunking.size(); ++i) { + assert(chunking[i] == chunking_info[i].feerate); + assert(SetInfo(depgraph, chunking_info[i].transactions) == chunking_info[i]); + } // Verify that chunk feerates are monotonically non-increasing. for (size_t i = 1; i < chunking.size(); ++i) { @@ -737,7 +744,7 @@ FUZZ_TARGET(clusterlin_chunking) // Naively recompute the chunks (each is the highest-feerate prefix of what remains). auto todo = depgraph.Positions(); - for (const auto& chunk_feerate : chunking) { + for (const auto& [chunk_set, chunk_feerate] : chunking_info) { assert(todo.Any()); SetInfo accumulator, best; for (DepGraphIndex idx : linearization) { @@ -749,6 +756,7 @@ FUZZ_TARGET(clusterlin_chunking) } } assert(chunk_feerate == best.feerate); + assert(chunk_set == best.transactions); assert(best.transactions.IsSubsetOf(todo)); todo -= best.transactions; } @@ -835,121 +843,6 @@ FUZZ_TARGET(clusterlin_simple_finder) assert(exh_finder.AllDone()); } -FUZZ_TARGET(clusterlin_linearization_chunking) -{ - // Verify the behavior of LinearizationChunking. - - // Retrieve a depgraph from the fuzz input. - SpanReader reader(buffer); - DepGraph depgraph; - try { - reader >> Using(depgraph); - } catch (const std::ios_base::failure&) {} - - // Retrieve a topologically-valid subset of depgraph (allowed to be empty, because the argument - // to LinearizationChunking::Intersect is allowed to be empty). - auto todo = depgraph.Positions(); - auto subset = SetInfo(depgraph, ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/false)); - - // Retrieve a valid linearization for depgraph. - auto linearization = ReadLinearization(depgraph, reader); - - // Construct a LinearizationChunking object, initially for the whole linearization. - LinearizationChunking chunking(depgraph, linearization); - - // Incrementally remove transactions from the chunking object, and check various properties at - // every step. - while (todo.Any()) { - assert(chunking.NumChunksLeft() > 0); - - // Construct linearization with just todo. - std::vector linearization_left; - for (auto i : linearization) { - if (todo[i]) linearization_left.push_back(i); - } - - // Compute the chunking for linearization_left. - auto chunking_left = ChunkLinearization(depgraph, linearization_left); - - // Verify that it matches the feerates of the chunks of chunking. - assert(chunking.NumChunksLeft() == chunking_left.size()); - for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) { - assert(chunking.GetChunk(i).feerate == chunking_left[i]); - } - - // Check consistency of chunking. - TestBitSet combined; - for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) { - const auto& chunk_info = chunking.GetChunk(i); - // Chunks must be non-empty. - assert(chunk_info.transactions.Any()); - // Chunk feerates must be monotonically non-increasing. - if (i > 0) assert(!(chunk_info.feerate >> chunking.GetChunk(i - 1).feerate)); - // Chunks must be a subset of what is left of the linearization. - assert(chunk_info.transactions.IsSubsetOf(todo)); - // Chunks' claimed feerates must match their transactions' aggregate feerate. - assert(depgraph.FeeRate(chunk_info.transactions) == chunk_info.feerate); - // Chunks must be the highest-feerate remaining prefix. - SetInfo accumulator, best; - for (auto j : linearization) { - if (todo[j] && !combined[j]) { - accumulator.Set(depgraph, j); - if (best.feerate.IsEmpty() || accumulator.feerate > best.feerate) { - best = accumulator; - } - } - } - assert(best.transactions == chunk_info.transactions); - assert(best.feerate == chunk_info.feerate); - // Chunks cannot overlap. - assert(!chunk_info.transactions.Overlaps(combined)); - combined |= chunk_info.transactions; - // Chunks must be topological. - for (auto idx : chunk_info.transactions) { - assert((depgraph.Ancestors(idx) & todo).IsSubsetOf(combined)); - } - } - assert(combined == todo); - - // Verify the expected properties of LinearizationChunking::IntersectPrefixes: - auto intersect = chunking.IntersectPrefixes(subset); - // - Intersecting again doesn't change the result. - assert(chunking.IntersectPrefixes(intersect) == intersect); - // - The intersection is topological. - TestBitSet intersect_anc; - for (auto idx : intersect.transactions) { - intersect_anc |= (depgraph.Ancestors(idx) & todo); - } - assert(intersect.transactions == intersect_anc); - // - The claimed intersection feerate matches its transactions. - assert(intersect.feerate == depgraph.FeeRate(intersect.transactions)); - // - The intersection may only be empty if its input is empty. - assert(intersect.transactions.Any() == subset.transactions.Any()); - // - The intersection feerate must be as high as the input. - assert(intersect.feerate >= subset.feerate); - // - No non-empty intersection between the intersection and a prefix of the chunks of the - // remainder of the linearization may be better than the intersection. - TestBitSet prefix; - for (DepGraphIndex i = 0; i < chunking.NumChunksLeft(); ++i) { - prefix |= chunking.GetChunk(i).transactions; - auto reintersect = SetInfo(depgraph, prefix & intersect.transactions); - if (!reintersect.feerate.IsEmpty()) { - assert(reintersect.feerate <= intersect.feerate); - } - } - - // Find a non-empty topologically valid subset of transactions to remove from the graph. - // Using an empty set would mean the next iteration is identical to the current one, and - // could cause an infinite loop. - auto done = ReadTopologicalSet(depgraph, todo, reader, /*non_empty=*/true); - todo -= done; - chunking.MarkDone(done); - subset = SetInfo(depgraph, subset.transactions - done); - } - - assert(chunking.NumChunksLeft() == 0); -} - FUZZ_TARGET(clusterlin_simple_linearize) { // Verify the behavior of SimpleLinearize(). Note that SimpleLinearize is only used in tests; @@ -1207,10 +1100,9 @@ FUZZ_TARGET(clusterlin_postlinearize) assert(cmp >= 0); // The chunks that come out of postlinearizing are always connected. - LinearizationChunking linchunking(depgraph, post_linearization); - while (linchunking.NumChunksLeft()) { - assert(depgraph.IsConnected(linchunking.GetChunk(0).transactions)); - linchunking.MarkDone(linchunking.GetChunk(0).transactions); + auto linchunking = ChunkLinearizationInfo(depgraph, post_linearization); + for (const auto& [chunk_set, _chunk_feerate] : linchunking) { + assert(depgraph.IsConnected(chunk_set)); } } diff --git a/src/test/fuzz/txgraph.cpp b/src/test/fuzz/txgraph.cpp index 79ee0a16e44..d44cf559e0c 100644 --- a/src/test/fuzz/txgraph.cpp +++ b/src/test/fuzz/txgraph.cpp @@ -1230,10 +1230,9 @@ FUZZ_TARGET(txgraph) // Construct a chunking object for the simulated graph, using the reported cluster // linearization as ordering, and compare it against the reported chunk feerates. if (sims.size() == 1 || level == TxGraph::Level::MAIN) { - cluster_linearize::LinearizationChunking simlinchunk(sim.graph, simlin); + auto simlinchunk = ChunkLinearizationInfo(sim.graph, simlin); DepGraphIndex idx{0}; - for (unsigned chunknum = 0; chunknum < simlinchunk.NumChunksLeft(); ++chunknum) { - auto chunk = simlinchunk.GetChunk(chunknum); + for (auto& chunk : simlinchunk) { // Require that the chunks of cluster linearizations are connected (this must // be the case as all linearizations inside are PostLinearized). assert(sim.graph.IsConnected(chunk.transactions)); diff --git a/src/txgraph.cpp b/src/txgraph.cpp index 3bc5cae6740..8a4fc28cf36 100644 --- a/src/txgraph.cpp +++ b/src/txgraph.cpp @@ -1013,11 +1013,11 @@ void GenericClusterImpl::Updated(TxGraphImpl& graph, int level) noexcept // ACCEPTABLE, so it is pointless to compute these if we haven't reached that quality level // yet. if (level == 0 && IsAcceptable()) { - const LinearizationChunking chunking(m_depgraph, m_linearization); + auto chunking = ChunkLinearizationInfo(m_depgraph, m_linearization); LinearizationIndex lin_idx{0}; // Iterate over the chunks. - for (unsigned chunk_idx = 0; chunk_idx < chunking.NumChunksLeft(); ++chunk_idx) { - auto chunk = chunking.GetChunk(chunk_idx); + for (unsigned chunk_idx = 0; chunk_idx < chunking.size(); ++chunk_idx) { + auto& chunk = chunking[chunk_idx]; auto chunk_count = chunk.transactions.Count(); Assume(chunk_count > 0); // Iterate over the transactions in the linearization, which must match those in chunk. @@ -1031,7 +1031,7 @@ void GenericClusterImpl::Updated(TxGraphImpl& graph, int level) noexcept chunk.transactions.Reset(idx); if (chunk.transactions.None()) { // Last transaction in the chunk. - if (chunk_count == 1 && chunk_idx + 1 == chunking.NumChunksLeft()) { + if (chunk_count == 1 && chunk_idx + 1 == chunking.size()) { // If this is the final chunk of the cluster, and it contains just a single // transaction (which will always be true for the very common singleton // clusters), store the special value -1 as chunk count. @@ -1311,13 +1311,12 @@ void SingletonClusterImpl::AppendChunkFeerates(std::vector& ret) const uint64_t GenericClusterImpl::AppendTrimData(std::vector& ret, std::vector>& deps) const noexcept { - const LinearizationChunking linchunking(m_depgraph, m_linearization); + auto linchunking = ChunkLinearizationInfo(m_depgraph, m_linearization); LinearizationIndex pos{0}; uint64_t size{0}; auto prev_index = GraphIndex(-1); // Iterate over the chunks of this cluster's linearization. - for (unsigned i = 0; i < linchunking.NumChunksLeft(); ++i) { - const auto& [chunk, chunk_feerate] = linchunking.GetChunk(i); + for (const auto& [chunk, chunk_feerate] : linchunking) { // Iterate over the transactions of that chunk, in linearization order. auto chunk_tx_count = chunk.Count(); for (unsigned j = 0; j < chunk_tx_count; ++j) { @@ -2759,7 +2758,8 @@ void GenericClusterImpl::SanityCheck(const TxGraphImpl& graph, int level) const } // Compute the chunking of m_linearization. - LinearizationChunking linchunking(m_depgraph, m_linearization); + auto linchunking = ChunkLinearizationInfo(m_depgraph, m_linearization); + unsigned chunk_num{0}; // Verify m_linearization. SetType m_done; @@ -2779,14 +2779,14 @@ void GenericClusterImpl::SanityCheck(const TxGraphImpl& graph, int level) const if (level == 0 && IsAcceptable()) { assert(entry.m_main_lin_index == linindex); ++linindex; - if (!linchunking.GetChunk(0).transactions[lin_pos]) { - linchunking.MarkDone(linchunking.GetChunk(0).transactions); + if (!linchunking[chunk_num].transactions[lin_pos]) { + ++chunk_num; chunk_pos = 0; } - assert(entry.m_main_chunk_feerate == linchunking.GetChunk(0).feerate); + assert(entry.m_main_chunk_feerate == linchunking[chunk_num].feerate); // Verify that an entry in the chunk index exists for every chunk-ending transaction. ++chunk_pos; - bool is_chunk_end = (chunk_pos == linchunking.GetChunk(0).transactions.Count()); + bool is_chunk_end = (chunk_pos == linchunking[chunk_num].transactions.Count()); assert((entry.m_main_chunkindex_iterator != graph.m_main_chunkindex.end()) == is_chunk_end); if (is_chunk_end) { auto& chunk_data = *entry.m_main_chunkindex_iterator; @@ -2797,7 +2797,7 @@ void GenericClusterImpl::SanityCheck(const TxGraphImpl& graph, int level) const } } // If this Cluster has an acceptable quality level, its chunks must be connected. - assert(m_depgraph.IsConnected(linchunking.GetChunk(0).transactions)); + assert(m_depgraph.IsConnected(linchunking[chunk_num].transactions)); } } // Verify that each element of m_depgraph occurred in m_linearization.