• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tari-project / tari / 26450381733

26 May 2026 01:18PM UTC coverage: 61.543% (-0.009%) from 61.552%
26450381733

push

github

web-flow
feat(sidechain)!: carry next-epoch hash in EndEpoch command (#7856)

## Description

Adds `EndEpochAtom { next_epoch_hash }` and changes `Command::EndEpoch`
to `Command::EndEpoch(EndEpochAtom)`.

The next epoch's base-layer boundary-block hash is now carried inside
the `EndEpoch` command, so it becomes part of the block's command merkle
root and is attested by the quorum that commits the end-of-epoch block.

## Motivation

Today each validator derives the next epoch's boundary hash from its own
base-layer oracle and locks it unilaterally when it stamps the next
epoch's genesis. When a base-layer reorg deeper than the confirmation
depth straddles an epoch boundary, the committee can split across two
different boundary-block hashes, after which nodes reject each other's
proposals (`InvalidEpochHash`) and consensus wedges with no automatic
recovery.

Carrying the hash in the command lets the committee **ratify the next
epoch's hash by quorum** instead: a validator only votes for the EOE
block if `next_epoch_hash` matches its own (lagged, reorg-stable) view,
so a hash can no longer be locked without agreement. The layer-2
consensus changes that consume this live in the tari-ootle repo.

## Notes

- The command still hashes via `command_hasher().chain(self)` (Borsh),
keeping the layer-2 command hash consistent with the consensus block's
command merkle root. The matching tari-ootle `Command` change preserves
byte-identical hashing so L1 inclusion-proof verification is unaffected.
- `EndEpoch` stays the last enum variant (Borsh index unchanged).

## How Has This Been Tested?

`cargo build` and `cargo test` for `tari_sidechain` pass.

0 of 15 new or added lines in 1 file covered. (0.0%)

7 existing lines in 4 files now uncovered.

72082 of 117125 relevant lines covered (61.54%)

223201.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.5
/base_layer/sidechain/src/command.rs
1
// Copyright 2024 The Tari Project
2
// SPDX-License-Identifier: BSD-3-Clause
3

4
use borsh::{BorshDeserialize, BorshSerialize};
5
use serde::{Deserialize, Serialize};
6
use tari_common_types::types::FixedHash;
7
use tari_hashing::layer2::command_hasher;
8

9
use crate::{eviction_proof::EvictNodeAtom, serde::hex_or_bytes};
10

11
pub trait ToCommand {
12
    fn to_command(&self) -> Command;
13
}
14

15
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
×
16
pub enum Command {
17
    LocalOnly,
18
    LocalPrepare,
19
    LocalAccept,
20
    AllAccept,
21
    SomeAccept,
22
    ForeignProposal,
23
    EvictNode(EvictNodeAtom),
24
    EndEpoch(EndEpochAtom),
25
}
26

27
impl Command {
28
    pub fn evict_node(&self) -> Option<&EvictNodeAtom> {
×
29
        match self {
×
30
            Self::EvictNode(evict_node_atom) => Some(evict_node_atom),
×
31
            _ => None,
×
32
        }
33
    }
×
34

NEW
35
    pub fn end_epoch(&self) -> Option<&EndEpochAtom> {
×
NEW
36
        match self {
×
NEW
37
            Self::EndEpoch(end_epoch_atom) => Some(end_epoch_atom),
×
NEW
38
            _ => None,
×
39
        }
NEW
40
    }
×
41

42
    pub fn hash(&self) -> FixedHash {
2✔
43
        command_hasher().chain(self).finalize().into()
2✔
44
    }
2✔
45
}
46

47
/// The atom committed by an `EndEpoch` command.
48
///
49
/// It carries the base-layer boundary-block hash of the *next* epoch so that the value is ratified
50
/// by the committee: a validator only votes for the end-of-epoch block if `next_epoch_hash` matches
51
/// its own (lagged, reorg-stable) view of the next epoch's boundary block. Because this hash is part
52
/// of the command, it is committed in the block's command merkle root and attested by the quorum
53
/// that commits the block — a node can no longer unilaterally lock an epoch hash the committee never
54
/// agreed on (which is what wedges consensus when a base-layer reorg deeper than the confirmation
55
/// depth straddles the epoch boundary).
NEW
56
#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize, BorshSerialize, BorshDeserialize)]
×
57
pub struct EndEpochAtom {
58
    #[serde(with = "hex_or_bytes")]
59
    next_epoch_hash: FixedHash,
60
}
61

62
impl EndEpochAtom {
NEW
63
    pub fn new(next_epoch_hash: FixedHash) -> Self {
×
NEW
64
        Self { next_epoch_hash }
×
NEW
65
    }
×
66

NEW
67
    pub fn next_epoch_hash(&self) -> FixedHash {
×
NEW
68
        self.next_epoch_hash
×
NEW
69
    }
×
70
}
71

72
impl ToCommand for EndEpochAtom {
NEW
73
    fn to_command(&self) -> Command {
×
NEW
74
        Command::EndEpoch(self.clone())
×
NEW
75
    }
×
76
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc