Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5b0a6aa
feat(slasher): add slasher crates
Rexagon Jul 17, 2025
b07959d
feat(collator): propagate original vset item index
Rexagon Nov 25, 2025
9e52d79
feat(core): add boxed block/state subscribers
Rexagon Nov 28, 2025
7817932
feat(slasher): send block batches to the slasher contract
Rexagon Dec 24, 2025
fa5157d
feat(slasher): add stub contract
Rexagon Dec 25, 2025
1bdc74f
feat(slasher): persistent storage
Rexagon Jan 15, 2026
0564982
feat(slasher): check blocks batch seqno range
Rexagon Feb 2, 2026
0ce93e0
chore(core): change ValidationSessionId signature
MrWad3r Feb 24, 2026
fd6473f
chore(contracts): propagate new validation session id into contract
MrWad3r Mar 10, 2026
bacd043
feat(slasher): add slasher analyzer to create signatures reports
MrWad3r Mar 11, 2026
2d6d18f
chore(contract): bump dependencies
MrWad3r Mar 20, 2026
11d7d5b
chore(slasher): update cc_seqno derivation from `KbNextSessionUpdate`
MrWad3r Mar 17, 2026
2527f61
chore(slasher): add slasher generation to zerostate
MrWad3r Mar 17, 2026
1d2f432
chore(slasher): add logs with `slasher` target
MrWad3r Mar 17, 2026
afae077
chore(slasher): review pt.1
MrWad3r Mar 26, 2026
6d20b08
chore(slasher): fix signature condition
MrWad3r Mar 27, 2026
1150fe3
fix(collator): shuffle with vset_switch_round
Mododo Apr 8, 2026
f42003d
feat(slasher): only update slasher state on config change
Rexagon May 13, 2026
a59f198
wip: simplify slasher storage
Rexagon May 15, 2026
7df9b15
feat(slasher): compute accusations in `complete_vset`
Rexagon May 22, 2026
3fe51e5
feat(slasher): check vset hash in slasher config
Rexagon May 28, 2026
b8353a6
fix(slasher): handle strange analyzer cases
Rexagon Jun 1, 2026
7257b50
feat(slasher): add slasher metrics
Rexagon Jun 3, 2026
4337f8a
fix(slasher): use proper cf for slasher state
Rexagon Jun 4, 2026
971cb79
feat(contracts): rework slasher replay protection
Rexagon Jun 5, 2026
f1b0b1f
fix(slasher): review fixes
Rexagon Jun 9, 2026
6fb79fd
feat(collator): reuse `CreateStats` feature for vset migration
Rexagon Jun 22, 2026
07e7bb5
fix(collator): catchain seqno compatibility
Mododo Jun 22, 2026
e08a631
fix(block-util): use capabilities from the checked block to get mode
Rexagon Jun 24, 2026
288f06a
fix(slasher): apply updated batch size to all running sessions
Rexagon Jun 24, 2026
70e67a7
chore(scripts): cleanup unused metrics
Rexagon Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ members = [
"gen-protos",
"network",
"rpc",
"rpc-subscriptions",
"simulator",
"slasher",
"slasher-traits",
"storage",
"storage-traits",
"tycho-build-info",
"util",
"util-proc",
"wu-tuner",
"rpc-subscriptions",
]

