From 94469db8571fcc1ec15e9f08b6c4dc5e5a701f6f Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Mon, 27 Nov 2023 16:26:46 -0600 Subject: [PATCH 1/3] GH-1913 Add a incremental merkle tree that does not do left/right bit flip --- libraries/chain/controller.cpp | 6 +- .../eosio/chain/block_header_state.hpp | 4 +- .../eosio/chain/incremental_merkle.hpp | 42 +-- .../chain/include/eosio/chain/merkle.hpp | 2 +- libraries/chain/merkle.cpp | 2 +- unittests/block_tests.cpp | 4 +- unittests/forked_tests.cpp | 4 +- unittests/merkle_tree_tests.cpp | 263 ++++++++++++++++++ unittests/protocol_feature_tests.cpp | 2 +- 9 files changed, 300 insertions(+), 29 deletions(-) create mode 100644 unittests/merkle_tree_tests.cpp diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 9093d19931..6532409859 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -1883,14 +1883,14 @@ struct controller_impl { auto action_merkle_fut = post_async_task( thread_pool.get_executor(), [ids{std::move( bb._action_receipt_digests )}]() mutable { - return merkle( std::move( ids ) ); + return canonical_merkle( std::move( ids ) ); } ); const bool calc_trx_merkle = !std::holds_alternative(bb._trx_mroot_or_receipt_digests); std::future trx_merkle_fut; if( calc_trx_merkle ) { trx_merkle_fut = post_async_task( thread_pool.get_executor(), [ids{std::move( std::get(bb._trx_mroot_or_receipt_digests) )}]() mutable { - return merkle( std::move( ids ) ); + return canonical_merkle( std::move( ids ) ); } ); } @@ -2465,7 +2465,7 @@ struct controller_impl { for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - return merkle( std::move(trx_digests) ); + return canonical_merkle( std::move(trx_digests) ); } void update_producers_authority() { diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index 996aa6b2e4..a8e36f339e 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -30,7 +30,7 @@ namespace legacy { uint32_t dpos_proposed_irreversible_blocknum = 0; uint32_t dpos_irreversible_blocknum = 0; producer_schedule_type active_schedule; - incremental_merkle blockroot_merkle; + incremental_canonical_merkle blockroot_merkle; flat_map producer_to_last_produced; flat_map producer_to_last_implied_irb; public_key_type block_signing_key; @@ -59,7 +59,7 @@ namespace detail { uint32_t dpos_irreversible_blocknum = 0; producer_authority_schedule active_schedule; uint32_t last_proposed_finalizer_set_generation = 0; // TODO: Add to snapshot_block_header_state_v3 - incremental_merkle blockroot_merkle; + incremental_canonical_merkle blockroot_merkle; flat_map producer_to_last_produced; flat_map producer_to_last_implied_irb; block_signing_authority valid_block_signing_authority; diff --git a/libraries/chain/include/eosio/chain/incremental_merkle.hpp b/libraries/chain/include/eosio/chain/incremental_merkle.hpp index 0a84d076f5..d0292a0c4e 100644 --- a/libraries/chain/include/eosio/chain/incremental_merkle.hpp +++ b/libraries/chain/include/eosio/chain/incremental_merkle.hpp @@ -90,14 +90,13 @@ inline void move_nodes(Container& to, Container&& from) { * change. This allows proofs based on this merkle to be very stable * after some time has past only needing to update or add a single * value to maintain validity. + * + * @param canonical if true use the merkle make_canonical_pair which sets the left/right bits of the hash */ -template class Container = vector, typename ...Args> +template class Container = vector, typename ...Args> class incremental_merkle_impl { public: - incremental_merkle_impl() - :_node_count(0) - {} - + incremental_merkle_impl() = default; incremental_merkle_impl( const incremental_merkle_impl& ) = default; incremental_merkle_impl( incremental_merkle_impl&& ) = default; incremental_merkle_impl& operator= (const incremental_merkle_impl& ) = default; @@ -188,7 +187,11 @@ class incremental_merkle_impl { // calculate the partially realized node value by implying the "right" value is identical // to the "left" value - top = DigestType::hash(make_canonical_pair(top, top)); + if constexpr (canonical) { + top = DigestType::hash(make_canonical_pair(top, top)); + } else { + top = DigestType::hash(std::make_pair(std::cref(top), std::cref(top))); + } partial = true; } else { // we are collapsing from a "right" value and an fully-realized "left" @@ -204,11 +207,15 @@ class incremental_merkle_impl { } // calculate the node - top = DigestType::hash(make_canonical_pair(left_value, top)); + if constexpr (canonical) { + top = DigestType::hash(make_canonical_pair(left_value, top)); + } else { + top = DigestType::hash(std::make_pair(std::cref(left_value), std::cref(top))); + } } // move up a level in the tree - current_depth--; + --current_depth; index = index >> 1; } @@ -219,16 +226,14 @@ class incremental_merkle_impl { detail::move_nodes(_active_nodes, std::move(updated_active_nodes)); // update the node count - _node_count++; + ++_node_count; return _active_nodes.back(); } - /**l + /** * return the current root of the incremental merkle - * - * @return */ DigestType get_root() const { if (_node_count > 0) { @@ -238,14 +243,17 @@ class incremental_merkle_impl { } } -// private: - uint64_t _node_count; + private: + friend struct fc::reflector; + uint64_t _node_count = 0; Container _active_nodes; }; -typedef incremental_merkle_impl incremental_merkle; -typedef incremental_merkle_impl shared_incremental_merkle; +typedef incremental_merkle_impl incremental_canonical_merkle; +typedef incremental_merkle_impl shared_incremental_canonical_merkle; +typedef incremental_merkle_impl incremental_merkle_tree; } } /// eosio::chain -FC_REFLECT( eosio::chain::incremental_merkle, (_active_nodes)(_node_count) ); +FC_REFLECT( eosio::chain::incremental_canonical_merkle, (_active_nodes)(_node_count) ); +FC_REFLECT( eosio::chain::incremental_merkle_tree, (_active_nodes)(_node_count) ); diff --git a/libraries/chain/include/eosio/chain/merkle.hpp b/libraries/chain/include/eosio/chain/merkle.hpp index b99d18c101..a622ab7fbc 100644 --- a/libraries/chain/include/eosio/chain/merkle.hpp +++ b/libraries/chain/include/eosio/chain/merkle.hpp @@ -17,6 +17,6 @@ namespace eosio { namespace chain { /** * Calculates the merkle root of a set of digests, if ids is odd it will duplicate the last id. */ - digest_type merkle( deque ids ); + digest_type canonical_merkle( deque ids ); } } /// eosio::chain diff --git a/libraries/chain/merkle.cpp b/libraries/chain/merkle.cpp index e4211f7bfd..ece0e573cc 100644 --- a/libraries/chain/merkle.cpp +++ b/libraries/chain/merkle.cpp @@ -32,7 +32,7 @@ bool is_canonical_right(const digest_type& val) { } -digest_type merkle(deque ids) { +digest_type canonical_merkle(deque ids) { if( 0 == ids.size() ) { return digest_type(); } while( ids.size() > 1 ) { diff --git a/unittests/block_tests.cpp b/unittests/block_tests.cpp index 2236758dcf..3119343572 100644 --- a/unittests/block_tests.cpp +++ b/unittests/block_tests.cpp @@ -35,7 +35,7 @@ BOOST_AUTO_TEST_CASE(block_with_invalid_tx_test) const auto& trxs = copy_b->transactions; for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - copy_b->transaction_mroot = merkle( std::move(trx_digests) ); + copy_b->transaction_mroot = canonical_merkle( std::move(trx_digests) ); // Re-sign the block auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state()->blockroot_merkle.get_root() ) ); @@ -115,7 +115,7 @@ std::pair corrupt_trx_in_block(validating_te const auto& trxs = copy_b->transactions; for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - copy_b->transaction_mroot = merkle( std::move(trx_digests) ); + copy_b->transaction_mroot = canonical_merkle( std::move(trx_digests) ); // Re-sign the block auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), main.control->head_block_state()->blockroot_merkle.get_root() ) ); diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index b375773391..b8cd8a154c 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -30,8 +30,8 @@ BOOST_AUTO_TEST_CASE( irrblock ) try { } FC_LOG_AND_RETHROW() struct fork_tracker { - vector blocks; - incremental_merkle block_merkle; + vector blocks; + incremental_canonical_merkle block_merkle; }; BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { diff --git a/unittests/merkle_tree_tests.cpp b/unittests/merkle_tree_tests.cpp new file mode 100644 index 0000000000..59838cf596 --- /dev/null +++ b/unittests/merkle_tree_tests.cpp @@ -0,0 +1,263 @@ +#include +#include +#include + +using namespace eosio::chain; + +BOOST_AUTO_TEST_SUITE(merkle_tree_tests) + +BOOST_AUTO_TEST_CASE(basic_append_and_root_check_canonical) { + incremental_canonical_merkle tree; + BOOST_CHECK_EQUAL(tree.get_root(), fc::sha256()); + + auto node1 = fc::sha256::hash("Node1"); + tree.append(node1); + BOOST_CHECK_EQUAL(tree.get_root(), node1); +} + +BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { + incremental_canonical_merkle tree; + auto node1 = fc::sha256::hash("Node1"); + auto node2 = fc::sha256::hash("Node2"); + auto node3 = fc::sha256::hash("Node3"); + auto node4 = fc::sha256::hash("Node4"); + auto node5 = fc::sha256::hash("Node5"); + auto node6 = fc::sha256::hash("Node6"); + auto node7 = fc::sha256::hash("Node7"); + auto node8 = fc::sha256::hash("Node8"); + auto node9 = fc::sha256::hash("Node9"); + + tree.append(node1); + BOOST_CHECK_EQUAL(tree.get_root(), node1); + + tree.append(node2); + BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(make_canonical_pair(node1, node2)).str()); + + tree.append(node3); + BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node3)))).str()); + + tree.append(node4); + auto calculated_root = fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node4)))); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node5); + calculated_root = fc::sha256::hash( + make_canonical_pair( + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node4)) + )), + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node5, node5)), + fc::sha256::hash(make_canonical_pair(node5, node5)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node6); + calculated_root = fc::sha256::hash( + make_canonical_pair( + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node4)) + )), + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node5, node6)), + fc::sha256::hash(make_canonical_pair(node5, node6)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node7); + calculated_root = fc::sha256::hash( + make_canonical_pair( + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node4)) + )), + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node5, node6)), + fc::sha256::hash(make_canonical_pair(node7, node7)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node8); + calculated_root = fc::sha256::hash( + make_canonical_pair( + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node4)) + )), + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node5, node6)), + fc::sha256::hash(make_canonical_pair(node7, node8)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node9); + calculated_root = fc::sha256::hash(make_canonical_pair( + fc::sha256::hash( + make_canonical_pair( + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node1, node2)), + fc::sha256::hash(make_canonical_pair(node3, node4)) + )), + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node5, node6)), + fc::sha256::hash(make_canonical_pair(node7, node8)) + )) + ) + ), + fc::sha256::hash( + make_canonical_pair( + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node9, node9)), + fc::sha256::hash(make_canonical_pair(node9, node9)) + )), + fc::sha256::hash(make_canonical_pair( + fc::sha256::hash(make_canonical_pair(node9, node9)), + fc::sha256::hash(make_canonical_pair(node9, node9)) + )) + ) + ) )); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); +} + +BOOST_AUTO_TEST_CASE(basic_append_and_root_check) { + incremental_merkle_tree tree; + BOOST_CHECK_EQUAL(tree.get_root(), fc::sha256()); + + auto node1 = fc::sha256::hash("Node1"); + tree.append(node1); + BOOST_CHECK_EQUAL(tree.get_root(), node1); +} + +BOOST_AUTO_TEST_CASE(multiple_appends) { + incremental_merkle_tree tree; + auto node1 = fc::sha256::hash("Node1"); + auto node2 = fc::sha256::hash("Node2"); + auto node3 = fc::sha256::hash("Node3"); + auto node4 = fc::sha256::hash("Node4"); + auto node5 = fc::sha256::hash("Node5"); + auto node6 = fc::sha256::hash("Node6"); + auto node7 = fc::sha256::hash("Node7"); + auto node8 = fc::sha256::hash("Node8"); + auto node9 = fc::sha256::hash("Node9"); + + tree.append(node1); + BOOST_CHECK_EQUAL(tree.get_root(), node1); + + tree.append(node2); + BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(std::make_pair(node1, node2)).str()); + + tree.append(node3); + BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node3)))).str()); + + tree.append(node4); + auto calculated_root = fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node4)))); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node5); + calculated_root = fc::sha256::hash( + std::make_pair( + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node4)) + )), + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node5, node5)), + fc::sha256::hash(std::make_pair(node5, node5)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node6); + calculated_root = fc::sha256::hash( + std::make_pair( + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node4)) + )), + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node5, node6)), + fc::sha256::hash(std::make_pair(node5, node6)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node7); + calculated_root = fc::sha256::hash( + std::make_pair( + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node4)) + )), + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node5, node6)), + fc::sha256::hash(std::make_pair(node7, node7)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node8); + calculated_root = fc::sha256::hash( + std::make_pair( + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node4)) + )), + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node5, node6)), + fc::sha256::hash(std::make_pair(node7, node8)) + )) + ) + ); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); + + tree.append(node9); + calculated_root = fc::sha256::hash(std::make_pair( + fc::sha256::hash( + std::make_pair( + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node1, node2)), + fc::sha256::hash(std::make_pair(node3, node4)) + )), + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node5, node6)), + fc::sha256::hash(std::make_pair(node7, node8)) + )) + ) + ), + fc::sha256::hash( + std::make_pair( + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node9, node9)), + fc::sha256::hash(std::make_pair(node9, node9)) + )), + fc::sha256::hash(std::make_pair( + fc::sha256::hash(std::make_pair(node9, node9)), + fc::sha256::hash(std::make_pair(node9, node9)) + )) + ) + ) )); + BOOST_CHECK_EQUAL(tree.get_root().str(), calculated_root.str()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index d248e60052..ba8822065e 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -2243,7 +2243,7 @@ BOOST_AUTO_TEST_CASE( block_validation_after_stage_1_test ) { try { const auto& trxs = copy_b->transactions; for( const auto& a : trxs ) trx_digests.emplace_back( a.digest() ); - copy_b->transaction_mroot = merkle( std::move(trx_digests) ); + copy_b->transaction_mroot = canonical_merkle( std::move(trx_digests) ); // Re-sign the block auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), tester1.control->head_block_state()->blockroot_merkle.get_root() ) ); From 8be3b1e95ae25af061d7942378d9ecb855a26bbf Mon Sep 17 00:00:00 2001 From: Kevin Heifner Date: Tue, 28 Nov 2023 07:16:45 -0600 Subject: [PATCH 2/3] GH-1913 Renamed incremental_canonical_merkle to incremental_canonical_merkle_tree --- .../chain/include/eosio/chain/block_header_state.hpp | 4 ++-- .../chain/include/eosio/chain/incremental_merkle.hpp | 6 +++--- unittests/forked_tests.cpp | 4 ++-- unittests/merkle_tree_tests.cpp | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libraries/chain/include/eosio/chain/block_header_state.hpp b/libraries/chain/include/eosio/chain/block_header_state.hpp index a8e36f339e..7c8ace0501 100644 --- a/libraries/chain/include/eosio/chain/block_header_state.hpp +++ b/libraries/chain/include/eosio/chain/block_header_state.hpp @@ -30,7 +30,7 @@ namespace legacy { uint32_t dpos_proposed_irreversible_blocknum = 0; uint32_t dpos_irreversible_blocknum = 0; producer_schedule_type active_schedule; - incremental_canonical_merkle blockroot_merkle; + incremental_canonical_merkle_tree blockroot_merkle; flat_map producer_to_last_produced; flat_map producer_to_last_implied_irb; public_key_type block_signing_key; @@ -59,7 +59,7 @@ namespace detail { uint32_t dpos_irreversible_blocknum = 0; producer_authority_schedule active_schedule; uint32_t last_proposed_finalizer_set_generation = 0; // TODO: Add to snapshot_block_header_state_v3 - incremental_canonical_merkle blockroot_merkle; + incremental_canonical_merkle_tree blockroot_merkle; flat_map producer_to_last_produced; flat_map producer_to_last_implied_irb; block_signing_authority valid_block_signing_authority; diff --git a/libraries/chain/include/eosio/chain/incremental_merkle.hpp b/libraries/chain/include/eosio/chain/incremental_merkle.hpp index d0292a0c4e..659f03a6ce 100644 --- a/libraries/chain/include/eosio/chain/incremental_merkle.hpp +++ b/libraries/chain/include/eosio/chain/incremental_merkle.hpp @@ -249,11 +249,11 @@ class incremental_merkle_impl { Container _active_nodes; }; -typedef incremental_merkle_impl incremental_canonical_merkle; -typedef incremental_merkle_impl shared_incremental_canonical_merkle; +typedef incremental_merkle_impl incremental_canonical_merkle_tree; +typedef incremental_merkle_impl shared_incremental_canonical_merkle_tree; typedef incremental_merkle_impl incremental_merkle_tree; } } /// eosio::chain -FC_REFLECT( eosio::chain::incremental_canonical_merkle, (_active_nodes)(_node_count) ); +FC_REFLECT( eosio::chain::incremental_canonical_merkle_tree, (_active_nodes)(_node_count) ); FC_REFLECT( eosio::chain::incremental_merkle_tree, (_active_nodes)(_node_count) ); diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index b8cd8a154c..133ca1fc33 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -30,8 +30,8 @@ BOOST_AUTO_TEST_CASE( irrblock ) try { } FC_LOG_AND_RETHROW() struct fork_tracker { - vector blocks; - incremental_canonical_merkle block_merkle; + vector blocks; + incremental_canonical_merkle_tree block_merkle; }; BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { diff --git a/unittests/merkle_tree_tests.cpp b/unittests/merkle_tree_tests.cpp index 59838cf596..a886caba5b 100644 --- a/unittests/merkle_tree_tests.cpp +++ b/unittests/merkle_tree_tests.cpp @@ -7,7 +7,7 @@ using namespace eosio::chain; BOOST_AUTO_TEST_SUITE(merkle_tree_tests) BOOST_AUTO_TEST_CASE(basic_append_and_root_check_canonical) { - incremental_canonical_merkle tree; + incremental_canonical_merkle_tree tree; BOOST_CHECK_EQUAL(tree.get_root(), fc::sha256()); auto node1 = fc::sha256::hash("Node1"); @@ -16,7 +16,7 @@ BOOST_AUTO_TEST_CASE(basic_append_and_root_check_canonical) { } BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { - incremental_canonical_merkle tree; + incremental_canonical_merkle_tree tree; auto node1 = fc::sha256::hash("Node1"); auto node2 = fc::sha256::hash("Node2"); auto node3 = fc::sha256::hash("Node3"); @@ -28,7 +28,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends_canonical) { auto node9 = fc::sha256::hash("Node9"); tree.append(node1); - BOOST_CHECK_EQUAL(tree.get_root(), node1); + BOOST_CHECK_EQUAL(tree.get_root().str(), node1.str()); tree.append(node2); BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(make_canonical_pair(node1, node2)).str()); @@ -155,7 +155,7 @@ BOOST_AUTO_TEST_CASE(multiple_appends) { auto node9 = fc::sha256::hash("Node9"); tree.append(node1); - BOOST_CHECK_EQUAL(tree.get_root(), node1); + BOOST_CHECK_EQUAL(tree.get_root().str(), node1.str()); tree.append(node2); BOOST_CHECK_EQUAL(tree.get_root().str(), fc::sha256::hash(std::make_pair(node1, node2)).str()); From d0111b813489974b4f6b7a1e6ab799acddbecfff Mon Sep 17 00:00:00 2001 From: greg7mdp Date: Tue, 28 Nov 2023 10:36:26 -0500 Subject: [PATCH 3/3] Add test for quorum state transitions. --- .../include/eosio/hotstuff/qc_chain.hpp | 2 +- libraries/hotstuff/test/hotstuff_tools.cpp | 222 ++++++++++++++++-- 2 files changed, 205 insertions(+), 19 deletions(-) diff --git a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp index e80dcb9400..3c1113536c 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/qc_chain.hpp @@ -233,7 +233,7 @@ namespace eosio::hotstuff { if (weak + strong >= _quorum) _state = state_t::weak_achieved; - if (weak >= (_num_finalizers - _quorum)) { + if (weak > (_num_finalizers - _quorum)) { if (_state == state_t::weak_achieved) _state = state_t::weak_final; else if (_state == state_t::unrestricted) diff --git a/libraries/hotstuff/test/hotstuff_tools.cpp b/libraries/hotstuff/test/hotstuff_tools.cpp index e2d3e994c9..ba1131c9c7 100644 --- a/libraries/hotstuff/test/hotstuff_tools.cpp +++ b/libraries/hotstuff/test/hotstuff_tools.cpp @@ -3,37 +3,42 @@ #include #include +#include +#include #include -BOOST_AUTO_TEST_CASE(view_number_tests) try { - - eosio::hotstuff::hs_proposal_message hspm_1; - eosio::hotstuff::hs_proposal_message hspm_2; - eosio::hotstuff::hs_proposal_message hspm_3; - eosio::hotstuff::hs_proposal_message hspm_4; - eosio::hotstuff::hs_proposal_message hspm_5; - hspm_1.block_id = eosio::chain::block_id_type("0b93846ba73bdfdc9b2383863b64f8f921c8a2379d6dde4e05bdd2e434e9392a"); //UX Network block #194217067 +BOOST_AUTO_TEST_CASE(view_number_tests) try { + using namespace eosio::hotstuff; + using eosio::chain::block_id_type; + + hs_proposal_message hspm_1; + hs_proposal_message hspm_2; + hs_proposal_message hspm_3; + hs_proposal_message hspm_4; + hs_proposal_message hspm_5; + + hspm_1.block_id = block_id_type("0b93846ba73bdfdc9b2383863b64f8f921c8a2379d6dde4e05bdd2e434e9392a"); //UX Network block #194217067 hspm_1.phase_counter = 0; - hspm_2.block_id = eosio::chain::block_id_type("0b93846ba73bdfdc9b2383863b64f8f921c8a2379d6dde4e05bdd2e434e9392a"); //UX Network block #194217067 + hspm_2.block_id = block_id_type("0b93846ba73bdfdc9b2383863b64f8f921c8a2379d6dde4e05bdd2e434e9392a"); //UX Network block #194217067 hspm_2.phase_counter = 1; - hspm_3.block_id = eosio::chain::block_id_type("0b93846cf55a3ecbcd8f9bd86866b1aecc2e8bd981e40c92609ce3a68dbd0824"); //UX Network block #194217068 + hspm_3.block_id = block_id_type("0b93846cf55a3ecbcd8f9bd86866b1aecc2e8bd981e40c92609ce3a68dbd0824"); //UX Network block #194217068 hspm_3.phase_counter = 0; - hspm_4.block_id = eosio::chain::block_id_type("0b93846cf55a3ecbcd8f9bd86866b1aecc2e8bd981e40c92609ce3a68dbd0824"); //UX Network block #194217068 + hspm_4.block_id = block_id_type("0b93846cf55a3ecbcd8f9bd86866b1aecc2e8bd981e40c92609ce3a68dbd0824"); //UX Network block #194217068 hspm_4.phase_counter = 1; - hspm_5.block_id = eosio::chain::block_id_type("0b93846cf55a3ecbcd8f9bd86866b1aecc2e8bd981e40c92609ce3a68dbd0824"); //UX Network block #194217068 + hspm_5.block_id = block_id_type("0b93846cf55a3ecbcd8f9bd86866b1aecc2e8bd981e40c92609ce3a68dbd0824"); //UX Network block #194217068 hspm_5.phase_counter = 2; - eosio::hotstuff::view_number vn_1 = hspm_1.get_view_number(); - eosio::hotstuff::view_number vn_2 = hspm_2.get_view_number(); - eosio::hotstuff::view_number vn_3 = hspm_3.get_view_number(); - eosio::hotstuff::view_number vn_4 = hspm_4.get_view_number(); - eosio::hotstuff::view_number vn_5 = hspm_5.get_view_number(); + view_number vn_1 = hspm_1.get_view_number(); + view_number vn_2 = hspm_2.get_view_number(); + view_number vn_3 = hspm_3.get_view_number(); + view_number vn_4 = hspm_4.get_view_number(); + view_number vn_5 = hspm_5.get_view_number(); //test getters BOOST_CHECK_EQUAL(vn_1.block_height(), 194217067); @@ -49,8 +54,189 @@ BOOST_AUTO_TEST_CASE(view_number_tests) try { //test constructor - eosio::hotstuff::view_number vn_6 = eosio::hotstuff::view_number(194217068, 2); + view_number vn_6 = view_number(194217068, 2); BOOST_CHECK_EQUAL(vn_5, vn_6); } FC_LOG_AND_RETHROW(); + + +// ----------------------------------------------------------------------------- +// Allow boost to print `pending_quorum_certificate::state_t` +// ----------------------------------------------------------------------------- +namespace std { + using state_t = eosio::hotstuff::pending_quorum_certificate::state_t; + std::ostream& operator<<(std::ostream& os, state_t s) + { + switch(s) { + case state_t::unrestricted: os << "unrestricted"; break; + case state_t::restricted: os << "restricted"; break; + case state_t::weak_achieved: os << "weak_achieved"; break; + case state_t::weak_final: os << "weak_final"; break; + case state_t::strong: os << "strong"; break; + } + return os; + } +} + +BOOST_AUTO_TEST_CASE(qc_state_transitions) try { + using namespace eosio::hotstuff; + using namespace eosio::chain; + using namespace fc::crypto::blslib; + using state_t = pending_quorum_certificate::state_t; + + digest_type d(fc::sha256("0000000000000000000000000000001")); + std::vector digest(d.data(), d.data() + d.data_size()); + + std::vector sk { + bls_private_key("PVT_BLS_r4ZpChd87ooyzl6MIkw23k7PRX8xptp7TczLJHCIIW88h/hS"), + bls_private_key("PVT_BLS_/l7xzXANaB+GrlTsbZEuTiSOiWTtpBoog+TZnirxUUSaAfCo"), + bls_private_key("PVT_BLS_3FoY73Q/gED3ejyg8cvnGqHrMmx4cLKwh/e0sbcsCxpCeqn3"), + bls_private_key("PVT_BLS_warwI76e+pPX9wLFZKPFagngeFM8bm6J8D5w0iiHpxW7PiId"), + bls_private_key("PVT_BLS_iZFwiqdogOl9RNr1Hv1z+Rd6AwD9BIoxZcU1EPX+XFSFmm5p"), + bls_private_key("PVT_BLS_Hmye7lyiCrdF54/nF/HRU0sY/Hrse1ls/yqojIUOVQsxXUIK") + }; + + std::vector pubkey; + pubkey.reserve(sk.size()); + for (const auto& k : sk) + pubkey.push_back(k.get_public_key()); + + auto weak_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index) { + return qc.add_weak_vote(digest, index, pubkey[index], sk[index].sign(digest)); + }; + + auto strong_vote = [&](pending_quorum_certificate& qc, const std::vector& digest, size_t index) { + return qc.add_strong_vote(digest, index, pubkey[index], sk[index].sign(digest)); + }; + + { + pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1 + BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted); + + // add one weak vote + // ----------------- + weak_vote(qc, digest, 0); + BOOST_CHECK_EQUAL(qc._state, state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + // add duplicate weak vote + // ----------------------- + bool ok = weak_vote(qc, digest, 0); + BOOST_CHECK(!ok); // vote was a duplicate + BOOST_CHECK_EQUAL(qc._state, state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + // add another weak vote + // --------------------- + weak_vote(qc, digest, 1); + BOOST_CHECK_EQUAL(qc._state, state_t::weak_final); + } + + { + pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1 + BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted); + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0); + BOOST_CHECK_EQUAL(qc._state, state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1); + BOOST_CHECK_EQUAL(qc._state, state_t::strong); + BOOST_CHECK(qc.is_quorum_met()); + } + + { + pending_quorum_certificate qc(2, 1); // 2 finalizers, quorum = 1 + BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1); + BOOST_CHECK_EQUAL(qc._state, state_t::strong); + BOOST_CHECK(qc.is_quorum_met()); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1); + BOOST_CHECK_EQUAL(qc._state, state_t::strong); + BOOST_CHECK(qc.is_quorum_met()); + } + + { + pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2 + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0); + BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted); + BOOST_CHECK(!qc.is_quorum_met()); + + // add a strong vote + // ----------------- + strong_vote(qc, digest, 1); + BOOST_CHECK_EQUAL(qc._state, state_t::weak_achieved); + BOOST_CHECK(qc.is_quorum_met()); + + { + pending_quorum_certificate qc2(qc); + + // add a weak vote + // --------------- + weak_vote(qc2, digest, 2); + BOOST_CHECK_EQUAL(qc2._state, state_t::weak_final); + BOOST_CHECK(qc2.is_quorum_met()); + } + + { + pending_quorum_certificate qc2(qc); + + // add a strong vote + // ----------------- + strong_vote(qc2, digest, 2); + BOOST_CHECK_EQUAL(qc2._state, state_t::strong); + BOOST_CHECK(qc2.is_quorum_met()); + } + } + + { + pending_quorum_certificate qc(3, 2); // 3 finalizers, quorum = 2 + + // add a weak vote + // --------------- + weak_vote(qc, digest, 0); + BOOST_CHECK_EQUAL(qc._state, state_t::unrestricted); + BOOST_CHECK(!qc.is_quorum_met()); + + // add a weak vote + // --------------- + weak_vote(qc, digest, 1); + BOOST_CHECK_EQUAL(qc._state, state_t::weak_final); + BOOST_CHECK(qc.is_quorum_met()); + + { + pending_quorum_certificate qc2(qc); + + // add a weak vote + // --------------- + weak_vote(qc2, digest, 2); + BOOST_CHECK_EQUAL(qc2._state, state_t::weak_final); + BOOST_CHECK(qc2.is_quorum_met()); + } + + { + pending_quorum_certificate qc2(qc); + + // add a strong vote + // ----------------- + strong_vote(qc2, digest, 2); + BOOST_CHECK_EQUAL(qc2._state, state_t::weak_final); + BOOST_CHECK(qc2.is_quorum_met()); + } + } + +} FC_LOG_AND_RETHROW();