@@ -14,7 +14,9 @@ use der::{
1414use generic_log_worker:: {
1515 batcher_id_from_lookup_key, deserialize, get_cached_metadata, get_durable_object_stub,
1616 init_logging, load_cache_kv, load_public_bucket,
17- log_ops:: { prove_subtree_inclusion, read_leaf, ProofError , CHECKPOINT_KEY } ,
17+ log_ops:: {
18+ prove_subtree_consistency, prove_subtree_inclusion, read_leaf, ProofError , CHECKPOINT_KEY ,
19+ } ,
1820 put_cache_entry_metadata, serialize,
1921 util:: now_millis,
2022 ObjectBackend , ObjectBucket , ENTRY_ENDPOINT , METRICS_ENDPOINT ,
@@ -28,7 +30,9 @@ use serde::{Deserialize, Serialize};
2830use serde_with:: { base64:: Base64 , serde_as} ;
2931use signed_note:: { NoteVerifier , VerifierList } ;
3032use std:: time:: Duration ;
31- use tlog_tiles:: { open_checkpoint, LeafIndex , PendingLogEntry , PendingLogEntryBlob } ;
33+ use tlog_tiles:: {
34+ open_checkpoint, CheckpointText , LeafIndex , PendingLogEntry , PendingLogEntryBlob ,
35+ } ;
3236#[ allow( clippy:: wildcard_imports) ]
3337use worker:: * ;
3438use x509_cert:: {
@@ -71,6 +75,24 @@ pub struct GetCertificateResponse {
7175 pub landmark_id : usize ,
7276}
7377
78+ /// GET response structure for the `/get-landmark-bundle` endpoint
79+ #[ serde_as]
80+ #[ derive( Serialize , Deserialize ) ]
81+ pub struct GetLandmarkBundleResponse {
82+ #[ serde_as( as = "Base64" ) ]
83+ pub checkpoint : Vec < u8 > ,
84+ pub subtrees : Vec < SubtreeWithConsistencyProof > ,
85+ }
86+
87+ #[ serde_as]
88+ #[ derive( Serialize , Deserialize ) ]
89+ pub struct SubtreeWithConsistencyProof {
90+ pub start : u64 ,
91+ pub end : u64 ,
92+ #[ serde_as( as = "Vec<Base64>" ) ]
93+ pub consistency_proof : Vec < Vec < u8 > > ,
94+ }
95+
7496/// Start is the first code run when the Wasm module is loaded.
7597#[ event( start) ]
7698fn start ( ) {
@@ -128,39 +150,13 @@ async fn main(req: Request, env: Env, _ctx: Context) -> Result<Response> {
128150 let object_backend = ObjectBucket :: new ( load_public_bucket ( & ctx. env , name) ?) ;
129151 // Fetch the current checkpoint to know which tiles to fetch
130152 // (full or partials).
131- let checkpoint_bytes = object_backend
132- . fetch ( CHECKPOINT_KEY )
133- . await ?
134- . ok_or ( "no checkpoint in object storage" . to_string ( ) ) ?;
135- let origin = & load_origin ( name) ;
136- let verifiers = & VerifierList :: new (
137- load_checkpoint_signers ( & ctx. env , name)
138- . iter ( )
139- . map ( |s| s. verifier ( ) )
140- . collect :: < Vec < Box < dyn NoteVerifier > > > ( ) ,
141- ) ;
142- let checkpoint = open_checkpoint (
143- origin. as_str ( ) ,
144- verifiers,
145- now_millis ( ) ,
146- & checkpoint_bytes,
147- )
148- . map_err ( |e| e. to_string ( ) ) ?
149- . 0 ;
153+ let ( checkpoint, _checkpoint_bytes) =
154+ get_current_checkpoint ( & ctx. env , name, & object_backend) . await ?;
150155 if leaf_index >= checkpoint. size ( ) {
151156 return Response :: error ( "Leaf index is not in log" , 422 ) ;
152157 }
153158
154- let seq = if let Some ( bytes) = object_backend. fetch ( LANDMARK_KEY ) . await ? {
155- let max_landmarks = params
156- . max_certificate_lifetime_secs
157- . div_ceil ( params. landmark_interval_secs )
158- + 1 ;
159- LandmarkSequence :: from_bytes ( & bytes, max_landmarks)
160- . map_err ( |e| e. to_string ( ) ) ?
161- } else {
162- return Err ( "failed to get landmark sequence" . into ( ) ) ;
163- } ;
159+ let seq = get_landmark_sequence ( name, & object_backend) . await ?;
164160 if leaf_index < seq. first_index ( ) {
165161 return Response :: error ( "Leaf index is before first active landmark" , 422 ) ;
166162 }
@@ -223,6 +219,9 @@ async fn main(req: Request, env: Env, _ctx: Context) -> Result<Response> {
223219
224220 Response :: from_json ( & GetCertificateResponse { data, landmark_id } )
225221 } )
222+ . get_async ( "/logs/:log/get-landmark-bundle" , |_req, ctx| async move {
223+ get_landmark_bundle ( & ctx. env , ctx. data ) . await
224+ } )
226225 . get ( "/logs/:log/metadata" , |_req, ctx| {
227226 let name = ctx. data ;
228227 let params = & CONFIG . logs [ name] ;
@@ -442,6 +441,90 @@ async fn add_entry(mut req: Request, env: &Env, name: &str) -> Result<Response>
442441 } )
443442}
444443
444+ async fn get_landmark_bundle ( env : & Env , name : & str ) -> Result < Response > {
445+ let object_backend = ObjectBucket :: new ( load_public_bucket ( env, name) ?) ;
446+
447+ // Fetch the current checkpoint.
448+ let ( checkpoint, checkpoint_bytes) = get_current_checkpoint ( env, name, & object_backend) . await ?;
449+
450+ // Fetch the current landmark sequence.
451+ let landmark_sequence = get_landmark_sequence ( name, & object_backend) . await ?;
452+
453+ // Compute the sequence of landmark subtrees and, for each subtree, a proof of consistency with
454+ // the checkpoint. Each signatureless MTC includes an inclusion proof in one of these subtrees.
455+ let mut subtrees = Vec :: new ( ) ;
456+ for landmark_subtree in landmark_sequence. subtrees ( ) {
457+ let consistency_proof = match prove_subtree_consistency (
458+ * checkpoint. hash ( ) ,
459+ checkpoint. size ( ) ,
460+ landmark_subtree. lo ( ) ,
461+ landmark_subtree. hi ( ) ,
462+ & object_backend,
463+ )
464+ . await
465+ {
466+ Ok ( p) => p,
467+ Err ( ProofError :: Tlog ( s) ) => return Response :: error ( s. to_string ( ) , 422 ) ,
468+ Err ( ProofError :: Other ( e) ) => return Err ( e. to_string ( ) . into ( ) ) ,
469+ } ;
470+
471+ subtrees. push ( SubtreeWithConsistencyProof {
472+ start : landmark_subtree. lo ( ) ,
473+ end : landmark_subtree. hi ( ) ,
474+ consistency_proof : consistency_proof. iter ( ) . map ( |h| h. 0 . to_vec ( ) ) . collect ( ) ,
475+ } ) ;
476+ }
477+
478+ Response :: from_json ( & GetLandmarkBundleResponse {
479+ checkpoint : checkpoint_bytes,
480+ subtrees,
481+ } )
482+ }
483+
484+ async fn get_current_checkpoint (
485+ env : & Env ,
486+ name : & str ,
487+ object_backend : & ObjectBucket ,
488+ ) -> Result < ( CheckpointText , Vec < u8 > ) > {
489+ let checkpoint_bytes = object_backend
490+ . fetch ( CHECKPOINT_KEY )
491+ . await ?
492+ . ok_or ( "no checkpoint in object storage" . to_string ( ) ) ?;
493+
494+ let origin = & load_origin ( name) ;
495+ let verifiers = & VerifierList :: new (
496+ load_checkpoint_signers ( env, name)
497+ . iter ( )
498+ . map ( |s| s. verifier ( ) )
499+ . collect :: < Vec < Box < dyn NoteVerifier > > > ( ) ,
500+ ) ;
501+ let ( checkpoint, _timestamp) =
502+ open_checkpoint ( origin. as_str ( ) , verifiers, now_millis ( ) , & checkpoint_bytes)
503+ . map_err ( |e| e. to_string ( ) ) ?;
504+ Ok ( ( checkpoint, checkpoint_bytes) )
505+ }
506+
507+ async fn get_landmark_sequence (
508+ name : & str ,
509+ object_backend : & ObjectBucket ,
510+ ) -> Result < LandmarkSequence > {
511+ let params = & CONFIG . logs [ name] ;
512+
513+ let Some ( landmark_sequence_bytes) = object_backend. fetch ( LANDMARK_KEY ) . await ? else {
514+ return Err ( "failed to get landmark sequence" . into ( ) ) ;
515+ } ;
516+
517+ let max_landmarks = params
518+ . max_certificate_lifetime_secs
519+ . div_ceil ( params. landmark_interval_secs )
520+ + 1 ;
521+
522+ let landmark_sequence = LandmarkSequence :: from_bytes ( & landmark_sequence_bytes, max_landmarks)
523+ . map_err ( |e| e. to_string ( ) ) ?;
524+
525+ Ok ( landmark_sequence)
526+ }
527+
445528fn headers_from_http_metadata ( meta : HttpMetadata ) -> Headers {
446529 let h = Headers :: new ( ) ;
447530 if let Some ( hdr) = meta. cache_control {
0 commit comments