[workspace.dependencies]
Expand Down Expand Up @@ -170,6 +172,8 @@ tycho-core = { path = "./core", version = "0.3.10" }
tycho-network = { path = "./network", version = "0.3.10" }
tycho-rpc-subscriptions = { path = "./rpc-subscriptions", version = "0.3.10" }
tycho-rpc = { path = "./rpc", version = "0.3.10" }
tycho-slasher = { path = "./slasher", version = "0.3.10" }
tycho-slasher-traits = { path = "./slasher-traits", version = "0.3.10" }
tycho-storage = { path = "./storage", version = "0.3.10" }
tycho-storage-traits = { path = "./storage-traits", version = "0.3.10" }
tycho-util = { path = "./util", version = "0.3.10" }
Expand Down
67 changes: 51 additions & 16 deletions block-util/src/block/block_proof_stuff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ impl BlockProofStuff {

let weight = match signatures
.signatures
.check_signatures(&subset.validators, &checked_data)
.check_signatures(subset.validators.iter().map(AsRef::as_ref), &checked_data)
{
Ok(weight) => weight,
Err(e) => anyhow::bail!("proof contains invalid signatures: {e:?}"),
Expand Down Expand Up @@ -348,12 +348,14 @@ impl BlockProofStuff {
(validator_set, shuffle_validators)
};

self.calc_validators_subset_standard(&validator_set, shuffle_validators)
let mode = CatchainSeqnoMode::from_capabilities(block_info.gen_software.capabilities);
self.calc_validators_subset_standard(&validator_set, mode, shuffle_validators)
}

fn process_prev_key_block_proof(
&self,
prev_key_block_proof: &BlockProofStuff,
checked_block_info: &BlockInfo,
) -> Result<ValidatorSubsetInfo> {
let (virt_key_block, prev_key_block_info) = prev_key_block_proof.pre_check_block_proof()?;

Expand All @@ -380,23 +382,36 @@ impl BlockProofStuff {
(validator_set, shuffle_validators)
};

self.calc_validators_subset_standard(&validator_set, shuffle_validators)
// NOTE: `prev_key_block_info` is unused here. We only need the previous
// key block to get the current validator set. All other stuff, like
// vset switch round or gen capabilities, must be used from the current block.
let caps = checked_block_info.gen_software.capabilities;
let mode = CatchainSeqnoMode::from_capabilities(caps);
self.calc_validators_subset_standard(&validator_set, mode, shuffle_validators)
}

/// NOTE: Despite the `mode` the only nonce used for subsets shuffling is
/// `vset_switch_round`. Subset mode only controls from where we extract it.
fn calc_validators_subset_standard(
&self,
validator_set: &ValidatorSet,
mode: CatchainSeqnoMode,
shuffle_validators: bool,
) -> Result<ValidatorSubsetInfo> {
let cc_seqno = self
.inner
.proof
.signatures
.as_ref()
.map(|s| s.validator_info.catchain_seqno)
.unwrap_or_default();

ValidatorSubsetInfo::compute_standard(validator_set, cc_seqno, shuffle_validators)
let Some(switch_round) = self.inner.proof.signatures.as_ref().map(|s| match mode {
// Originally (before https://github.com/broxus/tycho/pull/1098) `catchain_seqno`
// was initialized with `vset_switch_round`. However, we were reading this round
// "indirectly" from `validator_info` and didn't notice that `consensus_info` was
// set from the **next** state. So for old blocks/proofs we must continue to read
// switch round this way.
CatchainSeqnoMode::Original => s.validator_info.catchain_seqno,
// This mode can only be enabled on a new version of the node, so `consensus_info`
// is the same which was used to produce this block.
CatchainSeqnoMode::Sequential => s.consensus_info.vset_switch_round,
}) else {
anyhow::bail!("no signatures info to compute subset from");
};
ValidatorSubsetInfo::compute_standard(validator_set, switch_round, shuffle_validators)
}
}

Expand Down Expand Up @@ -463,7 +478,7 @@ pub fn check_with_prev_key_block_proof(
proof_id.seqno,
);

let subset = proof.process_prev_key_block_proof(prev_key_block_proof)?;
let subset = proof.process_prev_key_block_proof(prev_key_block_proof, virt_block_info)?;

