diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 1f83b3c7..e0cf6b61 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -382,6 +382,10 @@ namespace detail { ilog("Got block #${n} from network", ("n", blk_msg.block.block_num())); try { + // TODO: in the case where this block is valid but on a fork that's too old for us to switch to, + // you can help the network code out by throwing a block_older_than_undo_history exception. + // when the net code sees that, it will stop trying to push blocks from that chain, but + // leave that peer connected so that they can get sync blocks from us bool result = _chain_db->push_block(blk_msg.block, _is_block_producer ? database::skip_nothing : database::skip_transaction_signatures); // the block was accepted, so we now know all of the transactions contained in the block @@ -444,22 +448,37 @@ namespace detail { uint32_t& remaining_item_count, uint32_t limit) override { try { - vector result; + vector result; remaining_item_count = 0; if( _chain_db->head_block_num() == 0 ) return result; result.reserve(limit); block_id_type last_known_block_id; + + if (blockchain_synopsis.empty() || + (blockchain_synopsis.size() == 1 && blockchain_synopsis[0] == block_id_type())) + { + // peer has sent us an empty synopsis meaning they have no blocks. + // A bug in old versions would cause them to send a synopsis containing block 000000000 + // when they had an empty blockchain, so pretend they sent the right thing here. - for (const item_hash_t& block_id_in_synopsis : boost::adaptors::reverse(blockchain_synopsis)) - if (block_id_in_synopsis == block_id_type() || - (_chain_db->is_known_block(block_id_in_synopsis) && is_included_block(block_id_in_synopsis))) - { - last_known_block_id = block_id_in_synopsis; - break; - } - + // do nothing, leave last_known_block_id set to zero + } + else + { + bool found_a_block_in_synopsis = false; + for (const item_hash_t& block_id_in_synopsis : boost::adaptors::reverse(blockchain_synopsis)) + if (block_id_in_synopsis == block_id_type() || + (_chain_db->is_known_block(block_id_in_synopsis) && is_included_block(block_id_in_synopsis))) + { + last_known_block_id = block_id_in_synopsis; + found_a_block_in_synopsis = true; + break; + } + if (!found_a_block_in_synopsis) + FC_THROW_EXCEPTION(graphene::net::peer_is_on_an_unreachable_fork, "Unable to provide a list of blocks starting at any of the blocks in peer's synopsis"); + } for( uint32_t num = block_header::num_from_id(last_known_block_id); num <= _chain_db->head_block_num() && result.size() < limit; ++num ) diff --git a/libraries/net/include/graphene/net/exceptions.hpp b/libraries/net/include/graphene/net/exceptions.hpp index 7985ebc0..7750455d 100644 --- a/libraries/net/include/graphene/net/exceptions.hpp +++ b/libraries/net/include/graphene/net/exceptions.hpp @@ -26,5 +26,6 @@ namespace graphene { namespace net { FC_DECLARE_DERIVED_EXCEPTION( insufficient_relay_fee, graphene::net::net_exception, 90002, "insufficient relay fee" ); FC_DECLARE_DERIVED_EXCEPTION( already_connected_to_requested_peer, graphene::net::net_exception, 90003, "already connected to requested peer" ); FC_DECLARE_DERIVED_EXCEPTION( block_older_than_undo_history, graphene::net::net_exception, 90004, "block is older than our undo history allows us to process" ); + FC_DECLARE_DERIVED_EXCEPTION( peer_is_on_an_unreachable_fork, graphene::net::net_exception, 90005, "peer is on another fork" ); } } diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index 1e82dcd9..c109b434 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -2162,9 +2162,20 @@ namespace graphene { namespace net { namespace detail { } blockchain_item_ids_inventory_message reply_message; - reply_message.item_hashes_available = _delegate->get_block_ids(fetch_blockchain_item_ids_message_received.blockchain_synopsis, - reply_message.total_remaining_item_count ); reply_message.item_type = fetch_blockchain_item_ids_message_received.item_type; + reply_message.total_remaining_item_count = 0; + try + { + reply_message.item_hashes_available = _delegate->get_block_ids(fetch_blockchain_item_ids_message_received.blockchain_synopsis, + reply_message.total_remaining_item_count); + } + catch (const peer_is_on_an_unreachable_fork&) + { + dlog("Peer is on a fork and there's no set of blocks we can provide to switch them to our fork"); + // we reply with an empty list as if we had an empty blockchain; + // we don't want to disconnect because they may be able to provide + // us with blocks on their chain + } bool disconnect_from_inhibited_peer = false; // if our client doesn't have any items after the item the peer requested, it will send back