Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions lean_client/Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
### Variables

# Commit of lean specification. Allows to specify which spec version to use,
# when generating test vectors. Currently set to devnet-3 spec version.
LEAN_SPEC_COMMIT ?= f64b1038caef043849147c791ddd64907fbf19a7
# when generating test vectors. Pinned to leanSpec PR #682 merge commit.
LEAN_SPEC_COMMIT ?= 62eff6e7e6041a283877a546a07cb3b83f4f7d5b
# Docker image name. Used for both release (with publish) and local builds.
DOCKER_REPO ?= sifrai/lean
# Image tag.
Expand Down
19 changes: 12 additions & 7 deletions lean_client/fork_choice/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use tracing::warn;

use crate::block_cache::BlockCache;
use crate::store::{
INTERVALS_PER_SLOT, MILLIS_PER_INTERVAL, STATE_PRUNE_BUFFER, Store, tick_interval, update_head,
GOSSIP_DISPARITY_INTERVALS, INTERVALS_PER_SLOT, MILLIS_PER_INTERVAL, STATE_PRUNE_BUFFER, Store,
tick_interval, update_head,
};

#[inline]
Expand Down Expand Up @@ -74,15 +75,19 @@ fn validate_attestation_data(store: &Store, data: &AttestationData) -> Result<()
head_block.slot.0
);

// Validate attestation is not too far in the future
// We allow a small margin for clock disparity (1 slot), but no further.
let current_slot = store.time / INTERVALS_PER_SLOT;
// Honest validators emit votes only after their slot has begun. Allow exactly
// one interval (~800 ms) of clock skew between peers; a whole-slot margin would
// let an adversary pre-publish next-slot aggregates ahead of any honest
// validator. Lean analogue of mainnet's MAXIMUM_GOSSIP_CLOCK_DISPARITY.
let attestation_start_interval = data.slot.0 * INTERVALS_PER_SLOT;

ensure!(
data.slot.0 <= current_slot + 1,
"Attestation too far in future: attestation slot {} > current slot {} + 1",
attestation_start_interval <= store.time + GOSSIP_DISPARITY_INTERVALS,
"Attestation too far in future: data slot {} (start interval {}) > store time {} + {}",
data.slot.0,
current_slot
attestation_start_interval,
store.time,
GOSSIP_DISPARITY_INTERVALS,
);

Ok(())
Expand Down
5 changes: 5 additions & 0 deletions lean_client/fork_choice/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ pub const SECONDS_PER_SLOT: u64 = 4;
/// Milliseconds per interval: (4 * 1000) / 5 = 800ms
/// Using milliseconds avoids integer division truncation (4/5 = 0 in integer math)
pub const MILLIS_PER_INTERVAL: u64 = (SECONDS_PER_SLOT * 1000) / INTERVALS_PER_SLOT;
/// Future-slot tolerance for attestation gossip, in intervals.
/// Bounds the clock skew the time check absorbs when admitting a vote whose
/// slot has not yet started locally. One interval is ~800 ms — the lean
/// analogue of mainnet's MAXIMUM_GOSSIP_CLOCK_DISPARITY.
pub const GOSSIP_DISPARITY_INTERVALS: u64 = 1;