if virt_block_info.key_block {
pre_check_key_block_proof(virt_block)?;
Expand Down Expand Up @@ -520,18 +535,18 @@ fn pre_check_key_block_proof(virt_block: &Block) -> Result<()> {

#[derive(Clone, Debug)]
pub struct ValidatorSubsetInfo {
pub validators: Vec<ValidatorDescription>,
pub validators: Vec<IndexedValidatorDescription>,
pub short_hash: u32,
}

impl ValidatorSubsetInfo {
pub fn compute_standard(
validator_set: &ValidatorSet,
cc_seqno: u32,
vset_switch_round: u32,
shuffle_validators: bool,
) -> Result<Self> {
let Some((validators, short_hash)) =
validator_set.compute_mc_subset(cc_seqno, shuffle_validators)
validator_set.compute_mc_subset_indexed(vset_switch_round, shuffle_validators)
else {
anyhow::bail!("failed to compute a validator subset");
};
Expand All @@ -543,6 +558,26 @@ impl ValidatorSubsetInfo {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CatchainSeqnoMode {
Original,
Sequential,
}

impl CatchainSeqnoMode {
pub fn from_capabilities(caps: GlobalCapabilities) -> Self {
// NOTE: We are using this capability because it should be disabled for
// Tycho-based networks, but for some reason it is enabled everywhere.
// By relying on this capability we can do two things at once during
// migration.
if caps.contains(GlobalCapability::CapCreateStatsEnabled) {
Self::Original
} else {
Self::Sequential
}
}
}

// TODO: Move into `types`.
pub struct AlwaysInclude;

Expand Down
2 changes: 1 addition & 1 deletion block-util/src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use tycho_types::models::ShardIdent;

pub use self::block_id_ext::{BlockIdExt, BlockIdRelation, calc_next_block_id_short};
pub use self::block_proof_stuff::{
AlwaysInclude, BlockProofStuff, BlockProofStuffAug, ValidatorSubsetInfo,
AlwaysInclude, BlockProofStuff, BlockProofStuffAug, CatchainSeqnoMode, ValidatorSubsetInfo,
check_with_master_state, check_with_prev_key_block_proof,
};
pub use self::block_stuff::{BlockStuff, BlockStuffAug};
Expand Down
4 changes: 3 additions & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,16 @@ weedb = { workspace = true }
# local deps
tycho-block-util = { workspace = true }
tycho-collator = { workspace = true }
tycho-consensus = { workspace = true }
tycho-control = { workspace = true, features = ["full"] }
tycho-core = { workspace = true, features = ["cli"] }
tycho-network = { workspace = true }
tycho-rpc = { workspace = true, features = ["http2"] }
tycho-slasher = { workspace = true }
tycho-slasher-traits = { workspace = true }
tycho-storage = { workspace = true }
tycho-util = { workspace = true, features = ["cli"] }
tycho-wu-tuner = { workspace = true }
tycho-consensus = { workspace = true }

[dev-dependencies]
tycho-collator = { workspace = true, features = ["test"] }
Expand Down
Binary file added cli/res/slasher_code.boc
Binary file not shown.
5 changes: 2 additions & 3 deletions cli/src/cmd/tools/gen_zerostate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,9 +500,9 @@ impl Default for ZerostateConfig {
global_id: 0,
config_public_key: *zero_public_key(),
minter_public_key: None,
config_balance: Tokens::new(500_000_000_000), // 500
config_balance: Tokens::new(500_000_000_000),
config_code: None,
elector_balance: Tokens::new(500_000_000_000), // 500
elector_balance: Tokens::new(500_000_000_000),
elector_code: None,
accounts: Default::default(),
validators: Default::default(),
Expand Down Expand Up @@ -808,7 +808,6 @@ fn make_default_params() -> Result<BlockchainConfigParams> {

// Param 31
params.set_fundamental_addresses(&[HashBytes([0x00; 32]), HashBytes([0x33; 32])])?;

// Param 43
params.set_size_limits(&SizeLimitsConfig {
max_msg_bits: 1 << 21,
Expand Down
5 changes: 5 additions & 0 deletions cli/src/node/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use tycho_control::ControlServerConfig;
use tycho_core::node::NodeBaseConfig;
use tycho_crypto::ed25519;
use tycho_rpc::RpcConfig;
use tycho_slasher::SlasherConfig;
use tycho_types::cell::HashBytes;
use tycho_types::models::StdAddr;
use tycho_util::cli::config::ThreadPoolConfig;
Expand Down Expand Up @@ -165,6 +166,9 @@ pub struct NodeConfig {

pub validator: ValidatorStdImplConfig,

#[partial]
pub slasher: SlasherConfig,

#[partial]
pub rpc: Option<RpcConfig>,

Expand All @@ -191,6 +195,7 @@ impl Default for NodeConfig {
mempool: Default::default(),
internal_queue: Default::default(),
validator: Default::default(),
slasher: Default::default(),
rpc: Some(Default::default()),
control: Default::default(),
metrics: Some(Default::default()),
Expand Down
30 changes: 24 additions & 6 deletions cli/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use tycho_core::node::{NodeBase, NodeKeys};
use tycho_core::storage::NodeSyncState;
use tycho_network::InboundRequestMeta;
use tycho_rpc::{NodeBaseInitRpc, RpcConfig};
use tycho_slasher::SlasherConfig;
use tycho_types::models::*;
use tycho_util::futures::JoinTask;
use tycho_wu_tuner::service::WuTunerServiceBuilder;
Expand All @@ -57,6 +58,7 @@ pub struct Node {
collator_config: CollatorConfig,
validator_config: ValidatorStdImplConfig,
internal_queue_config: QueueConfig,
slasher_config: SlasherConfig,
mempool_config_override: Option<MempoolGlobalConfig>,

/// Path to the work units tuner config.
Expand Down Expand Up @@ -131,6 +133,7 @@ impl Node {
collator_config: node_config.collator,
validator_config: node_config.validator,
internal_queue_config: node_config.internal_queue,
slasher_config: node_config.slasher,
mempool_config_override: global_config.mempool,
wu_tuner_config_path,
})
Expand Down Expand Up @@ -176,14 +179,13 @@ impl Node {
.context("failed to load mc zerostate on run")?;

{
let config = mc_state.config_params()?;
let current_validator_set = config.get_current_validator_set()?;
let current_validator_set = mc_state.config_params()?.get_current_validator_set()?;
base.validator_resolver()
.update_validator_set(&current_validator_set);
let v_set_len = current_validator_set.list.len();
let vset_len = current_validator_set.list.len();
anyhow::ensure!(
is_single_node == (v_set_len == 1),
"cannot start with v_set_len={v_set_len} and single_node={is_single_node}"
is_single_node == (vset_len == 1),
"cannot start with vset_len={vset_len} and single_node={is_single_node}"
);
}

Expand Down Expand Up @@ -218,6 +220,16 @@ impl Node {
let message_queue_adapter = MessageQueueAdapterStdImpl::new(queue);
message_queue_adapter.recover_after_restart(&mc_state)?;

let slasher = tycho_slasher::Slasher::new(
base.keypair.clone(),
tycho_slasher::StdSlasherContract,
base.blockchain_rpc_client.clone(),
&base.storage_context,
self.slasher_config,
&mc_state,
)
.context("failed to create slasher")?;

let validator = ValidatorStdImpl::new(
ValidatorNetworkContext {
network: base.network.clone(),
Expand All @@ -227,6 +239,7 @@ impl Node {
},
base.keypair.clone(),
self.validator_config,
slasher.validator_events_listener(),
);

// Explicitly handle the initial state
Expand Down Expand Up @@ -345,7 +358,12 @@ impl Node {
(
ShardStateApplier::new(
base.core_storage.clone(),
(collator_subcriber, rpc_state_subscriber, control_server),
(
collator_subcriber,
rpc_state_subscriber,
control_server,
slasher,
),
),
rpc_block_subscriber,
base.validator_resolver().clone(),
Expand Down
2 changes: 1 addition & 1 deletion collator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ tycho-consensus = { workspace = true }
tycho-core = { workspace = true }
tycho-executor = { workspace = true }
tycho-network = { workspace = true }
tycho-slasher-traits = { workspace = true }
tycho-storage = { workspace = true }
tycho-util = { workspace = true }
tycho-vm = { workspace = true }
Expand All @@ -89,7 +90,6 @@ tycho-util = { workspace = true, features = ["test"] }
[features]
default = []
test = ["tycho-block-util/test", "tycho-storage/test", "tycho-core/test", "tycho-consensus/test"]
block-creator-stats = []
bench-helpers = []

[lints]
Expand Down
Loading
Loading