diff --git a/lean_client/Cargo.lock b/lean_client/Cargo.lock index c0de67c1..4b48388c 100644 --- a/lean_client/Cargo.lock +++ b/lean_client/Cargo.lock @@ -149,7 +149,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -160,7 +160,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -602,7 +602,7 @@ dependencies = [ [[package]] name = "backend" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "mt-air", "mt-fiat-shamir", @@ -1724,7 +1724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2997,7 +2997,7 @@ dependencies = [ [[package]] name = "lean_compiler" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "backend", "lean_vm", @@ -3012,7 +3012,7 @@ dependencies = [ [[package]] name = "lean_prover" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "backend", "itertools 0.14.0", @@ -3029,7 +3029,7 @@ dependencies = [ [[package]] name = "lean_vm" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "backend", "itertools 0.14.0", @@ -3045,7 +3045,7 @@ dependencies = [ [[package]] name = "leansig" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanSig?branch=devnet4#5cc7e37480362f94e86695428a9ceb9a96b66b97" +source = "git+https://github.com/leanEthereum/leanSig#c08a3bae74b0d85379cab72dcbefa4091546ecbb" dependencies = [ "dashmap", "ethereum_ssz", @@ -3085,7 +3085,7 @@ dependencies = [ [[package]] name = "leansig_wrapper" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "backend", "ethereum_ssz", @@ -3701,7 +3701,7 @@ checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" [[package]] name = "mt-air" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "mt-field", "mt-poly", @@ -3710,7 +3710,7 @@ dependencies = [ [[package]] name = "mt-fiat-shamir" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "mt-field", "mt-koala-bear", @@ -3723,7 +3723,7 @@ dependencies = [ [[package]] name = "mt-field" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "itertools 0.14.0", "mt-utils", @@ -3738,7 +3738,7 @@ dependencies = [ [[package]] name = "mt-koala-bear" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "itertools 0.14.0", "mt-field", @@ -3754,7 +3754,7 @@ dependencies = [ [[package]] name = "mt-poly" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "itertools 0.14.0", "mt-field", @@ -3767,7 +3767,7 @@ dependencies = [ [[package]] name = "mt-sumcheck" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "mt-air", "mt-fiat-shamir", @@ -3780,7 +3780,7 @@ dependencies = [ [[package]] name = "mt-symetric" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "mt-field", "mt-koala-bear", @@ -3790,7 +3790,7 @@ dependencies = [ [[package]] name = "mt-utils" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "serde", ] @@ -3798,7 +3798,7 @@ dependencies = [ [[package]] name = "mt-whir" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "itertools 0.14.0", "mt-fiat-shamir", @@ -4048,7 +4048,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4131,7 +4131,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "p3-baby-bear" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "p3-challenger", "p3-field", @@ -4146,7 +4146,7 @@ dependencies = [ [[package]] name = "p3-challenger" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "p3-field", "p3-maybe-rayon", @@ -4159,21 +4159,21 @@ dependencies = [ [[package]] name = "p3-dft" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "itertools 0.14.0", "p3-field", "p3-matrix", "p3-maybe-rayon", "p3-util", - "spin 0.10.0", + "spin 0.11.0", "tracing", ] [[package]] name = "p3-field" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "itertools 0.14.0", "num-bigint", @@ -4188,7 +4188,7 @@ dependencies = [ [[package]] name = "p3-koala-bear" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "p3-challenger", "p3-field", @@ -4203,7 +4203,7 @@ dependencies = [ [[package]] name = "p3-matrix" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "itertools 0.14.0", "p3-field", @@ -4217,12 +4217,12 @@ dependencies = [ [[package]] name = "p3-maybe-rayon" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" [[package]] name = "p3-mds" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "p3-dft", "p3-field", @@ -4234,7 +4234,7 @@ dependencies = [ [[package]] name = "p3-monty-31" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "itertools 0.14.0", "num-bigint", @@ -4250,16 +4250,17 @@ dependencies = [ "paste", "rand 0.10.1", "serde", - "spin 0.10.0", + "spin 0.11.0", "tracing", ] [[package]] name = "p3-poseidon1" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "p3-field", + "p3-mds", "p3-symmetric", "rand 0.10.1", ] @@ -4267,7 +4268,7 @@ dependencies = [ [[package]] name = "p3-poseidon2" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "p3-field", "p3-mds", @@ -4279,7 +4280,7 @@ dependencies = [ [[package]] name = "p3-symmetric" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "itertools 0.14.0", "p3-field", @@ -4290,7 +4291,7 @@ dependencies = [ [[package]] name = "p3-util" version = "0.5.1" -source = "git+https://github.com/Plonky3/Plonky3.git#c7bacaeb4c870e3d6f9b7c23064c05e555c80bc8" +source = "git+https://github.com/Plonky3/Plonky3.git#2aeaa17557e7f54b0caa0add42e7d5b9aec4f564" dependencies = [ "serde", "transpose", @@ -5054,7 +5055,7 @@ dependencies = [ [[package]] name = "rec_aggregation" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "backend", "lean_compiler", @@ -5362,7 +5363,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5793,7 +5794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -5819,9 +5820,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" +checksum = "783f3f6f6b01e295a669edfc402133a5f2553d1f0e81284b3ba4594e80bdd4a2" dependencies = [ "lock_api", ] @@ -5962,7 +5963,7 @@ dependencies = [ [[package]] name = "sub_protocols" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "backend", "lean_vm", @@ -6084,7 +6085,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -6652,7 +6653,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "utils" version = "0.1.0" -source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=2eb4b9d983171139af36749f127dd9890c9109e6#2eb4b9d983171139af36749f127dd9890c9109e6" +source = "git+https://github.com/leanEthereum/leanMultisig.git?rev=5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa#5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" dependencies = [ "backend", "tracing", diff --git a/lean_client/Cargo.toml b/lean_client/Cargo.toml index 5f05c80e..eb0d3e68 100644 --- a/lean_client/Cargo.toml +++ b/lean_client/Cargo.toml @@ -251,9 +251,9 @@ indexmap = "2" http-body-util = "0.1" http_api_utils = { git = "https://github.com/grandinetech/grandine", rev = "64afdee3c6be79fceffb66933dcb69a943f3f1ae" } k256 = "0.13" -rec_aggregation = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "2eb4b9d983171139af36749f127dd9890c9109e6" } -leansig = { git = "https://github.com/leanEthereum/leanSig", branch = "devnet4" } -leansig_wrapper = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "2eb4b9d983171139af36749f127dd9890c9109e6" } +rec_aggregation = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" } +leansig = { git = "https://github.com/leanEthereum/leanSig" } +leansig_wrapper = { git = "https://github.com/leanEthereum/leanMultisig.git", rev = "5eba3b141455349d7cdbf0f5d3ccfb2e640b02aa" } libp2p = { version = "0.56.0", default-features = false, features = [ 'dns', 'gossipsub', diff --git a/lean_client/containers/src/state.rs b/lean_client/containers/src/state.rs index b345fd78..ea7d79b2 100644 --- a/lean_client/containers/src/state.rs +++ b/lean_client/containers/src/state.rs @@ -108,6 +108,23 @@ impl JustifiedSlots { } } +fn attestation_data_matches_chain( + attestation_data: &AttestationData, + historical_block_hashes: &[H256], +) -> bool { + if attestation_data.source.root.is_zero() || attestation_data.target.root.is_zero() { + return false; + } + let source_slot = attestation_data.source.slot.0 as usize; + let target_slot = attestation_data.target.slot.0 as usize; + if source_slot >= historical_block_hashes.len() || target_slot >= historical_block_hashes.len() + { + return false; + } + historical_block_hashes[source_slot] == attestation_data.source.root + && historical_block_hashes[target_slot] == attestation_data.target.root +} + #[derive(Clone, Debug, Ssz, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct State { @@ -653,11 +670,10 @@ impl State { Vec, )> { let mut selected: Vec<(AggregatedAttestation, AggregatedSignatureProof)> = Vec::new(); + let mut child_payloads_consumed: u64 = 0; + let mut processed_data_roots_count: usize = 0; if !aggregated_payloads.is_empty() { - // Genesis edge case: process_block_header rebinds latest_justified.root - // to parent_root when building on slot 0. Apply the same derivation - // here so attestation sources match. let mut current_justified = if self.latest_block_header.slot == Slot(0) { Checkpoint { root: parent_root, @@ -667,7 +683,23 @@ impl State { self.latest_justified.clone() }; - // Sort by target.slot for deterministic processing order. + let mut current_finalized_slot = self.latest_finalized.slot; + let mut current_justified_slots = self + .justified_slots + .clone() + .extend_to_slot(current_finalized_slot, Slot(slot.0.saturating_sub(1))); + + let parent_slot = self.latest_block_header.slot.0 as usize; + let num_empty_slots = (slot.0 as usize).saturating_sub(parent_slot + 1); + let mut extended_historical_block_hashes: Vec = (&self.historical_block_hashes) + .into_iter() + .copied() + .collect(); + extended_historical_block_hashes.push(parent_root); + for _ in 0..num_empty_slots { + extended_historical_block_hashes.push(H256::zero()); + } + let mut sorted_entries: Vec<( &H256, &(AttestationData, Vec), @@ -677,6 +709,11 @@ impl State { let mut processed_data_roots: HashSet = HashSet::new(); loop { + let select_timer = METRICS.get().map(|m| { + m.lean_block_proposal_attestation_build_phase_seconds + .with_label_values(&["select_payloads"]) + .start_timer() + }); let mut found_new = false; for &(data_root, (att_data, proofs)) in &sorted_entries { @@ -689,7 +726,25 @@ impl State { if !known_block_roots.contains(&att_data.head.root) { continue; } - if att_data.source != current_justified { + + if !attestation_data_matches_chain(att_data, &extended_historical_block_hashes) + { + continue; + } + + if !current_justified_slots + .is_slot_justified(current_finalized_slot, att_data.source.slot)? + { + continue; + } + + let is_genesis_self_vote = + att_data.source.slot == Slot(0) && att_data.target.slot == Slot(0); + + if !is_genesis_self_vote + && current_justified_slots + .is_slot_justified(current_finalized_slot, att_data.target.slot)? + { continue; } @@ -697,6 +752,7 @@ impl State { found_new = true; let indices = AggregatedSignatureProof::select_greedily(proofs); + child_payloads_consumed += indices.len() as u64; for idx in indices { let proof = proofs[idx].clone(); selected.push(( @@ -709,11 +765,18 @@ impl State { } } + drop(select_timer); + if !found_new { break; } - // Run STF on a candidate block to see if justification advanced. + let stf_timer = METRICS.get().map(|m| { + m.lean_block_proposal_attestation_build_phase_seconds + .with_label_values(&["stf_simulate"]) + .start_timer() + }); + let candidate_attestations = AggregatedAttestations::try_from_iter( selected.iter().map(|(att, _)| att.clone()), )?; @@ -728,17 +791,29 @@ impl State { }; let post_state = self.process_slots(slot)?.process_block(&candidate_block)?; - if post_state.latest_justified != current_justified { + drop(stf_timer); + + if post_state.latest_justified != current_justified + || post_state.latest_finalized.slot != current_finalized_slot + { current_justified = post_state.latest_justified; + current_justified_slots = post_state.justified_slots.clone(); + current_finalized_slot = post_state.latest_finalized.slot; } else { break; } } + + processed_data_roots_count = processed_data_roots.len(); } - // Compact: merge proofs sharing the same AttestationData via recursive - // aggregation so each AttestationData appears at most once in the body. + let compact_timer = METRICS.get().map(|m| { + m.lean_block_proposal_attestation_build_phase_seconds + .with_label_values(&["compact"]) + .start_timer() + }); let compacted = self.compact_proofs_by_data(selected, log_inv_rate)?; + drop(compact_timer); METRICS.get().map(|metrics| { metrics @@ -754,6 +829,16 @@ impl State { let (aggregated_attestations, aggregated_signatures): (Vec<_>, Vec<_>) = compacted.into_iter().unzip(); + METRICS.get().map(|m| { + m.lean_block_proposal_attestation_builds_total.inc(); + m.lean_block_proposal_child_payloads_consumed_total + .inc_by(child_payloads_consumed); + m.lean_block_proposal_attestation_data_selected + .observe(processed_data_roots_count as f64); + m.lean_block_proposal_aggregates_selected + .observe(aggregated_signatures.len() as f64); + }); + let mut final_block = Block { slot, proposer_index, diff --git a/lean_client/fork_choice/src/store.rs b/lean_client/fork_choice/src/store.rs index 20c60f5e..17006560 100644 --- a/lean_client/fork_choice/src/store.rs +++ b/lean_client/fork_choice/src/store.rs @@ -695,12 +695,16 @@ pub fn execute_block_production( "proposer block built" ); - ensure!( - final_post_state.latest_justified.slot >= store_latest_justified.slot, - "Produced block justified={} < store justified={}. Fixed-point attestation loop did not converge.", - final_post_state.latest_justified.slot.0, - store_latest_justified.slot.0, - ); + if final_post_state.latest_justified.slot < store_latest_justified.slot { + warn!( + produced = final_post_state.latest_justified.slot.0, + store = store_latest_justified.slot.0, + "Produced block justified < store justified. Fixed-point did not converge." + ); + METRICS + .get() + .map(|m| m.lean_build_block_fixed_point_no_converge_total.inc()); + } let block_root = final_block.hash_tree_root(); diff --git a/lean_client/fork_choice/tests/unit_tests/common.rs b/lean_client/fork_choice/tests/unit_tests/common.rs index bdc7b53a..11b6a92d 100644 --- a/lean_client/fork_choice/tests/unit_tests/common.rs +++ b/lean_client/fork_choice/tests/unit_tests/common.rs @@ -22,5 +22,5 @@ pub fn create_test_store() -> Store { signature: Default::default(), }; - get_forkchoice_store(state, signed_block, config) + get_forkchoice_store(state, signed_block, config, true) } diff --git a/lean_client/fork_choice/tests/unit_tests/validator.rs b/lean_client/fork_choice/tests/unit_tests/validator.rs index dbee9fa0..dde00b71 100644 --- a/lean_client/fork_choice/tests/unit_tests/validator.rs +++ b/lean_client/fork_choice/tests/unit_tests/validator.rs @@ -7,12 +7,15 @@ use std::collections::HashMap; use crate::unit_tests::common::create_test_store; use containers::{ AggregatedSignatureProof, AggregationBits, Attestation, AttestationData, Block, BlockBody, - Checkpoint, Config, SignedBlock, Slot, State, Validator, + BlockSignatures, Checkpoint, Config, SignedBlock, Slot, State, Validator, }; +use fork_choice::block_cache::BlockCache; +use fork_choice::handlers::on_block; use fork_choice::store::{Store, get_forkchoice_store, produce_block_with_signatures, update_head}; use rand::SeedableRng; use rand_chacha::ChaChaRng; use ssz::{H256, SszHash}; +use std::collections::HashSet; use xmss::SecretKey; /// Build an `AggregatedSignatureProof` for the given validator set on the @@ -109,7 +112,10 @@ fn create_test_store_with_signers() -> (Store, HashMap) { signature: Default::default(), }; - (get_forkchoice_store(state, signed_block, config), keys) + ( + get_forkchoice_store(state, signed_block, config, true), + keys, + ) } // --------------------------------------------------------------------------- // TestBlockProduction @@ -489,7 +495,7 @@ fn test_validator_operations_empty_store() { signature: Default::default(), }; - let mut store = get_forkchoice_store(state, signed_block, config); + let mut store = get_forkchoice_store(state, signed_block, config, true); // Should be able to produce block and attestation let (_root, block, _sig) = @@ -606,3 +612,175 @@ fn test_produce_attestation_data_uses_head_state_justified() { assert_eq!(attestation_data.source, expected_source); assert_ne!(attestation_data.source, store.latest_justified); } + +fn produce_and_apply( + store: &mut Store, + cache: &mut BlockCache, + slot: Slot, + keys: &HashMap, +) -> H256 { + let num_validators = store.states[&store.head].validators.len_u64(); + let proposer = slot.0 % num_validators; + let _ = keys; + let (_block_root, block, _sigs) = + produce_block_with_signatures(store, slot, proposer, 1).expect("block production failed"); + let signed = SignedBlock { + block, + signature: BlockSignatures::default(), + }; + let block_root = signed.block.hash_tree_root(); + on_block(store, cache, signed, false).expect("on_block failed"); + block_root +} + +#[test] +fn test_produce_block_closes_justification_gap() { + let (mut store, keys) = create_test_store_with_signers(); + let mut cache = BlockCache::new(); + let num_validators = store.states[&store.head].validators.len_u64(); + let genesis_root = store.head; + let genesis_ckpt = Checkpoint { + root: genesis_root, + slot: Slot(0), + }; + + let block_1_root = produce_and_apply(&mut store, &mut cache, Slot(1), &keys); + let block_2_root = produce_and_apply(&mut store, &mut cache, Slot(2), &keys); + let block_3_root = produce_and_apply(&mut store, &mut cache, Slot(3), &keys); + + let block_1_ckpt = Checkpoint { + root: block_1_root, + slot: Slot(1), + }; + let block_2_ckpt = Checkpoint { + root: block_2_root, + slot: Slot(2), + }; + let block_3_ckpt = Checkpoint { + root: block_3_root, + slot: Slot(3), + }; + + let att_target_block_1 = AttestationData { + slot: Slot(4), + head: block_3_ckpt.clone(), + target: block_1_ckpt.clone(), + source: genesis_ckpt.clone(), + }; + publish_aggregated_payload( + &mut store, + &att_target_block_1, + &[0, 1, 2, 3, 4, 5, 6], + &keys, + ); + + let block_4_root = produce_and_apply(&mut store, &mut cache, Slot(4), &keys); + let block_4_ckpt = Checkpoint { + root: block_4_root, + slot: Slot(4), + }; + assert_eq!(store.latest_justified, block_1_ckpt); + + let att_target_block_4 = AttestationData { + slot: Slot(5), + head: block_4_ckpt.clone(), + target: block_4_ckpt.clone(), + source: block_1_ckpt.clone(), + }; + publish_aggregated_payload(&mut store, &att_target_block_4, &[7, 8], &keys); + + let block_5_root = produce_and_apply(&mut store, &mut cache, Slot(5), &keys); + assert_eq!(store.latest_justified, block_1_ckpt); + assert_eq!(store.head, block_5_root); + + let att_target_block_2 = AttestationData { + slot: Slot(6), + head: block_3_ckpt.clone(), + target: block_2_ckpt.clone(), + source: genesis_ckpt.clone(), + }; + publish_aggregated_payload( + &mut store, + &att_target_block_2, + &[0, 1, 2, 3, 4, 5, 6], + &keys, + ); + + let block_3_state = store + .states + .get(&block_3_root) + .expect("block_3 state missing") + .clone(); + let known_block_roots: HashSet = store.blocks.keys().copied().collect(); + let aggregated_payloads: HashMap)> = + store + .latest_known_aggregated_payloads + .iter() + .filter_map(|(root, proofs)| { + store + .attestation_data_by_root + .get(root) + .map(|data| (*root, (data.clone(), proofs.clone()))) + }) + .collect(); + let proposer_6 = Slot(6).0 % num_validators; + let (block_6, _post_state_6, _atts_6, _sigs_6) = block_3_state + .build_block( + Slot(6), + proposer_6, + block_3_root, + &known_block_roots, + &aggregated_payloads, + 1, + ) + .expect("build_block for sibling block_6 failed"); + let signed_block_6 = SignedBlock { + block: block_6, + signature: BlockSignatures::default(), + }; + let block_6_root = signed_block_6.block.hash_tree_root(); + on_block(&mut store, &mut cache, signed_block_6, false).expect("on_block for block_6 failed"); + + assert_eq!(store.latest_justified, block_2_ckpt); + assert_eq!(store.head, block_5_root); + + let gap_closers: Vec<&AttestationData> = store + .latest_known_aggregated_payloads + .keys() + .filter_map(|root| store.attestation_data_by_root.get(root)) + .filter(|d| d.target == block_2_ckpt) + .collect(); + assert_eq!(gap_closers.len(), 1); + assert_eq!(gap_closers[0].source, genesis_ckpt); + assert_eq!(gap_closers[0].slot, Slot(6)); + + let proposer_7 = Slot(7).0 % num_validators; + let (_block_7_root, block_7, _sigs_7) = + produce_block_with_signatures(&mut store, Slot(7), proposer_7, 1) + .expect("block production for block_7 failed"); + + assert_eq!(block_7.parent_root, block_5_root); + + let body_targets: Vec = (0..block_7.body.attestations.len_usize()) + .map(|i| { + block_7 + .body + .attestations + .get(i as u64) + .expect("missing attestation") + .data + .target + .clone() + }) + .collect(); + assert!(body_targets.contains(&block_2_ckpt)); + + let signed_block_7 = SignedBlock { + block: block_7, + signature: BlockSignatures::default(), + }; + on_block(&mut store, &mut cache, signed_block_7, false).expect("on_block for block_7 failed"); + assert_eq!(store.latest_justified, block_2_ckpt); + + let _ = block_6_root; +} diff --git a/lean_client/metrics/src/metrics.rs b/lean_client/metrics/src/metrics.rs index cbd8cc44..50148821 100644 --- a/lean_client/metrics/src/metrics.rs +++ b/lean_client/metrics/src/metrics.rs @@ -3,7 +3,8 @@ use std::{sync::Arc, time::SystemTime}; use anyhow::{Context, Result}; use once_cell::sync::OnceCell; use prometheus::{ - GaugeVec, Histogram, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, histogram_opts, opts, + GaugeVec, Histogram, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, + histogram_opts, opts, }; pub static METRICS: OnceCell> = OnceCell::new(); @@ -194,6 +195,12 @@ pub struct Metrics { /// the data_root present in `latest_known_aggregated_payloads`. Drift /// between the two maps would silently shrink the proposer's pool. pub lean_build_block_pool_missing_att_data: IntCounter, + pub lean_build_block_fixed_point_no_converge_total: IntCounter, + pub lean_block_proposal_attestation_build_phase_seconds: HistogramVec, + pub lean_block_proposal_attestation_builds_total: IntCounter, + pub lean_block_proposal_child_payloads_consumed_total: IntCounter, + pub lean_block_proposal_attestation_data_selected: Histogram, + pub lean_block_proposal_aggregates_selected: Histogram, /// Snapshot size at clone time, measured in total entries across the largest /// Store maps (blocks + states + gossip_signatures + known_aggregated_payloads @@ -534,6 +541,38 @@ impl Metrics { "lean_build_block_pool_missing_att_data", "Total payload entries dropped at proposal time because attestation_data_by_root has no entry for the data_root", )?, + lean_build_block_fixed_point_no_converge_total: IntCounter::new( + "lean_build_block_fixed_point_no_converge_total", + "Total build_block calls where the produced block's latest_justified did not reach the store's latest_justified", + )?, + lean_block_proposal_attestation_build_phase_seconds: HistogramVec::new( + histogram_opts!( + "lean_block_proposal_attestation_build_phase_seconds", + "Phase-level time in block-proposal attestation selection (build_block): select_payloads, compact, stf_simulate", + vec![ + 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0 + ] + ), + &["phase"], + )?, + lean_block_proposal_attestation_builds_total: IntCounter::new( + "lean_block_proposal_attestation_builds_total", + "Completed block-proposal attestation selection runs (one per proposal attempt)", + )?, + lean_block_proposal_child_payloads_consumed_total: IntCounter::new( + "lean_block_proposal_child_payloads_consumed_total", + "Child aggregated payloads selected during greedy proof picking (before recursive compaction)", + )?, + lean_block_proposal_attestation_data_selected: Histogram::with_opts(histogram_opts!( + "lean_block_proposal_attestation_data_selected", + "Distinct AttestationData entries in the proposal block body", + vec![0.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0] + ))?, + lean_block_proposal_aggregates_selected: Histogram::with_opts(histogram_opts!( + "lean_block_proposal_aggregates_selected", + "Aggregated signature proofs in the proposal result after compaction", + vec![0.0, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0] + ))?, lean_aggregation_snapshot_size_entries: Histogram::with_opts(histogram_opts!( "lean_aggregation_snapshot_size_entries", "Total entries across all major Store maps at snapshot clone time", @@ -785,6 +824,26 @@ impl Metrics { default_registry.register(Box::new( self.lean_build_block_pool_missing_att_data.clone(), ))?; + default_registry.register(Box::new( + self.lean_build_block_fixed_point_no_converge_total.clone(), + ))?; + default_registry.register(Box::new( + self.lean_block_proposal_attestation_build_phase_seconds + .clone(), + ))?; + default_registry.register(Box::new( + self.lean_block_proposal_attestation_builds_total.clone(), + ))?; + default_registry.register(Box::new( + self.lean_block_proposal_child_payloads_consumed_total + .clone(), + ))?; + default_registry.register(Box::new( + self.lean_block_proposal_attestation_data_selected.clone(), + ))?; + default_registry.register(Box::new( + self.lean_block_proposal_aggregates_selected.clone(), + ))?; default_registry.register(Box::new( self.lean_aggregation_snapshot_size_entries.clone(), ))?; diff --git a/lean_client/xmss/src/secret_key.rs b/lean_client/xmss/src/secret_key.rs index 50a449ce..ba93b51c 100644 --- a/lean_client/xmss/src/secret_key.rs +++ b/lean_client/xmss/src/secret_key.rs @@ -3,7 +3,7 @@ use derive_more::Debug; use leansig::serialization::Serializable; use leansig::signature::SignatureScheme; use leansig::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_32::{ - SchemeAbortingTargetSumLifetime32Dim46Base8 as XmssScheme, + SIGAbortingTargetSumLifetime32Dim46Base8 as XmssScheme, SecretKeyAbortingTargetSumLifetime32Dim46Base8 as XmssSecretKey, }; use rand::CryptoRng;