diff --git a/Makefile.am b/Makefile.am index f6d86b09d9..af80e31135 100755 --- a/Makefile.am +++ b/Makefile.am @@ -362,6 +362,8 @@ test_libbitcoin_system_test_SOURCES = \ test/unicode/utf8_everywhere/ofstream.cpp \ test/unicode/utf8_everywhere/unicode_istream.cpp \ test/unicode/utf8_everywhere/unicode_ostream.cpp \ + test/utreexo/utreexo.cpp \ + test/utreexo/utreexo.hpp \ test/wallet/context.cpp \ test/wallet/message.cpp \ test/wallet/neutrino_filter.cpp \ diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 4ebdf5b59b..430871bfa6 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -849,6 +849,8 @@ if (with-tests) "../../test/unicode/utf8_everywhere/ofstream.cpp" "../../test/unicode/utf8_everywhere/unicode_istream.cpp" "../../test/unicode/utf8_everywhere/unicode_ostream.cpp" + "../../test/utreexo/utreexo.cpp" + "../../test/utreexo/utreexo.hpp" "../../test/wallet/context.cpp" "../../test/wallet/message.cpp" "../../test/wallet/neutrino_filter.cpp" diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj index 4a8715565d..b8f7c72dee 100644 --- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj @@ -245,6 +245,7 @@ + @@ -305,6 +306,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters index 172500f9f4..42f272295f 100644 --- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters @@ -14,7 +14,7 @@ {51A424A9-2C12-4211-0000-000000000001} - {51A424A9-2C12-4211-0000-000000000002} + {51A424A9-2C12-4211-0000-000000000003} {51A424A9-2C12-4211-0000-000000000002} @@ -35,22 +35,22 @@ {51A424A9-2C12-4211-0000-000000000007} - {51A424A9-2C12-4211-0000-000000000003} + {51A424A9-2C12-4211-0000-000000000004} - {51A424A9-2C12-4211-0000-000000000006} + {51A424A9-2C12-4211-0000-000000000007} - {51A424A9-2C12-4211-0000-000000000004} + {51A424A9-2C12-4211-0000-000000000005} - {51A424A9-2C12-4211-0000-000000000005} + {51A424A9-2C12-4211-0000-000000000006} {51A424A9-2C12-4211-0000-000000000008} - {51A424A9-2C12-4211-0000-000000000007} + {51A424A9-2C12-4211-0000-000000000008} {51A424A9-2C12-4211-0000-000000000009} @@ -68,37 +68,40 @@ {51A424A9-2C12-4211-0000-00000000000D} - {51A424A9-2C12-4211-0000-000000000008} + {51A424A9-2C12-4211-0000-000000000009} - {51A424A9-2C12-4211-0000-000000000009} + {51A424A9-2C12-4211-0000-000000000010} - {51A424A9-2C12-4211-0000-000000000010} + {51A424A9-2C12-4211-0000-0000000000A1} {51A424A9-2C12-4211-0000-00000000000E} - {51A424A9-2C12-4211-0000-0000000000A1} + {51A424A9-2C12-4211-0000-0000000000B1} - + {51A424A9-2C12-4211-0000-00000000000F} + + {51A424A9-2C12-4211-0000-000000000001} + - {51A424A9-2C12-4211-0000-0000000000B1} + {51A424A9-2C12-4211-0000-0000000000C1} - {51A424A9-2C12-4211-0000-0000000000C1} + {51A424A9-2C12-4211-0000-0000000000D1} - {51A424A9-2C12-4211-0000-0000000000D1} + {51A424A9-2C12-4211-0000-0000000000E1} - {51A424A9-2C12-4211-0000-000000000001} + {51A424A9-2C12-4211-0000-000000000002} - {51A424A9-2C12-4211-0000-0000000000E1} + {51A424A9-2C12-4211-0000-0000000000F1} @@ -561,6 +564,9 @@ src\unicode\utf8_everywhere + + src\utreexo + src\wallet\addresses @@ -695,6 +701,9 @@ src + + src\utreexo + src\wallet\mnemonics diff --git a/include/bitcoin/system/impl/math/bits.ipp b/include/bitcoin/system/impl/math/bits.ipp index b6cb4db7f4..26afe1fb65 100644 --- a/include/bitcoin/system/impl/math/bits.ipp +++ b/include/bitcoin/system/impl/math/bits.ipp @@ -294,8 +294,8 @@ template > constexpr void shift_left_into(Value& value, size_t shift, bool overflow) NOEXCEPT { constexpr auto span = bits; - overflow && shift >= span ? value = 0 : - value <<= (shift % span); + overflow && shift >= span ? value = 0 : depromote( + value <<= (shift % span)); } // signed overloads (shift left of negative is undefined behavior). diff --git a/include/bitcoin/system/impl/math/overflow.ipp b/include/bitcoin/system/impl/math/overflow.ipp index d2429e0b3a..d4487ded29 100644 --- a/include/bitcoin/system/impl/math/overflow.ipp +++ b/include/bitcoin/system/impl/math/overflow.ipp @@ -33,6 +33,21 @@ constexpr Integral minimum_ = std::numeric_limits::min(); template = true> constexpr Integral maximum_ = std::numeric_limits::max(); +// shift +// ---------------------------------------------------------------------------- + +template > +constexpr bool is_left_shift_overflow(Value value, size_t shift) NOEXCEPT +{ + return to_bool(bit_and(value, unmask_left(shift))); +} + +template > +constexpr bool is_right_shift_overflow(Value value, size_t shift) NOEXCEPT +{ + return to_bool(bit_and(value, unmask_right(shift))); +} + // add/subtract // ---------------------------------------------------------------------------- diff --git a/include/bitcoin/system/math/overflow.hpp b/include/bitcoin/system/math/overflow.hpp index 9dd9870b14..7503c0f8ac 100644 --- a/include/bitcoin/system/math/overflow.hpp +++ b/include/bitcoin/system/math/overflow.hpp @@ -23,8 +23,19 @@ namespace libbitcoin { namespace system { + +/// shift +/// --------------------------------------------------------------------------- + +/// Shift would cause 1-valued bit(s) to be left-shifted out. +template = true> +constexpr bool is_left_shift_overflow(Value value, size_t shift) NOEXCEPT; + +/// Shift would cause 1-valued bit(s) to be right-shifted out. +template = true> +constexpr bool is_right_shift_overflow(Value value, size_t shift) NOEXCEPT; -/// add/subtract. +/// shift/add/subtract. /// --------------------------------------------------------------------------- // TODO: generalize is_add_overflow() and is_subtract_overflow() argument types. diff --git a/test/math/overflow.cpp b/test/math/overflow.cpp index 2c93aa7f4b..ffa24447eb 100644 --- a/test/math/overflow.cpp +++ b/test/math/overflow.cpp @@ -30,6 +30,67 @@ constexpr auto signed_zero = 0_i16; constexpr auto signed_max = max_int16; constexpr auto signed_half = to_half(signed_max); +// is_left_shift_overflow +// ---------------------------------------------------------------------------- + +// unsigned only +static_assert(!is_left_shift_overflow(0b00000000, 0)); +static_assert(!is_left_shift_overflow(0b00000000, 1)); +static_assert(!is_left_shift_overflow(0b00000000, 2)); +static_assert(!is_left_shift_overflow(0b00000000, 4)); +static_assert(!is_left_shift_overflow(0b00000000, 8)); + +static_assert(!is_left_shift_overflow(0b00000001, 0)); +static_assert(!is_left_shift_overflow(0b00000001, 1)); +static_assert(!is_left_shift_overflow(0b00000001, 2)); +static_assert(!is_left_shift_overflow(0b00000001, 4)); +static_assert( is_left_shift_overflow(0b00000001, 8)); + +static_assert(!is_left_shift_overflow(0b10000000, 0)); +static_assert( is_left_shift_overflow(0b10000000, 1)); +static_assert( is_left_shift_overflow(0b10000000, 2)); +static_assert( is_left_shift_overflow(0b10000000, 4)); +static_assert( is_left_shift_overflow(0b10000000, 8)); + +static_assert(!is_left_shift_overflow(0b00101010, 0)); +static_assert(!is_left_shift_overflow(0b00101010, 1)); +static_assert(!is_left_shift_overflow(0b00101010, 2)); +static_assert( is_left_shift_overflow(0b00101010, 4)); +static_assert( is_left_shift_overflow(0b00101010, 8)); + +static_assert(!is_left_shift_overflow(0b11111111, 0)); +static_assert( is_left_shift_overflow(0b11111111, 1)); +static_assert( is_left_shift_overflow(0b11111111, 2)); +static_assert( is_left_shift_overflow(0b11111111, 4)); +static_assert( is_left_shift_overflow(0b11111111, 8)); + +static_assert(!is_left_shift_overflow(0b00010000'00000000, 0)); +static_assert(!is_left_shift_overflow(0b00010000'00000000, 1)); +static_assert(!is_left_shift_overflow(0b00010000'00000000, 2)); +static_assert( is_left_shift_overflow(0b00010000'00000000, 4)); +static_assert( is_left_shift_overflow(0b00010000'00000000, 8)); +static_assert( is_left_shift_overflow(0b00000000'00000001, bits)); +static_assert(!is_left_shift_overflow(0b00000000'00000001, sub1(bits))); + +static_assert(!is_left_shift_overflow(0b00010000'00000000'00000000'00000000, 0)); +static_assert(!is_left_shift_overflow(0b00010000'00000000'00000000'00000000, 1)); +static_assert(!is_left_shift_overflow(0b00010000'00000000'00000000'00000000, 2)); +static_assert( is_left_shift_overflow(0b00010000'00000000'00000000'00000000, 4)); +static_assert( is_left_shift_overflow(0b00010000'00000000'00000000'00000000, 8)); +static_assert( is_left_shift_overflow(0b00000000'00000000'00000000'00000001, bits)); +static_assert(!is_left_shift_overflow(0b00000000'00000000'00000000'00000001, sub1(bits))); + +static_assert(!is_left_shift_overflow(0b00010000'00000000'00000000'00000000'00010000'00000000'00000000'00000000, 0)); +static_assert(!is_left_shift_overflow(0b00010000'00000000'00000000'00000000'00010000'00000000'00000000'00000000, 1)); +static_assert(!is_left_shift_overflow(0b00010000'00000000'00000000'00000000'00010000'00000000'00000000'00000000, 2)); +static_assert( is_left_shift_overflow(0b00010000'00000000'00000000'00000000'00010000'00000000'00000000'00000000, 4)); +static_assert( is_left_shift_overflow(0b00010000'00000000'00000000'00000000'00010000'00000000'00000000'00000000, 8)); +static_assert( is_left_shift_overflow(0b00000000'00000000'00000000'00000000'00000000'00000000'00000000'00000001, bits)); +static_assert(!is_left_shift_overflow(0b00000000'00000000'00000000'00000000'00000000'00000000'00000000'00000001, sub1(bits))); + +// is_right_shift_overflow +// ---------------------------------------------------------------------------- + // is_negate_overflow // ---------------------------------------------------------------------------- @@ -68,7 +129,7 @@ static_assert(is_add_overflow(unsigned_max, unsigned_half)); static_assert(is_add_overflow(unsigned_half, unsigned_max)); static_assert(!is_add_overflow(unsigned_half, unsigned_half)); -// is_underflow +// is_subtract_overflow // ---------------------------------------------------------------------------- // signed diff --git a/test/utreexo/utreexo.cpp b/test/utreexo/utreexo.cpp index b5da3e7aaa..dadecf0c51 100644 --- a/test/utreexo/utreexo.cpp +++ b/test/utreexo/utreexo.cpp @@ -25,6 +25,67 @@ BOOST_AUTO_TEST_SUITE(utreexo_tests) // inferred from rustreexo implementation (no test provided) +BOOST_AUTO_TEST_CASE(utreexo__parent__various__expected) +{ + static_assert(parent(0, 0) == 1); + static_assert(parent(0, 1) == 2); + static_assert(parent(0, 2) == 4); + static_assert(parent(0, 3) == 8); + static_assert(parent(0, 4) == 16); + static_assert(parent(0, 5) == 32); + static_assert(parent(0, 6) == 64); + + static_assert(parent(1, 0) == 1); + static_assert(parent(1, 1) == 2); + static_assert(parent(1, 2) == 4); + static_assert(parent(1, 3) == 8); + static_assert(parent(1, 4) == 16); + static_assert(parent(1, 5) == 32); + static_assert(parent(1, 6) == 64); + + static_assert(parent(128, 0) == 64 + 1); + static_assert(parent(128, 1) == 64 + 2); + static_assert(parent(128, 2) == 64 + 4); + static_assert(parent(128, 3) == 64 + 8); + static_assert(parent(128, 4) == 64 + 16); + static_assert(parent(128, 5) == 64 + 32); + static_assert(parent(128, 6) == 64); + + BOOST_REQUIRE_EQUAL(parent(128, 0), 65_u64); + BOOST_REQUIRE_EQUAL(parent(128, 1), 66_u64); + BOOST_REQUIRE_EQUAL(parent(128, 2), 68_u64); + BOOST_REQUIRE_EQUAL(parent(128, 3), 72_u64); + BOOST_REQUIRE_EQUAL(parent(128, 4), 80_u64); + BOOST_REQUIRE_EQUAL(parent(128, 5), 96_u64); + BOOST_REQUIRE_EQUAL(parent(128, 6), 64_u64); +} + +BOOST_AUTO_TEST_CASE(utreexo__left_child__various__expected) +{ + // same as children() + static_assert(left_child(4, 2) == 0); + static_assert(left_child(49, 5) == 34); + static_assert(left_child(50, 5) == 36); + static_assert(left_child(44, 5) == 24); + BOOST_REQUIRE_EQUAL(left_child(4, 2), 0_u64); + BOOST_REQUIRE_EQUAL(left_child(49, 5), 34_u64); + BOOST_REQUIRE_EQUAL(left_child(50, 5), 36_u64); + BOOST_REQUIRE_EQUAL(left_child(44, 5), 24_u64); +} + +BOOST_AUTO_TEST_CASE(utreexo__right_child__various__expected) +{ + // same as add1(children()) + static_assert(right_child(4, 2) == 0 + 1); + static_assert(right_child(49, 5) == 34 + 1); + static_assert(right_child(50, 5) == 36 + 1); + static_assert(right_child(44, 5) == 24 + 1); + BOOST_REQUIRE_EQUAL(right_child(4, 2), 1_u64); + BOOST_REQUIRE_EQUAL(right_child(49, 5), 35_u64); + BOOST_REQUIRE_EQUAL(right_child(50, 5), 37_u64); + BOOST_REQUIRE_EQUAL(right_child(44, 5), 25_u64); +} + BOOST_AUTO_TEST_CASE(utreexo__is_root_populated__various__expected) { static_assert(!is_root_populated(0b00000000, 0)); @@ -66,14 +127,14 @@ BOOST_AUTO_TEST_CASE(utreexo__left_sibling__various__expected) BOOST_AUTO_TEST_CASE(utreexo__start_position_at_row__various__expected) { // forest_rows must be >= row. - static_assert(start_position_at_row(0, 0) == 0_u64); - static_assert(start_position_at_row(0, 1) == 0_u64); - static_assert(start_position_at_row(1, 1) == 2_u64); - static_assert(start_position_at_row(1, 2) == 4_u64); - static_assert(start_position_at_row(1, 3) == 8_u64); - static_assert(start_position_at_row(2, 2) == 6_u64); - static_assert(start_position_at_row(2, 3) == 12_u64); - static_assert(start_position_at_row(3, 3) == 14_u64); + static_assert(start_position_at_row(0, 0) == 0); + static_assert(start_position_at_row(0, 1) == 0); + static_assert(start_position_at_row(1, 1) == 2); + static_assert(start_position_at_row(1, 2) == 4); + static_assert(start_position_at_row(1, 3) == 8); + static_assert(start_position_at_row(2, 2) == 6); + static_assert(start_position_at_row(2, 3) == 12); + static_assert(start_position_at_row(3, 3) == 14); BOOST_REQUIRE_EQUAL(start_position_at_row(0, 0), 0_u64); BOOST_REQUIRE_EQUAL(start_position_at_row(0, 1), 0_u64); BOOST_REQUIRE_EQUAL(start_position_at_row(1, 1), 2_u64); @@ -86,11 +147,11 @@ BOOST_AUTO_TEST_CASE(utreexo__start_position_at_row__various__expected) BOOST_AUTO_TEST_CASE(utreexo__number_of_roots__various__expected) { - static_assert(number_of_roots(0) == 0_size); - static_assert(number_of_roots(1) == 1_size); - static_assert(number_of_roots(2) == 1_size); - static_assert(number_of_roots(3) == 2_size); - static_assert(number_of_roots(0xfefefefefefefefe) == (64_size - 8_size)); + static_assert(number_of_roots(0) == 0); + static_assert(number_of_roots(1) == 1); + static_assert(number_of_roots(2) == 1); + static_assert(number_of_roots(3) == 2); + static_assert(number_of_roots(0xfefefefefefefefe) == (64 - 8)); BOOST_REQUIRE_EQUAL(number_of_roots(0), 0_size); BOOST_REQUIRE_EQUAL(number_of_roots(1), 1_size); BOOST_REQUIRE_EQUAL(number_of_roots(2), 1_size); @@ -131,10 +192,10 @@ BOOST_AUTO_TEST_CASE(utreexo__is_sibling__various__expected) // github.com/mit-dci/rustreexo/blob/main/src/accumulator/util.rs#L474 BOOST_AUTO_TEST_CASE(utreexo__children__various__expected) { - static_assert(children(4, 2) == 0_u64); - static_assert(children(49, 5) == 34_u64); - static_assert(children(50, 5) == 36_u64); - static_assert(children(44, 5) == 24_u64); + static_assert(children(4, 2) == 0); + static_assert(children(49, 5) == 34); + static_assert(children(50, 5) == 36); + static_assert(children(44, 5) == 24); BOOST_REQUIRE_EQUAL(children(4, 2), 0_u64); BOOST_REQUIRE_EQUAL(children(49, 5), 34_u64); BOOST_REQUIRE_EQUAL(children(50, 5), 36_u64); @@ -151,10 +212,10 @@ BOOST_AUTO_TEST_CASE(utreexo__is_root_position__various__expected) // github.com/mit-dci/rustreexo/blob/main/src/accumulator/util.rs#L424 BOOST_AUTO_TEST_CASE(utreexo__tree_rows__various__expected) { - static_assert(tree_rows(8) == 3_u8); - static_assert(tree_rows(9) == 4_u8); - static_assert(tree_rows(12) == 4_u8); - static_assert(tree_rows(255) == 8_u8); + static_assert(tree_rows(8) == 3); + static_assert(tree_rows(9) == 4); + static_assert(tree_rows(12) == 4); + static_assert(tree_rows(255) == 8); BOOST_REQUIRE_EQUAL(tree_rows(8), 3_u8); BOOST_REQUIRE_EQUAL(tree_rows(9), 4_u8); BOOST_REQUIRE_EQUAL(tree_rows(12), 4_u8); @@ -192,15 +253,17 @@ BOOST_AUTO_TEST_CASE(utreexo__detect_row__scenario__expected) return success; }; - static_assert(test_detect_row_()); + + // constexpr evaluation hit maximum step limit. + ////static_assert(test_detect_row_()); BOOST_REQUIRE(test_detect_row_()); } // github.com/mit-dci/rustreexo/blob/main/src/accumulator/util.rs#L359 BOOST_AUTO_TEST_CASE(utreexo__root_position__various__expected) { - static_assert(root_position(5, 2, 3) == 12_u64); - static_assert(root_position(5, 0, 3) == 4_u64); + static_assert(root_position(5, 2, 3) == 12); + static_assert(root_position(5, 0, 3) == 4); BOOST_REQUIRE_EQUAL(root_position(5, 2, 3), 12_u64); BOOST_REQUIRE_EQUAL(root_position(5, 0, 3), 4_u64); } @@ -246,14 +309,30 @@ BOOST_AUTO_TEST_CASE(utreexo__detwin__various__expected) // 0 1 2 3 4 5 6 7 const positions expected1{ 7, 8, 10 }; - positions targets1{ 0, 1, 4, 5, 7 }; - detwin(targets1, 3); - BOOST_REQUIRE_EQUAL(targets1, expected1); + const positions targets1{ 0, 1, 4, 5, 7 }; + BOOST_REQUIRE_EQUAL(detwin(targets1, 3), expected1); const positions expected2{ 4, 6, 12 }; - positions targets2{ 4, 6, 8, 9 }; - detwin(targets2, 3); - BOOST_REQUIRE_EQUAL(targets2, expected2); + const positions targets2{ 4, 6, 8, 9 }; + BOOST_REQUIRE_EQUAL(detwin(targets2, 3), expected2); +} + +BOOST_AUTO_TEST_CASE(utreexo__get_proof_positions__sorted__expected) +{ + const positions expected{ 6, 9 }; + constexpr uint64_t leaves = 8; + constexpr auto rows = tree_rows(leaves); + const auto targets = get_proof_positions({ 4, 5, 7, 8 }, leaves, rows); + BOOST_REQUIRE_EQUAL(targets, expected); +} + +BOOST_AUTO_TEST_CASE(utreexo__get_proof_positions__unsorted__expected) +{ + const positions expected{ 6, 9 }; + constexpr uint64_t leaves = 8; + constexpr auto rows = tree_rows(leaves); + const auto targets = get_proof_positions({ 4, 8, 7, 5 }, leaves, rows); + BOOST_REQUIRE_EQUAL(targets, expected); } // rustreexo examples diff --git a/test/utreexo/utreexo.hpp b/test/utreexo/utreexo.hpp index 8a1ab130c5..c493f0c93f 100644 --- a/test/utreexo/utreexo.hpp +++ b/test/utreexo/utreexo.hpp @@ -20,21 +20,18 @@ #define LIBBITCOIN_SYSTEM_UTREEXO_HPP #include +#include #include #include namespace libbitcoin { namespace system { namespace utreexo { - -using node_hash = hash_digest; + using positions = std::vector; +using node_hash = hash_digest; constexpr auto empty_hash = node_hash{}; - -constexpr std::pair okay(bool result) NOEXCEPT -{ - return { result, (result ? "success" : "fail") }; -} +constexpr auto dummy_hash = base16_hash("4242424242424242424242424242424242424242424242424242424242424242"); constexpr node_hash parent_hash(const node_hash& left, const node_hash& right) NOEXCEPT @@ -47,41 +44,41 @@ constexpr node_hash hash_from_u8(uint8_t byte) NOEXCEPT return sha256::hash(byte); } -// TODO: test. -// return parent position of passed-in child -constexpr uint64_t parent(uint64_t position, uint8_t forest_rows) NOEXCEPT +constexpr uint64_t parent(uint64_t child, uint8_t forest_rows) NOEXCEPT { - return set_right(shift_right(position), forest_rows); + return set_right(shift_right(child), forest_rows); } -constexpr uint64_t children(uint64_t position, uint8_t forest_rows) NOEXCEPT +constexpr uint64_t children(uint64_t parent, uint8_t forest_rows) NOEXCEPT { - const auto mask = unmask_right(add1(forest_rows)); - return bit_and(shift_left(position), mask); + // What happens when these bits are lost? + BC_ASSERT(!is_left_shift_overflow(parent, add1(forest_rows))); + + return bit_and(shift_left(parent), + unmask_right(add1(forest_rows))); } -// TODO: test. -constexpr uint64_t left_child(uint64_t position, uint8_t forest_rows) NOEXCEPT +constexpr uint64_t left_child(uint64_t parent, uint8_t forest_rows) NOEXCEPT { - return children(position, forest_rows); + return children(parent, forest_rows); } -// TODO: test. -constexpr uint64_t right_child(uint64_t position, uint8_t forest_rows) NOEXCEPT +constexpr uint64_t right_child(uint64_t parent, uint8_t forest_rows) NOEXCEPT { - BC_ASSERT(!is_add_overflow(children(position, forest_rows), one)); + // What happens when these bits are lost? + BC_ASSERT(!is_add_overflow(children(parent, forest_rows), one)); - return add1(children(position, forest_rows)); + return add1(children(parent, forest_rows)); } -constexpr uint64_t left_sibling(uint64_t position) NOEXCEPT +constexpr uint64_t left_sibling(uint64_t node) NOEXCEPT { - return set_right(position, zero, false); + return set_right(node, zero, false); } -constexpr bool is_left_niece(uint64_t position) NOEXCEPT +constexpr bool is_left_niece(uint64_t node) NOEXCEPT { - return !get_right(position); + return !get_right(node); } constexpr bool is_right_sibling(uint64_t node, uint64_t next) NOEXCEPT @@ -100,25 +97,22 @@ constexpr bool is_root_populated(uint64_t leaves, uint8_t row) NOEXCEPT return get_right(leaves, row); } -constexpr uint8_t detect_row(uint64_t position, uint8_t forest_rows) NOEXCEPT +constexpr uint8_t detect_row(uint64_t node, uint8_t forest_rows) NOEXCEPT { auto bit = forest_rows; - while (!is_zero(bit) && get_right(position, bit)) --bit; + while (!is_zero(bit) && get_right(node, bit)) { --bit; } return subtract(forest_rows, bit); } +// TODO: arbitrary behavior if given row does not have a root. constexpr uint64_t root_position(uint64_t leaves, uint8_t row, uint8_t forest_rows) NOEXCEPT { - // TODO: undefined behavior if given row does not have a root. - BC_ASSERT(!is_add_overflow(row, 1)); - BC_ASSERT(!is_add_overflow(forest_rows, 1)); - BC_ASSERT(!is_subtract_overflow(add1(forest_rows), row)); - - // sub1 cannot overflow here. - const auto mask = unmask_right(add1(forest_rows)); - const auto before = bit_and(leaves, shift_left(mask, add1(row))); - const auto left = shift_left(mask, subtract(add1(forest_rows), row)); + BC_ASSERT(!is_subtract_overflow(add1(forest_rows), row)); + + const auto mask = unmask_right(add1(forest_rows)); + const auto before = bit_and(leaves, shift_left(mask, add1(row))); + const auto left = shift_left(mask, subtract(add1(forest_rows), row)); const auto right = shift_right(before, row); const auto shifted = bit_or(left, right); return bit_and(shifted, mask); @@ -128,31 +122,29 @@ constexpr bool is_root_position(uint64_t position, uint64_t leaves, uint8_t forest_rows) NOEXCEPT { const auto row = detect_row(position, forest_rows); - const auto present = get_right(leaves, row); - const auto expected = root_position(leaves, row, forest_rows); - return present && (position == expected); + return get_right(leaves, row) && + (position == root_position(leaves, row, forest_rows)); } constexpr uint64_t remove_bit(uint64_t value, size_t bit) NOEXCEPT { - const auto mask_lo = mask_right(add1(bit)); - const auto mask_hi = unmask_right(bit); - return bit_or(shift_right(bit_and(value, mask_lo), 1), - bit_and(value, mask_hi)); + const auto hi = unmask_right(bit); + const auto lo = mask_right(add1(bit)); + return bit_or(shift_right(bit_and(value, lo)), bit_and(value, hi)); } -constexpr bool calculate_next(uint64_t& out, uint64_t position, - uint64_t delete_position, uint8_t forest_rows) NOEXCEPT +constexpr bool calculate_next(uint64_t& out, uint64_t node, + uint64_t delete_node, uint8_t forest_rows) NOEXCEPT { - const auto position_row = detect_row(position, forest_rows); - const auto delete_row = detect_row(delete_position, forest_rows); - if (delete_row < position_row) + const auto node_row = detect_row(node, forest_rows); + const auto delete_row = detect_row(delete_node, forest_rows); + if (is_subtract_overflow(delete_row, node_row)) return false; - const auto bit = subtract(delete_row, position_row); - const auto lo = remove_bit(position, bit); + const auto bit = subtract(delete_row, node_row); + const auto lo = remove_bit(node, bit); - const auto row = add1(position_row); + const auto row = add1(node_row); const auto hi = shift_left(bit_right(row), subtract(forest_rows, row)); @@ -178,31 +170,173 @@ constexpr size_t number_of_roots(uint64_t leaves) NOEXCEPT constexpr uint8_t tree_rows(uint64_t leaves) NOEXCEPT { - // nothing here can overflow. return narrow_cast(is_zero(leaves) ? zero : subtract(bits, left_zeros(sub1(leaves)))); } -inline void detwin(positions& nodes, uint8_t forest_rows) NOEXCEPT +inline positions detwin(const positions& nodes, uint8_t forest_rows) NOEXCEPT { if (nodes.empty()) - return; + return {}; + + auto out{ nodes }; + for (auto index{ one }; index < out.size(); ++index) + { + const auto node = out.at(sub1(index)); + const auto next = out.at(index); + + if (is_right_sibling(node, next)) + { + const auto dad = parent(node, forest_rows); + const auto from = std::next(out.begin(), sub1(index)); + const auto stop = std::next(out.begin(), add1(index)); + out.erase(from, stop); + out.push_back(dad); + sort(out); + --index; + } + } + + return out; +} + +// TODO: test. +// TODO: guard overflows. +bool roots_to_destroy(positions& out, std::vector&& roots, + uint64_t adding, uint64_t leaves) NOEXCEPT +{ + uint8_t row{}; + + for (auto leaf{ adding }; !is_zero(leaf); --leaf, ++leaves) + { + while (get_right(leaves, add1(row))) + { + if (roots.empty()) + return false; + + if (roots.back() == empty_hash) + { + const auto rows = tree_rows(add(leaves, leaf)); + out.push_back(root_position(leaves, row, rows)); + } + + roots.pop_back(); + ++row; + } + + roots.push_back(dummy_hash); + } + + return true; +} + +// TODO: test. +// TODO: guard overflows. +std::tuple detect_offset(uint64_t node, + uint64_t leaves) NOEXCEPT +{ + uint8_t trees{}; + auto rows = tree_rows(leaves); + const auto row = detect_row(node, rows); - for (auto node = std::next(nodes.begin()); node != nodes.end();) + while (true) { - const auto prior = std::prev(node); + const auto mask = unmask_right(rows); + const auto size = bit_and(set_right(rows), leaves); + if (bit_and(shift_left(node, row), mask) < size) + break; - if (is_right_sibling(*prior, *node)) + if (!is_zero(size)) { - const auto dad = parent(*prior, forest_rows); - node = nodes.erase(prior, std::next(node)); - nodes.push_back(dad); - sort(nodes); - continue; + node -= size; + ++trees; } - ++node; + --rows; + }; + + return { trees, subtract(rows, row), !node }; +} + +// TODO: test. +constexpr bool parent_many(uint64_t& out, uint64_t node, uint8_t rise, + uint8_t forest_rows) NOEXCEPT +{ + if (is_zero(rise)) + { + out = node; + return true; + } + + if (rise > forest_rows) + return false; + + const auto left = subtract(forest_rows, sub1(rise)); + const auto mask = sub1(bit_right(add1(forest_rows))); + out = bit_and(bit_or(shift_right(node, rise), shift_left(mask, left)), mask); + return true; +} + +// TODO: test. +constexpr bool max_position_at_row(uint64_t& out, uint8_t row, uint8_t rows, + uint64_t leaves) NOEXCEPT +{ + uint64_t many{}; + if (!parent_many(many, leaves, row, rows)) + return false; + + out = floored_subtract(many, one); + return true; +} + +// TODO: test. +constexpr bool is_ancestor(uint64_t higer, uint64_t lower, + uint8_t forest_rows) NOEXCEPT +{ + if (higer == lower) + return false; + + uint64_t ancestor{}; + const auto lo = detect_row(lower, forest_rows); + const auto hi = detect_row(higer, forest_rows); + return !is_subtract_overflow(hi, lo) && + parent_many(ancestor, lower, subtract(hi, lo), forest_rows) && + (ancestor == higer); +} + +inline positions get_proof_positions(positions&& targets, uint64_t leaves, + uint8_t forest_rows) NOEXCEPT +{ + sort(targets); + positions proof{}; + + for (uint8_t row{}; row < forest_rows; ++row) + { + auto sorted{ true }; + const auto rows{ targets }; + + for (auto it = rows.begin(); it != rows.end(); ++it) + { + const auto node = *it; + if ((detect_row(node, forest_rows) != row) || + is_root_position(node, leaves, forest_rows)) + continue; + + const auto next = std::next(it); + if (next != rows.end() && is_sibling(node, *next)) + ++it; + else + proof.push_back(bit_xor(node, one)); + + targets.push_back(parent(node, forest_rows)); + sorted = false; + } + + if (!sorted) + sort(targets); } + + return proof; } } // namespace utreexo @@ -210,166 +344,3 @@ inline void detwin(positions& nodes, uint8_t forest_rows) NOEXCEPT } // namespace libbitcoin #endif - -////// roots_to_destroy returns the empty roots that get written over after num_adds -////// amount of leaves have been added. -////pub fn roots_to_destroy( -//// num_adds: u64, -//// mut num_leaves: u64, -//// orig_roots: &[Hash], -////) -> Vec { -//// let mut roots = orig_roots.to_vec(); -//// let mut deleted = vec![]; -//// let mut h = 0; -//// for add in 0..num_adds { -//// while (num_leaves >> h) & 1 == 1 { -//// let root = roots -//// .pop() -//// .expect("If (num_leaves >> h) & 1 == 1, it must have at least one root left"); -//// if root.is_empty() { -//// let root_pos = -//// root_position(num_leaves, h, tree_rows(num_leaves + (num_adds - add))); -//// deleted.push(root_pos); -//// } -//// h += 1; -//// } -//// // Just adding a non-zero value to the slice. -//// roots.push(AccumulatorHash::placeholder()); -//// num_leaves += 1; -//// } -//// -//// deleted -////} - -////pub fn detect_offset(pos: u64, num_leaves: u64) -> (u8, u8, u64) { -//// let mut tr = tree_rows(num_leaves); -//// let nr = detect_row(pos, tr); -//// -//// let mut bigger_trees: u8 = 0; -//// let mut marker = pos; -//// -//// // add trees until you would exceed position of node -//// -//// // This is a bit of an ugly predicate. The goal is to detect if we've -//// // gone past the node we're looking for by inspecting progressively shorter -//// // trees; once we have, the loop is over. - -//// // The predicate breaks down into 3 main terms: -//// // A: pos << nh -//// // B: mask -//// // C: 1<= C) -//// // A is position up-shifted by the row of the node we're targeting. -//// // B is the "mask" we use in other functions; a bunch of 0s at the MSB side -//// // and then a bunch of 1s on the LSB side, such that we can use bitwise AND -//// // to discard high bits. Together, A&B is shifting position up by nr bits, -//// // and then discarding (zeroing out) the high bits. This is the same as in -//// // n_grandchild. C checks for whether a tree exists at the current tree -//// // rows. If there is no tree at tr, C is 0. If there is a tree, it will -//// // return a power of 2: the base size of that tree. -//// // The C term actually is used 3 times here, which is ugly; it's redefined -//// // right on the next line. -//// // In total, what this loop does is to take a node position, and -//// // see if it's in the next largest tree. If not, then subtract everything -//// // covered by that tree from the position, and proceed to the next tree, -//// // skipping trees that don't exist. - -//// while (marker << nr) & ((2 << tr) - 1) >= (1 << tr) & num_leaves { -//// let tree_size = (1 << tr) & num_leaves; -//// if tree_size != 0 { -//// marker -= tree_size; -//// bigger_trees += 1; -//// } -//// tr -= 1; -//// } -//// -//// (bigger_trees, tr - nr, !marker) -////} - -/////// max_position_at_row returns the biggest position an accumulator can have for the -/////// requested row for the given num_leaves. -////pub fn max_position_at_row(row: u8, total_rows: u8, num_leaves: u64) -> Result { -//// Ok(parent_many(num_leaves, row, total_rows)?.saturating_sub(1)) -////} -//// -////pub fn read_u64(buf: &mut Source) -> Result { -//// let mut bytes = [0u8; 8]; -//// buf.read_exact(&mut bytes) -//// .map_err(|_| "Failed to read u64")?; -//// Ok(u64::from_le_bytes(bytes)) -////} - -////pub fn parent_many(pos: u64, rise: u8, forest_rows: u8) -> Result { -//// if rise == 0 { -//// return Ok(pos); -//// } -//// if rise > forest_rows { -//// return Err(format!( -//// "Cannot rise more than the forestRows: rise: {} forest_rows: {}", -//// rise, forest_rows -//// )); -//// } -//// -//// let mask = (2_u64 << forest_rows) - 1; -//// Ok((pos >> rise | (mask << (forest_rows - (rise - 1)) as u64)) & mask) -////} - -////std::pair is_ancestor(uint64_t higher_pos, -//// uint64_t lower_pos, uint8_t forest_rows) -////{ -//// if (higher_pos == lower_pos) -//// return okay(false); -//// -//// auto lower_row = detect_row(lower_pos, forest_rows); -//// auto higher_row = detect_row(higher_pos, forest_rows); -//// -//// // Prevent underflows by checking that the higherRow is not less than lowerRow. -//// if (higher_row < lower_row) -//// return okay(false); -//// -//// // TODO: Return false if we error out or the calculated ancestor doesn't match the higherPos. -//// auto ancestor = parent_many(lower_pos, higher_row - lower_row, forest_rows); -//// -//// return (higher_pos == ancestor); -////} - -/////// Returns which node should have its hashes on the proof, along with all nodes -/////// whose hashes will be calculated to reach a root -////Vec get_proof_positions(targets: &[u64], num_leaves: u64, forest_rows: u8) NOEXCEPT -////{ -//// let mut proof_positions = vec![]; -//// let mut computed_positions = targets.to_vec(); -//// computed_positions.sort(); -//// -//// for row in 0..=forest_rows { -//// let mut row_targets = computed_positions -//// .iter() -//// .cloned() -//// .filter(|x| super::util::detect_row(*x, forest_rows) == row) -//// .collect::>() -//// .into_iter() -//// .peekable(); -//// -//// while let Some(node) = row_targets.next() { -//// if is_root_position(node, num_leaves, forest_rows) { -//// continue; -//// } -//// -//// if let Some(next) = row_targets.peek() { -//// if !is_sibling(node, *next) { -//// proof_positions.push(node ^ 1); -//// } else { -//// row_targets.next(); -//// } -//// } else { -//// proof_positions.push(node ^ 1); -//// } -//// -//// computed_positions.push(parent(node, forest_rows)); -//// } -//// -//// computed_positions.sort(); -//// } -//// -//// proof_positions -////}