/// Forkchoice store tracking chain state and validator attestations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@
"leanEnv": "prod",
"endpoint": "/lean/v0/admin/aggregator",
"method": "GET",
"initialIsAggregator": false,
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"is_aggregator": false
},
"_info": {
"hash": "0xb999abf70a1e969388d7fe0e3a958ffb37a25ef5447e7dd7a2c72fd0b956e604",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_aggregator_status_disabled[fork_Devnet]",
"description": "GET aggregator status on a node started with aggregator disabled.",
"fixtureFormat": "api_endpoint"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@
"leanEnv": "prod",
"endpoint": "/lean/v0/admin/aggregator",
"method": "GET",
"initialIsAggregator": true,
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"initialIsAggregator": true,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"is_aggregator": true
},
"_info": {
"hash": "0x8999239b61dc6dd3bea6d273a03110f4dc524517448ae3cbeeaa7633b2ccbbeb",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_aggregator_status_enabled[fork_Devnet]",
"description": "GET aggregator status on a node started with aggregator enabled.",
"fixtureFormat": "api_endpoint"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
"leanEnv": "prod",
"endpoint": "/lean/v0/admin/aggregator",
"method": "POST",
"initialIsAggregator": false,
"requestBody": {
"enabled": true
},
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"requestBody": {
"enabled": true
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"is_aggregator": true,
"previous": false
},
"_info": {
"hash": "0xd8bcd3cbe421e33455bca1a2156267b360311465240df93fbac98b8001e00a03",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_aggregator_toggle_activate[fork_Devnet]",
"description": "POST enable=true flips the role from off to on.",
"fixtureFormat": "api_endpoint"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
"leanEnv": "prod",
"endpoint": "/lean/v0/admin/aggregator",
"method": "POST",
"initialIsAggregator": true,
"requestBody": {
"enabled": false
},
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"requestBody": {
"enabled": false
},
"initialIsAggregator": true,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"is_aggregator": false,
"previous": true
},
"_info": {
"hash": "0x3222c740014574a5fc103db4a43a6812ab7801bf1de8a03f87d7ed699a0ac337",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_aggregator_toggle_deactivate[fork_Devnet]",
"description": "POST enable=false flips the role from on to off.",
"fixtureFormat": "api_endpoint"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
"leanEnv": "prod",
"endpoint": "/lean/v0/admin/aggregator",
"method": "POST",
"initialIsAggregator": false,
"requestBody": {
"enabled": false
},
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"requestBody": {
"enabled": false
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"is_aggregator": false,
"previous": false
},
"_info": {
"hash": "0xeab1eb434423c8224bdd3b35ae2300ed900ce420e14e1af7eb3f960f9a261916",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_aggregator_toggle_idempotent_disable[fork_Devnet]",
"description": "POST enable=false on an already-disabled node returns previous=false and is a no-op.",
"fixtureFormat": "api_endpoint"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
"leanEnv": "prod",
"endpoint": "/lean/v0/admin/aggregator",
"method": "POST",
"initialIsAggregator": true,
"requestBody": {
"enabled": true
},
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"requestBody": {
"enabled": true
},
"initialIsAggregator": true,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"is_aggregator": true,
"previous": true
},
"_info": {
"hash": "0xe4d9c5d8178a36c7ddade1372c479b286f96080d2fe553ced3ff1d37f81b29b5",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_aggregator_toggle_idempotent_enable[fork_Devnet]",
"description": "POST enable=true on an already-enabled node returns previous=true and is a no-op.",
"fixtureFormat": "api_endpoint"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
"network": "Devnet",
"leanEnv": "prod",
"endpoint": "/lean/v0/states/finalized",
"method": "GET",
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/octet-stream",
"expectedBody": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dba9671bac9513c9482f1416a53aabd2c6ce90d5a5f865ce5a55c775325c91360000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4000000e4000000e5000000a5020000a5020000011865a50c6478a3173882ff01f2b7904a2b96b64a8ad63d04ea53b34a539bfd672ffffd40f2e93c2900311f068cf9641b81fd364def30711e272be66f8850f13c393f5c7dc302f6112d3d04672b715b0caef8b823cccac138b5ab9644845cc6262dd8076f49bacf3d0000000000000000ce45954436b2b4038330ba17071d9c482f56e43cfd3c21414a0f326635cc6132523b995e346e4e2df535e6742a5bb506e0c913652fdc4e5caacbdf3117a6687b09e7f46a132bd54f0e9b8730d3e57c655a60557a488ae4078e248244fadeab6d506a79355b5631420100000000000000a6cece462ea48c5e5a31d1576d6d7c7367fdf851a94dd738442c93570f88556ca04991462fedb17191054f65eeb9eb3777a8d27ccfe9915524913f40da7ad5649bf69f450d72b952d2296627072e50524f7e8b775c61c17c36283b210521db7517ef097ca996de100200000000000000a9536c0b365edf64b9f2a50b7e0c84157697d444bcfeaa72f966070ca539090b00ac6d5e154aa3279c5f624bfa96fd025b66ba09951ee23a3065d322d64174463ac89b4fa863ed648a44c02e331e7511da875b4c4fb27c3c2689272634fed634d67b256868f3be59030000000000000001",
"_info": {
"hash": "0x0bad805dc81534775712ed53d44f92930678bd44be08143183e173274ec5cdd8",
"hash": "0xafebe406f0f70121fae02afeb9082de3012203b847efbb50376e855f801f4cfd",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_finalized_state_4v[fork_Devnet]",
"description": "Full SSZ-encoded finalized state for a 4-validator genesis.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
"network": "Devnet",
"leanEnv": "prod",
"endpoint": "/lean/v0/fork_choice",
"method": "GET",
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
Expand All @@ -32,7 +34,7 @@
"validator_count": 4
},
"_info": {
"hash": "0x9fef671ba5f90989e6a16abe45cbe760772d2e64ead892dac806f910bd841dc5",
"hash": "0x3ca9c1339d2685d53ba440deac842390b3c3bd33467420f7e8afe1f969a20bb8",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_fork_choice_4v[fork_Devnet]",
"description": "Fork choice tree at genesis: single node, zero attestation weights.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
"network": "Devnet",
"leanEnv": "prod",
"endpoint": "/lean/v0/fork_choice",
"method": "GET",
"genesisParams": {
"numValidators": 8,
"genesisTime": 0
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
Expand All @@ -32,7 +34,7 @@
"validator_count": 8
},
"_info": {
"hash": "0x0b66df5b4fb0c872fa9f0f3f69f241c7214349e3d28d708da46a264eaf12c63a",
"hash": "0xe3cd4c738e97a2078e332c512fddb24bf56c0420e139f2c58064f5287b45e2d6",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_fork_choice_8v[fork_Devnet]",
"description": "Fork choice tree at genesis with 8 validators. Same shape, higher count.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
"network": "Devnet",
"leanEnv": "prod",
"endpoint": "/lean/v0/health",
"method": "GET",
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"status": "healthy",
"service": "lean-rpc-api"
},
"_info": {
"hash": "0xde9ad15a93299a23397a0fd4c8300527f00d699e4661fb4ec0b4012f823f7c72",
"hash": "0xe4a0ee746c2b746fd53a5fb07b37b51fa44acd9dd8b75455a7780568404879ae",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_health[fork_Devnet]",
"description": "Health returns a fixed payload independent of consensus state.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
"network": "Devnet",
"leanEnv": "prod",
"endpoint": "/lean/v0/checkpoints/justified",
"method": "GET",
"genesisParams": {
"numValidators": 4,
"genesisTime": 0
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"slot": 0,
"root": "0xd123d3d19ba32a08df9b3bf9e55e4447d1a3a3b4f905583d013b8f05c77d585e"
},
"_info": {
"hash": "0xf8fb33f5448cfe776905f89f79078bd7c095f353fe37c889346220c9bd46b176",
"hash": "0xad4d6042d90973af1c9dd20a7c02f77b5c49fcebf1ed17ae80e7546bb81588ec",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_justified_checkpoint_4v[fork_Devnet]",
"description": "Justified checkpoint at genesis with 4 validators.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@
"network": "Devnet",
"leanEnv": "prod",
"endpoint": "/lean/v0/checkpoints/justified",
"method": "GET",
"genesisParams": {
"numValidators": 8,
"genesisTime": 0
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/json",
"expectedBody": {
"slot": 0,
"root": "0xd567e95979f6ce2186cbada494967c77142153c6e5ab38f2bd7a23ee6b92170b"
},
"_info": {
"hash": "0x07ba861cfad6285a8da6bac8b7d5ef3ef08d67b52222f75a4383c65a241fb513",
"hash": "0xa4adc9beef1276e99aee48c2eb46ecba782ddac00a8473655a89a81af5b9ede4",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_endpoints.py::test_justified_checkpoint_8v[fork_Devnet]",
"description": "Justified checkpoint at genesis with 8 validators. Root differs from 4v.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"tests/consensus/devnet/api/test_api_post_genesis.py::test_finalized_state_at_slot_3[fork_Devnet][fork_devnet-api_endpoint]": {
"network": "Devnet",
"leanEnv": "prod",
"endpoint": "/lean/v0/states/finalized",
"method": "GET",
"genesisParams": {
"numValidators": 4,
"genesisTime": 0,
"anchorSlot": 3
},
"initialIsAggregator": false,
"expectedStatusCode": 200,
"expectedContentType": "application/octet-stream",
"expectedBody": "0x00000000000000000300000000000000030000000000000003000000000000006b7c8df5d3628c2425caf72e487418f09c4b34b2693dcccddcba29e28219d5ab0000000000000000000000000000000000000000000000000000000000000000dba9671bac9513c9482f1416a53aabd2c6ce90d5a5f865ce5a55c775325c9136d123d3d19ba32a08df9b3bf9e55e4447d1a3a3b4f905583d013b8f05c77d585e0000000000000000d123d3d19ba32a08df9b3bf9e55e4447d1a3a3b4f905583d013b8f05c77d585e0000000000000000e400000044010000450100000503000005030000d123d3d19ba32a08df9b3bf9e55e4447d1a3a3b4f905583d013b8f05c77d585e6214b969cc3f585a85432ed9dcd3884d4842fb561a3b303a35a771475d58aa886b7c8df5d3628c2425caf72e487418f09c4b34b2693dcccddcba29e28219d5ab041865a50c6478a3173882ff01f2b7904a2b96b64a8ad63d04ea53b34a539bfd672ffffd40f2e93c2900311f068cf9641b81fd364def30711e272be66f8850f13c393f5c7dc302f6112d3d04672b715b0caef8b823cccac138b5ab9644845cc6262dd8076f49bacf3d0000000000000000ce45954436b2b4038330ba17071d9c482f56e43cfd3c21414a0f326635cc6132523b995e346e4e2df535e6742a5bb506e0c913652fdc4e5caacbdf3117a6687b09e7f46a132bd54f0e9b8730d3e57c655a60557a488ae4078e248244fadeab6d506a79355b5631420100000000000000a6cece462ea48c5e5a31d1576d6d7c7367fdf851a94dd738442c93570f88556ca04991462fedb17191054f65eeb9eb3777a8d27ccfe9915524913f40da7ad5649bf69f450d72b952d2296627072e50524f7e8b775c61c17c36283b210521db7517ef097ca996de100200000000000000a9536c0b365edf64b9f2a50b7e0c84157697d444bcfeaa72f966070ca539090b00ac6d5e154aa3279c5f624bfa96fd025b66ba09951ee23a3065d322d64174463ac89b4fa863ed648a44c02e331e7511da875b4c4fb27c3c2689272634fed634d67b256868f3be59030000000000000001",
"_info": {
"hash": "0x213780c9845f99dd9417c4462a66baeda8bc46d2e53d7c6d3035ef00b05bf0b4",
"comment": "`leanSpec` generated test",
"testId": "tests/consensus/devnet/api/test_api_post_genesis.py::test_finalized_state_at_slot_3[fork_Devnet]",
"description": "Finalized-state response returns the SSZ-encoded anchor state after chain advance.\n\n Finalization has not yet moved past genesis (no attestations have\n been injected), but the served state now carries non-empty\n historical_block_hashes because the chain has processed three empty\n blocks. Pins the exact SSZ bytes at that configuration.",
"fixtureFormat": "api_endpoint"
}
}
}
Loading
Loading