diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index bad5509ec2..26ea080e68 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -27,6 +27,7 @@ add_library( steemit_chain steem_objects.cpp shared_authority.cpp block_log.cpp + incremental_merkle_hash.cpp util/reward.cpp diff --git a/libraries/chain/include/steemit/chain/incremental_merkle_hash.hpp b/libraries/chain/include/steemit/chain/incremental_merkle_hash.hpp new file mode 100644 index 0000000000..432f053df7 --- /dev/null +++ b/libraries/chain/include/steemit/chain/incremental_merkle_hash.hpp @@ -0,0 +1,147 @@ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace steemit { namespace chain { + +/** + * Contains a data structure which allows incremental computation of Merkle root. + */ + +class incremental_merkle_hash +{ + public: + typedef fc::sha256 hash_type; + + incremental_merkle_hash() {} + virtual ~incremental_merkle_hash() {} + + /** + * The algorithm used to compute inner node hashes. + */ + static hash_type hash_pair( const hash_type& left, const hash_type& right ) + { + return hash_type::hash( std::make_pair( left, right ) ); + } + + /** + * The definition of the root hash of an empty tree. + */ + static hash_type empty_tree_hash(); + + /** + * Add a node hash. + * Worst-case performance is O(log(n)) hash_pair() operations. + * Average-case performance for building a tree is O(1) hash_pair() operations. + */ + void add_node_hash( const hash_type& node ); + + /** + * Compute the root hash. Requires O(log(n)) hash_apri + */ + hash_type compute_root_hash()const; + + /** + * Convenience method to hash and add a node of any FC serializable type. + */ + template< typename T > + void add_node( const T& node ) + { + add_node_hash( hash_type::hash( node ) ); + } + + /** + * The number of leaf nodes that have been added. + */ + uint64_t leaf_count = 0; + + /** + * The hash of the root of each L-branch. + * + * An L-branch is a subtree with the following two conditions: + * - The L-branch root's parent is an ancestor node of the next leaf node. + * - The L-branch root is not an ancestor node of the next leaf node. + * + * Since the tree's filled left-to-right, L-branch roots are the left-child siblings + * of the right-child ancestors of the next leaf node. + */ + std::vector< hash_type > lbranches; +}; + +/** + * Incremental Merkle hash which remembers the last computed hash value. + * Avoids expensive recomputation of hashes. Has the same + * binary and JSON serialization as incremental_merkle_hash. + */ + +class annotated_incremental_merkle_hash +{ + typedef incremental_merkle_hash::hash_type hash_type; + + annotated_incremental_merkle_hash() {} + virtual ~annotated_incremental_merkle_hash() {} + + hash_type get_root_hash() + { + if( !root_hash.valid() ) + root_hash = ihash.compute_root_hash(); + return *root_hash; + } + + void add_node_hash( const hash_type& node ) + { + root_hash.reset(); + ihash.add_node_hash( node ); + } + + template< typename T > + void add_node( const T& node ) + { + add_node_hash( hash_type::hash( node ) ); + } + + incremental_merkle_hash ihash; + fc::optional< hash_type > root_hash; +}; + +} } + +FC_REFLECT( steemit::chain::incremental_merkle_hash, (leaf_count)(lbranches) ) + +namespace fc { + +template +void to_variant( const steemit::chain::annotated_incremental_merkle_hash& h, variant& vo ) +{ + to_variant( h.ihash, vo ); +} + +template +void from_variant( const variant& vo, steemit::chain::annotated_incremental_merkle_hash& h ) +{ + from_variant( vo, h.ihash ); +} + +namespace raw { + +template< typename Stream > +inline void pack( Stream& s, const steemit::chain::annotated_incremental_merkle_hash& h ) +{ + fc::raw::pack( s, h.ihash ); +} + +template< typename Stream > +inline void unpack( Stream& s, steemit::chain::annotated_incremental_merkle_hash& h ) +{ + h.root_hash.reset(); + unpack( s, h.ihash ); +} + +} } diff --git a/libraries/chain/incremental_merkle_hash.cpp b/libraries/chain/incremental_merkle_hash.cpp new file mode 100644 index 0000000000..a0a7ec2707 --- /dev/null +++ b/libraries/chain/incremental_merkle_hash.cpp @@ -0,0 +1,63 @@ + +#include + +namespace steemit { namespace chain { + +incremental_merkle_hash::hash_type incremental_merkle_hash::empty_tree_hash() +{ + return incremental_merkle_hash::hash_type::hash(""); +} + +void incremental_merkle_hash::add_node_hash( const incremental_merkle_hash::hash_type& node ) +{ + // + // Every 1 in the binary representation of leaf_count is an L-branch. + // + // An even-numbered leaf will become a new L-branch consisting of a single node. + // + // For an odd-numbered leaf, we can follow some number of right-child links in + // its ancestry until we reach the lowest left-child link (if we traverse all the + // way to the root, we simply reparent the tree as the left child of a new root). + // Call this the R-path. The number of links in the R-path is the number of 1 + // bits at the end of leaf_count. + // + // The root of the right-child links is a new L-branch obtained by combining the + // L-branches along the R-path with the new node. + // + + uint64_t lbits = leaf_count ^ (leaf_count+1); + // leaf_count=...0 -> lbits=1 + // leaf_count=...01 -> lbits=11 + // leaf_count=...011 -> lbits=111 + // leaf_count=...0111 -> lbits=1111 + // leaf_count=...01^K -> lbits=1^(K+1) + + lbranches.push_back( node ); + size_t n = lbranches.size(); + + while( lbits > 1 ) + { + lbranches[n-2] = hash_pair( lbranches[n-2], lbranches[n-1] ); + lbranches.pop_back(); + --n; + lbits >>= 1; + } + ++leaf_count; +} + +incremental_merkle_hash::hash_type incremental_merkle_hash::compute_root_hash()const +{ + size_t n = lbranches.size(); + if( n == 0 ) + return empty_tree_hash(); + + hash_type h = lbranches[--n]; + while( n > 0 ) + { + --n; + h = hash_pair( lbranches[n], h ); + } + return h; +} + +} } // steemit::chain diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 200b91d6ad..ec70ea524f 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include @@ -324,4 +325,200 @@ BOOST_AUTO_TEST_CASE( merkle_root ) BOOST_CHECK( block.calculate_merkle_root() == c(dO) ); } +BOOST_AUTO_TEST_CASE( incremental_merkle ) +{ + vector< incremental_merkle_hash::hash_type > leaf; + const uint32_t num_leaf = 10; + incremental_merkle_hash im; + + for( uint32_t i=0; i digest_type + { return incremental_merkle_hash::hash_pair( left, right ); }; + + try { + + BOOST_CHECK( im.compute_root_hash() == incremental_merkle_hash::empty_tree_hash() ); + + im.add_node_hash( leaf[0] ); + BOOST_CHECK( im.compute_root_hash() == leaf[0] ); + + digest_type dA, dB, dC, dD, dE, dI, dJ, dK, dM, dN, dO; + + /**************** + * * + * A=d(0,1) * + * / \ * + * 0 1 * + * * + ****************/ + + dA = d(leaf[0], leaf[1]); + + im.add_node_hash( leaf[1] ); + BOOST_CHECK( im.compute_root_hash() == dA ); + + /************************* + * * + * I=d(A,B) * + * / \ * + * A=d(0,1) B=2 * + * / \ / * + * 0 1 2 * + * * + *************************/ + + dB = leaf[2]; + dI = d(dA, dB); + + im.add_node_hash( leaf[2] ); + BOOST_CHECK( im.compute_root_hash() == dI ); + + /*************************** + * * + * I=d(A,B) * + * / \ * + * A=d(0,1) B=d(2,3) * + * / \ / \ * + * 0 1 2 3 * + * * + *************************** + */ + + dB = d(leaf[2], leaf[3]); + dI = d(dA, dB); + + im.add_node_hash( leaf[3] ); + BOOST_CHECK( im.compute_root_hash() == dI ); + + /*************************************** + * * + * __M=d(I,J)__ * + * / \ * + * I=d(A,B) J=C * + * / \ / * + * A=d(0,1) B=d(2,3) C=4 * + * / \ / \ / * + * 0 1 2 3 4 * + * * + ***************************************/ + + dC = leaf[4]; + dJ = dC; + dM = d(dI, dJ); + + im.add_node_hash( leaf[4] ); + BOOST_CHECK( im.compute_root_hash() == dM ); + + /************************************** + * * + * __M=d(I,J)__ * + * / \ * + * I=d(A,B) J=C * + * / \ / * + * A=d(0,1) B=d(2,3) C=d(4,5) * + * / \ / \ / \ * + * 0 1 2 3 4 5 * + * * + **************************************/ + + dC = d(leaf[4], leaf[5]); + dJ = dC; + dM = d(dI, dJ); + + im.add_node_hash( leaf[5] ); + BOOST_CHECK( im.compute_root_hash() == dM ); + + /*********************************************** + * * + * __M=d(I,J)__ * + * / \ * + * I=d(A,B) J=d(C,D) * + * / \ / \ * + * A=d(0,1) B=d(2,3) C=d(4,5) D=6 * + * / \ / \ / \ / * + * 0 1 2 3 4 5 6 * + * * + ***********************************************/ + + dD = leaf[6]; + dJ = d(dC, dD); + dM = d(dI, dJ); + + im.add_node_hash( leaf[6] ); + BOOST_CHECK( im.compute_root_hash() == dM ); + + /************************************************* + * * + * __M=d(I,J)__ * + * / \ * + * I=d(A,B) J=d(C,D) * + * / \ / \ * + * A=d(0,1) B=d(2,3) C=d(4,5) D=d(6,7) * + * / \ / \ / \ / \ * + * 0 1 2 3 4 5 6 7 * + * * + *************************************************/ + + dD = d(leaf[6], leaf[7]); + dJ = d(dC, dD); + dM = d(dI, dJ); + + im.add_node_hash( leaf[7] ); + BOOST_CHECK( im.compute_root_hash() == dM ); + + /************************************************************************ + * * + * _____________O=d(M,N)______________ * + * / \ * + * __M=d(I,J)__ N=K * + * / \ / * + * I=d(A,B) J=d(C,D) K=E * + * / \ / \ / * + * A=d(0,1) B=d(2,3) C=d(4,5) D=d(6,7) E=8 * + * / \ / \ / \ / \ / * + * 0 1 2 3 4 5 6 7 8 * + * * + ************************************************************************/ + + dE = leaf[8]; + dK = dE; + dN = dK; + dO = d(dM, dN); + + im.add_node_hash( leaf[8] ); + BOOST_CHECK( im.compute_root_hash() == dO ); + + /************************************************************************ + * * + * _____________O=d(M,N)______________ * + * / \ * + * __M=d(I,J)__ N=K * + * / \ / * + * I=d(A,B) J=d(C,D) K=E * + * / \ / \ / * + * A=d(0,1) B=d(2,3) C=d(4,5) D=d(6,7) E=d(8,9) * + * / \ / \ / \ / \ / \ * + * 0 1 2 3 4 5 6 7 8 9 * + * * + ************************************************************************/ + + dE = d(leaf[8], leaf[9]); + dK = dE; + dN = dK; + dO = d(dM, dN); + + im.add_node_hash( leaf[9] ); + BOOST_CHECK( im.compute_root_hash() == dO ); + } + catch( const fc::exception& e ) + { + wlog( "Caught exception: ${e}", ("e", e.to_detail_string()) ); + BOOST_CHECK( false ); + } +} + BOOST_AUTO_TEST_SUITE_END()