Skip to content

Commit b31dd12

Browse files
committed
treat blocks below the fork database window as unlinkable rather than invalid
A block older than head - max_size is usually an honest block from a peer that is slightly behind us (brief disconnection, or relaying a soon-to-be- orphaned fork branch). With one-block irreversibility the window is typically only ~2 blocks deep, so this is easy to hit during any network hiccup. Previously this raised a generic assert, which the p2p layer treats as 'peer sent an invalid block': it disconnects the sender plus every other peer advertising the same block. During the 2026-07-09 incident this turned a routine micro-fork into a network-wide disconnect storm (peer counts repeatedly collapsing to 0, participation dropping to ~61%). Throwing unlinkable_block_exception instead routes these blocks through the existing fork-reconciliation path (restart sync with the peer), which is the intended behavior between honest nodes on different forks.
1 parent 2f7b76b commit b31dd12

1 file changed

Lines changed: 11 additions & 3 deletions

File tree

libraries/chain/fork_database.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,17 @@ void fork_database::_push_block(const item_ptr& item)
7474
{
7575
if (_head) // make sure the block is within the range that we are caching
7676
{
77-
FC_ASSERT(item->get_block_num() > std::max<int64_t>(0, int64_t(_head->get_block_num()) - _max_size),
78-
"attempting to push a block that is too old",
79-
("item->num", item->get_block_num())("head", _head->get_block_num())("max_size", _max_size));
77+
// A block below our cache window is most likely an honest block from a peer that is a little
78+
// behind us (e.g. it was briefly disconnected, or is relaying a soon-to-be-orphaned fork branch).
79+
// With one-block irreversibility the window is typically only a couple of blocks deep, so this
80+
// is easy to hit during any network hiccup. Throw unlinkable_block_exception so the p2p layer
81+
// treats it like any other block it can't attach (triggering fork reconciliation via sync)
82+
// instead of treating the peer as if it had sent an invalid block and disconnecting it -- that
83+
// punitive reaction turned a routine micro-fork into a network-wide disconnect storm on 2026-07-09.
84+
HIVE_ASSERT(item->get_block_num() > std::max<int64_t>(0, int64_t(_head->get_block_num()) - _max_size),
85+
unlinkable_block_exception,
86+
"attempting to push a block that is too old",
87+
("item->num", item->get_block_num())("head", _head->get_block_num())("max_size", _max_size));
8088
}
8189
//if we can't link the new item all the way back to genesis block,
8290
// throw an unlinkable block exception

0 commit comments

Comments
 (0)