From 0bcfc69da23b7e215d62778da63eb4c760568af2 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Mon, 24 Aug 2015 13:22:20 -0400 Subject: [PATCH] Further improve Merkle root algorithm, implement unit test #266 This commit redefines the hash h of a node in the Merkle tree as: h(unary_node) = unary_node h(binary_node) = H(left_child + right_child) Previous code in c0b9af9a996c0ae90b6816d6b995006ff0b8e5b2 defined hash as: h(unary_node) = H(unary_node + digest_type()) h(binary_node) = H(left_child + right_child) The improved definition in this commit saves some hash computations. --- libraries/chain/protocol/block.cpp | 43 +++---- tests/tests/basic_tests.cpp | 177 +++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 30 deletions(-) diff --git a/libraries/chain/protocol/block.cpp b/libraries/chain/protocol/block.cpp index 808eb927..cda95a32 100644 --- a/libraries/chain/protocol/block.cpp +++ b/libraries/chain/protocol/block.cpp @@ -61,41 +61,24 @@ namespace graphene { namespace chain { if( transactions.size() == 0 ) return checksum_type(); - vector ids; - ids.resize( ((transactions.size() + 1)/2)*2 ); + vector ids; + ids.resize( transactions.size() ); for( uint32_t i = 0; i < transactions.size(); ++i ) ids[i] = transactions[i].merkle_digest(); vector::size_type current_number_of_hashes = ids.size(); - while( true ) + while( current_number_of_hashes > 1 ) { -#define AUG_20_TESTNET_COMPATIBLE -#ifdef AUG_20_TESTNET_COMPATIBLE - for( uint32_t i = 0; i < transactions.size(); i += 2 ) -#else - for( uint32_t i = 0; i < current_number_of_hashes; i += 2 ) -#endif - ids[i/2] = digest_type::hash( std::make_pair( ids[i], ids[i+1] ) ); - // since we're processing hashes in pairs, we need to ensure that we always - // have an even number of hashes in the ids list. If we would end up with - // an odd number, add a default-initialized hash to compensate - current_number_of_hashes /= 2; -#ifdef AUG_20_TESTNET_COMPATIBLE - if (current_number_of_hashes <= 1) - break; -#else - if (current_number_of_hashes == 1) - break; - if (current_number_of_hashes % 2) - { - ++current_number_of_hashes; - // TODO: HARD FORK: we should probably enable the next line the next time we fire - // up a new testnet; it will change the merkle roots we generate, but will - // give us a better-defined algorithm for calculating them - // - ids[current_number_of_hashes - 1] = digest_type(); - } -#endif + // hash ID's in pairs + uint32_t i_max = current_number_of_hashes - (current_number_of_hashes&1); + uint32_t k = 0; + + for( uint32_t i = 0; i < i_max; i += 2 ) + ids[k++] = digest_type::hash( std::make_pair( ids[i], ids[i+1] ) ); + + if( current_number_of_hashes&1 ) + ids[k++] = ids[i_max]; + current_number_of_hashes = k; } return checksum_type::hash( ids[0] ); } diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 6aeae968..8b92ec4e 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -350,4 +350,181 @@ BOOST_AUTO_TEST_CASE( scaled_precision ) GRAPHENE_CHECK_THROW( asset::scaled_precision(19), fc::exception ); } +BOOST_AUTO_TEST_CASE( merkle_root ) +{ + signed_block block; + vector tx; + vector t; + const uint32_t num_tx = 10; + + for( uint32_t i=0; i checksum_type + { return checksum_type::hash( digest ); }; + + auto d = []( const digest_type& left, const digest_type& right ) -> digest_type + { return digest_type::hash( std::make_pair( left, right ) ); }; + + BOOST_CHECK( block.calculate_merkle_root() == checksum_type() ); + + block.transactions.push_back( tx[0] ); + BOOST_CHECK( block.calculate_merkle_root() == + c(t[0]) + ); + + digest_type dA, dB, dC, dD, dE, dI, dJ, dK, dM, dN, dO; + + /* + A=d(0,1) + / \ + 0 1 + */ + + dA = d(t[0], t[1]); + + block.transactions.push_back( tx[1] ); + BOOST_CHECK( block.calculate_merkle_root() == c(dA) ); + + /* + I=d(A,B) + / \ + A=d(0,1) B=2 + / \ / + 0 1 2 + */ + + dB = t[2]; + dI = d(dA, dB); + + block.transactions.push_back( tx[2] ); + BOOST_CHECK( block.calculate_merkle_root() == c(dI) ); + + /* + I=d(A,B) + / \ + A=d(0,1) B=d(2,3) + / \ / \ + 0 1 2 3 + */ + + dB = d(t[2], t[3]); + dI = d(dA, dB); + + block.transactions.push_back( tx[3] ); + BOOST_CHECK( block.calculate_merkle_root() == c(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 = t[4]; + dJ = dC; + dM = d(dI, dJ); + + block.transactions.push_back( tx[4] ); + BOOST_CHECK( block.calculate_merkle_root() == c(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(t[4], t[5]); + dJ = dC; + dM = d(dI, dJ); + + block.transactions.push_back( tx[5] ); + BOOST_CHECK( block.calculate_merkle_root() == c(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 = t[6]; + dJ = d(dC, dD); + dM = d(dI, dJ); + + block.transactions.push_back( tx[6] ); + BOOST_CHECK( block.calculate_merkle_root() == c(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(t[6], t[7]); + dJ = d(dC, dD); + dM = d(dI, dJ); + + block.transactions.push_back( tx[7] ); + BOOST_CHECK( block.calculate_merkle_root() == c(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 = t[8]; + dK = dE; + dN = dK; + dO = d(dM, dN); + + block.transactions.push_back( tx[8] ); + BOOST_CHECK( block.calculate_merkle_root() == c(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(t[8], t[9]); + dK = dE; + dN = dK; + dO = d(dM, dN); + + block.transactions.push_back( tx[9] ); + BOOST_CHECK( block.calculate_merkle_root() == c(dO) ); +} + BOOST_AUTO_TEST_SUITE_END()