From 4dcb2bcb3892982f19e158cfc2c648270ea8495d Mon Sep 17 00:00:00 2001 From: Bart Wyatt Date: Fri, 1 Jun 2018 12:28:14 -0400 Subject: [PATCH] EOSIO/eos#3725 fix multiple bugs that were only excercised with malicious blocks --- libraries/chain/controller.cpp | 11 ++- libraries/chain/fork_database.cpp | 8 +- .../eosio/chain/incremental_merkle.hpp | 7 +- unittests/forked_tests.cpp | 99 +++++++++++++++++++ 4 files changed, 115 insertions(+), 10 deletions(-) diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 58110734339..956b1382fba 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -871,13 +871,14 @@ struct controller_impl { if (except) { elog("exception thrown while switching forks ${e}", ("e",except->to_detail_string())); - while (ritr != branches.first.rend() ) { - fork_db.set_validity( *ritr, false ); - ++ritr; - } + // ritr currently points to the block that threw + // if we mark it invalid it will automatically remove all forks built off it. + fork_db.set_validity( *ritr, false ); // pop all blocks from the bad fork - for( auto itr = (ritr + 1).base(); itr != branches.second.end(); ++itr ) { + // ritr base is a forward itr to the last block successfully applied + auto applied_itr = ritr.base(); + for( auto itr = applied_itr; itr != branches.first.end(); ++itr ) { fork_db.mark_in_current_chain( *itr , false ); pop_block(); } diff --git a/libraries/chain/fork_database.cpp b/libraries/chain/fork_database.cpp index 560221afde0..f8e99e51a9a 100644 --- a/libraries/chain/fork_database.cpp +++ b/libraries/chain/fork_database.cpp @@ -209,14 +209,14 @@ namespace eosio { namespace chain { my->index.erase(itr); auto& previdx = my->index.get(); - auto previtr = previdx.find(id); - while( previtr != previdx.end() ) { + auto previtr = previdx.lower_bound(remove_queue[i]); + while( previtr != previdx.end() && (*previtr)->header.previous == remove_queue[i] ) { remove_queue.push_back( (*previtr)->id ); - previdx.erase(previtr); - previtr = previdx.find(id); + ++previtr; } } //wdump((my->index.size())); + my->head = *my->index.get().begin(); } void fork_database::set_validity( const block_state_ptr& h, bool valid ) { diff --git a/libraries/chain/include/eosio/chain/incremental_merkle.hpp b/libraries/chain/include/eosio/chain/incremental_merkle.hpp index 15c3fc4392e..0a84d076f58 100644 --- a/libraries/chain/include/eosio/chain/incremental_merkle.hpp +++ b/libraries/chain/include/eosio/chain/incremental_merkle.hpp @@ -98,7 +98,12 @@ class incremental_merkle_impl { :_node_count(0) {} - template + incremental_merkle_impl( const incremental_merkle_impl& ) = default; + incremental_merkle_impl( incremental_merkle_impl&& ) = default; + incremental_merkle_impl& operator= (const incremental_merkle_impl& ) = default; + incremental_merkle_impl& operator= ( incremental_merkle_impl&& ) = default; + + template, incremental_merkle_impl>::value, int> = 0> incremental_merkle_impl( Allocator&& alloc ):_active_nodes(forward(alloc)){} /* diff --git a/unittests/forked_tests.cpp b/unittests/forked_tests.cpp index 6d8ea72c643..be6b267a858 100644 --- a/unittests/forked_tests.cpp +++ b/unittests/forked_tests.cpp @@ -38,6 +38,105 @@ BOOST_AUTO_TEST_CASE( irrblock ) try { } FC_LOG_AND_RETHROW() +struct fork_tracker { + vector blocks; + incremental_merkle block_merkle; +}; + +BOOST_AUTO_TEST_CASE( fork_with_bad_block ) try { + tester bios; + bios.produce_block(); + bios.produce_block(); + bios.create_accounts( {N(a),N(b),N(c),N(d),N(e)} ); + + bios.produce_block(); + auto res = bios.set_producers( {N(a),N(b),N(c),N(d),N(e)} ); + + // run until the producers are installed and its the start of "a's" round + while( bios.control->pending_block_state()->header.producer.to_string() != "a" || bios.control->head_block_state()->header.producer.to_string() != "e") { + bios.produce_block(); + } + + // sync remote node + tester remote; + while( remote.control->head_block_num() < bios.control->head_block_num() ) { + auto fb = bios.control->fetch_block_by_number( remote.control->head_block_num()+1 ); + remote.push_block( fb ); + } + + // produce 6 blocks on bios + for (int i = 0; i < 6; i ++) { + bios.produce_block(); + BOOST_REQUIRE_EQUAL( bios.control->head_block_state()->header.producer.to_string(), "a" ); + } + + vector forks(7); + // enough to skip A's blocks + auto offset = fc::milliseconds(config::block_interval_ms * 13); + + // skip a's blocks on remote + // create 7 forks of 7 blocks so this fork is longer where the ith block is corrupted + for (size_t i = 0; i < 7; i ++) { + auto b = remote.produce_block(offset); + BOOST_REQUIRE_EQUAL( b->producer.to_string(), "b" ); + + for (size_t j = 0; j < 7; j ++) { + auto& fork = forks.at(j); + + if (j <= i) { + auto copy_b = std::make_shared(*b); + if (j == i) { + // corrupt this block + fork.block_merkle = remote.control->head_block_state()->blockroot_merkle; + copy_b->action_mroot._hash[0] ^= 0x1ULL; + } else if (j < i) { + // link to a corrupted chain + copy_b->previous = fork.blocks.back()->id(); + } + + // re-sign the block + auto header_bmroot = digest_type::hash( std::make_pair( copy_b->digest(), fork.block_merkle.get_root() ) ); + auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, remote.control->head_block_state()->pending_schedule_hash) ); + copy_b->producer_signature = remote.get_private_key(N(b), "active").sign(sig_digest); + + // add this new block to our corrupted block merkle + fork.block_merkle.append(copy_b->id()); + fork.blocks.emplace_back(copy_b); + } else { + fork.blocks.emplace_back(b); + } + } + + offset = fc::milliseconds(config::block_interval_ms); + } + + // go from most corrupted fork to least + for (size_t i = 0; i < forks.size(); i++) { + BOOST_TEST_CONTEXT("Testing Fork: " << i) { + const auto& fork = forks.at(i); + // push the fork to the original node + for (int fidx = 0; fidx < fork.blocks.size() - 1; fidx++) { + const auto& b = fork.blocks.at(fidx); + // push the block only if its not known already + if (!bios.control->fetch_block_by_id(b->id())) { + bios.push_block(b); + } + } + + // push the block which should attempt the corrupted fork and fail + BOOST_REQUIRE_THROW(bios.push_block(fork.blocks.back()), fc::exception); + } + } + + // make sure we can still produce a blocks until irreversibility moves + auto lib = bios.control->head_block_state()->dpos_irreversible_blocknum; + size_t tries = 0; + while (bios.control->head_block_state()->dpos_irreversible_blocknum == lib && ++tries < 10000) { + bios.produce_block(); + } + +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_CASE( forking ) try { tester c; c.produce_block();