Bug
ConsensusEngine::add_event() and receive_event_from_network() both call vlc.merge() followed by vlc.tick() when processing a received event. However, VLC::merge() internally calls VLCSnapshot::receive(), which already performs the increment (merge vector clocks → set logical_time = max + 1 → increment local node's vector clock entry).
The subsequent vlc.tick() calls VLCSnapshot::increment() again, resulting in two increments per receive — the local node's vector clock entry advances by 2 and logical_time advances by 2 for every single received event.
Impact
- Vector clock entries grow at 2x the correct rate on receive paths
logical_time inflates faster than it should, causing should_fold() (VLC delta threshold) to trigger prematurely
- Causal ordering comparisons (
happens_before) are still correct (monotonicity is preserved), but the clock values are unnecessarily inflated
Root cause
// consensus/src/engine.rs — add_event() and receive_event_from_network()
{
let mut vlc = self.vlc.write().await;
vlc.merge(&event.vlc_snapshot); // calls receive() which already increments
vlc.tick(); // redundant second increment
}
VLC::merge() → VLCSnapshot::receive():
pub fn receive(&mut self, other: &VLCSnapshot, local_node_id: &str) {
self.vector_clock.merge(&other.vector_clock);
self.logical_time = self.logical_time.max(other.logical_time) + 1; // +1
self.vector_clock.increment(local_node_id); // increment
self.physical_time = Self::current_physical_time();
}
Fix
Remove the redundant vlc.tick() after vlc.merge() in both add_event() and receive_event_from_network(). The create_event() and tick_and_get_vlc() paths are correct (they only call tick() once for local event creation).
Reference
VLC update rules per Chrono: Building a Verifiable Logical Clock for P2P Networks — UPDATE(id, c, [c_received]) performs merge + single increment. The receive path should not double-increment.
Bug
ConsensusEngine::add_event()andreceive_event_from_network()both callvlc.merge()followed byvlc.tick()when processing a received event. However,VLC::merge()internally callsVLCSnapshot::receive(), which already performs the increment (merge vector clocks → set logical_time = max + 1 → increment local node's vector clock entry).The subsequent
vlc.tick()callsVLCSnapshot::increment()again, resulting in two increments per receive — the local node's vector clock entry advances by 2 and logical_time advances by 2 for every single received event.Impact
logical_timeinflates faster than it should, causingshould_fold()(VLC delta threshold) to trigger prematurelyhappens_before) are still correct (monotonicity is preserved), but the clock values are unnecessarily inflatedRoot cause
VLC::merge()→VLCSnapshot::receive():Fix
Remove the redundant
vlc.tick()aftervlc.merge()in bothadd_event()andreceive_event_from_network(). Thecreate_event()andtick_and_get_vlc()paths are correct (they only calltick()once for local event creation).Reference
VLC update rules per Chrono: Building a Verifiable Logical Clock for P2P Networks —
UPDATE(id, c, [c_received])performs merge + single increment. The receive path should not double-increment.