@@ -7,7 +7,9 @@ pub const Slots = struct {
77};
88
99/// Fetch finalized and justified slots from lean node endpoints
10- /// The finalized endpoint returns SSZ-encoded LeanState data
10+ /// - Finalized: /lean/v0/states/finalized (SSZ-encoded LeanState)
11+ /// - Justified: /lean/v0/checkpoints/justified (JSON checkpoint with slot)
12+ /// Falls back to finalized slot for justified when the justified endpoint is unavailable
1113pub fn fetchSlots (
1214 allocator : std.mem.Allocator ,
1315 client : * std.http.Client ,
@@ -24,16 +26,88 @@ pub fn fetchSlots(
2426 out_state_ssz ,
2527 );
2628
27- // For now, use finalized slot as justified slot since /lean/v0/states/justified returns 404
28- // TODO: Find the correct endpoint for justified slot
29- const justified_slot = finalized_slot ;
29+ // Fetch justified slot from JSON checkpoint endpoint (zeam serves this)
30+ const justified_slot = fetchJustifiedSlotFromJsonEndpoint (
31+ allocator ,
32+ client ,
33+ base_url ,
34+ ) catch | err | {
35+ log .debug ("Justified checkpoint unavailable ({s}), using finalized slot" , .{@errorName (err )});
36+ return Slots {
37+ .justified_slot = finalized_slot ,
38+ .finalized_slot = finalized_slot ,
39+ };
40+ };
3041
3142 return Slots {
3243 .justified_slot = justified_slot ,
3344 .finalized_slot = finalized_slot ,
3445 };
3546}
3647
48+ /// Fetch justified slot from /lean/v0/checkpoints/justified
49+ /// Returns JSON: {"root": "0x...", "slot": 123}
50+ fn fetchJustifiedSlotFromJsonEndpoint (
51+ allocator : std.mem.Allocator ,
52+ client : * std.http.Client ,
53+ base_url : []const u8 ,
54+ ) ! u64 {
55+ var url_buf : [512 ]u8 = undefined ;
56+ const url = try std .fmt .bufPrint (& url_buf , "{s}/lean/v0/checkpoints/justified" , .{base_url });
57+ const uri = try std .Uri .parse (url );
58+
59+ var header_buf : [4096 ]u8 = undefined ;
60+ var req = try client .open (.GET , uri , .{
61+ .server_header_buffer = & header_buf ,
62+ .extra_headers = &.{
63+ .{ .name = "accept" , .value = "application/json" },
64+ .{ .name = "connection" , .value = "close" },
65+ },
66+ });
67+ defer req .deinit ();
68+
69+ try req .send ();
70+ try req .finish ();
71+ try req .wait ();
72+
73+ if (req .response .status != .ok ) {
74+ log .warn ("Bad status from {s}: {any}" , .{ url , req .response .status });
75+ return error .BadStatus ;
76+ }
77+
78+ var body_buf = std .ArrayList (u8 ).init (allocator );
79+ defer body_buf .deinit ();
80+ try req .reader ().readAllArrayList (& body_buf , 64 * 1024 );
81+
82+ const slot = parseJustifiedSlotFromJson (allocator , body_buf .items ) catch | err | {
83+ log .warn ("Failed to parse justified checkpoint JSON from {s}: {}" , .{ url , err });
84+ return err ;
85+ };
86+
87+ log .debug ("Successfully fetched justified slot {d} from {s}" , .{ slot , url });
88+ return slot ;
89+ }
90+
91+ /// Parse slot from justified checkpoint JSON: {"root": "0x...", "slot": N}
92+ fn parseJustifiedSlotFromJson (allocator : std.mem.Allocator , body : []const u8 ) ! u64 {
93+ var parser = std .json .parseFromSlice (std .json .Value , allocator , body , .{}) catch return error .InvalidJson ;
94+ defer parser .deinit ();
95+
96+ const root = parser .value ;
97+ if (root != .object ) return error .InvalidJson ;
98+ const obj = root .object ;
99+ const slot_val = obj .get ("slot" ) orelse return error .MissingSlot ;
100+ const slot : u64 = switch (slot_val ) {
101+ .integer = > | i | if (i >= 0 ) @intCast (i ) else return error .InvalidSlot ,
102+ .float = > | f | if (f >= 0 and f < 1e18 ) @intFromFloat (f ) else return error .InvalidSlot ,
103+ else = > return error .InvalidSlot ,
104+ };
105+
106+ const max_reasonable_slot : u64 = 1_000_000_000 ;
107+ if (slot > max_reasonable_slot ) return error .InvalidSlot ;
108+ return slot ;
109+ }
110+
37111/// Fetch slot from SSZ-encoded endpoint
38112/// The lean nodes return SSZ-encoded LeanState data in this structure:
39113/// - config.genesis_time: u64 (8 bytes, offset 0-7)
@@ -164,6 +238,12 @@ fn fetchSlotFromSSZEndpoint(
164238 return slot ;
165239}
166240
241+ test "parse justified checkpoint JSON" {
242+ const json = "{\" root\" :\" 0x0000000000000000000000000000000000000000000000000000000000000000\" ,\" slot\" :42}" ;
243+ const slot = try parseJustifiedSlotFromJson (std .testing .allocator , json );
244+ try std .testing .expectEqual (@as (u64 , 42 ), slot );
245+ }
246+
167247test "extract slot from ssz bytes" {
168248 // Simulate SSZ LeanState data
169249 var data : [300 ]u8 = undefined ;
0 commit comments