@@ -2,15 +2,19 @@ mod config;
2
2
mod types;
3
3
mod watcher;
4
4
5
+ use std:: iter:: empty;
5
6
use std:: time:: Duration ;
6
7
8
+ use either:: Either ;
7
9
use espresso_types:: { Header , NamespaceId , Transaction } ;
8
- use reqwest:: { StatusCode , Url , redirect:: Policy } ;
10
+ use multisig:: { Unchecked , Validated } ;
11
+ use reqwest:: { StatusCode , Url } ;
9
12
use serde:: { Serialize , de:: DeserializeOwned } ;
10
13
use serde_json as json;
11
- use timeboost_types:: CertifiedBlock ;
14
+ use timeboost_types:: sailfish:: CommitteeVec ;
15
+ use timeboost_types:: { BlockNumber , CertifiedBlock } ;
12
16
use tokio:: time:: sleep;
13
- use tracing:: warn;
17
+ use tracing:: { debug , warn} ;
14
18
15
19
use crate :: types:: { TX , TaggedBase64 , TransactionsWithProof , VidCommonResponse } ;
16
20
@@ -20,7 +24,7 @@ pub use config::{Config, ConfigBuilder};
20
24
pub use espresso_types;
21
25
22
26
/// A client for the Espresso network.
23
- #[ derive( Debug ) ]
27
+ #[ derive( Debug , Clone ) ]
24
28
pub struct Client {
25
29
config : Config ,
26
30
client : reqwest:: Client ,
@@ -31,7 +35,6 @@ impl Client {
31
35
let r = reqwest:: Client :: builder ( )
32
36
. https_only ( true )
33
37
. timeout ( Duration :: from_secs ( 30 ) )
34
- . redirect ( Policy :: limited ( c. max_redirects ) )
35
38
. build ( )
36
39
. expect ( "TLS and DNS resolver work" ) ;
37
40
Self {
@@ -40,45 +43,90 @@ impl Client {
40
43
}
41
44
}
42
45
43
- pub async fn height ( & mut self ) -> Result < Height , Error > {
46
+ pub fn config ( & self ) -> & Config {
47
+ & self . config
48
+ }
49
+
50
+ pub async fn height ( & self ) -> Result < Height , Error > {
44
51
let u = self . config . base_url . join ( "status/block-height" ) ?;
45
52
self . get_with_retry ( u) . await
46
53
}
47
54
48
- pub async fn submit ( & mut self , cb : & CertifiedBlock ) -> Result < ( ) , Error > {
49
- let nid = NamespaceId :: from ( u64:: from ( u32:: from ( cb. data ( ) . namespace ( ) ) ) ) ;
50
- let trx = Transaction :: new ( nid, serialize ( cb) ?) ;
55
+ pub async fn submit < N > ( & self , nsid : N , cb : & CertifiedBlock < Validated > ) -> Result < ( ) , Error >
56
+ where
57
+ N : Into < NamespaceId > ,
58
+ {
59
+ let trx = Transaction :: new ( nsid. into ( ) , serialize ( cb) ?) ;
51
60
let url = self . config . base_url . join ( "submit/submit" ) ?;
52
61
self . post_with_retry :: < _ , TaggedBase64 < TX > > ( url, & trx)
53
62
. await ?;
54
63
Ok ( ( ) )
55
64
}
56
65
57
- pub async fn verify ( & mut self , h : & Header , cb : & CertifiedBlock ) -> Result < ( ) , Error > {
58
- let nsid = NamespaceId :: from ( u64:: from ( u32:: from ( cb. data ( ) . namespace ( ) ) ) ) ;
59
-
60
- let trxs = self . transactions ( h. height ( ) , nsid) . await ?;
66
+ pub async fn verified < N , const C : usize > (
67
+ & self ,
68
+ nsid : N ,
69
+ hdr : & Header ,
70
+ cvec : & CommitteeVec < C > ,
71
+ ) -> impl Iterator < Item = BlockNumber >
72
+ where
73
+ N : Into < NamespaceId > ,
74
+ {
75
+ let nsid = nsid. into ( ) ;
76
+ debug ! ( node = %self . config. label, %nsid, height = %hdr. height( ) , "verifying blocks" ) ;
77
+ let Ok ( trxs) = self . transactions ( hdr. height ( ) , nsid) . await else {
78
+ debug ! ( node = %self . config. label, %nsid, height = %hdr. height( ) , "no transactions" ) ;
79
+ return Either :: Left ( empty ( ) ) ;
80
+ } ;
61
81
let Some ( proof) = trxs. proof else {
62
- return Err ( ProofError :: NoProof . into ( ) ) ;
82
+ debug ! ( node = %self . config. label, %nsid, height = %hdr. height( ) , "no proof" ) ;
83
+ return Either :: Left ( empty ( ) ) ;
63
84
} ;
64
- if !trxs. transactions . iter ( ) . any ( |t| matches ( t. payload ( ) , cb) ) {
65
- return Err ( Error :: TransactionNotFound ) ;
66
- }
67
-
68
- let vidc = self . vid_common ( h. height ( ) ) . await ?;
69
-
70
- let Some ( ( trxs, ns) ) = proof. verify ( h. ns_table ( ) , & h. payload_commitment ( ) , & vidc. common )
85
+ let Ok ( vidc) = self . vid_common ( hdr. height ( ) ) . await else {
86
+ debug ! ( node = %self . config. label, height = %hdr. height( ) , "no vid common" ) ;
87
+ return Either :: Left ( empty ( ) ) ;
88
+ } ;
89
+ let Some ( ( trxs, ns) ) =
90
+ proof. verify ( hdr. ns_table ( ) , & hdr. payload_commitment ( ) , & vidc. common )
71
91
else {
72
- return Err ( ProofError :: InvalidProof . into ( ) ) ;
92
+ warn ! ( node = %self . config. label, %nsid, height = %hdr. height( ) , "proof verification failed" ) ;
93
+ return Either :: Left ( empty ( ) ) ;
73
94
} ;
74
95
if ns != nsid {
75
- return Err ( ProofError :: NamespaceMismatch ( ns, nsid) . into ( ) ) ;
76
- }
77
- if !trxs. iter ( ) . any ( |t| matches ( t. payload ( ) , cb) ) {
78
- return Err ( ProofError :: TransactionNotInProof . into ( ) ) ;
96
+ warn ! ( node = %self . config. label, a = %nsid, b = %ns, height = %hdr. height( ) , "namespace mismatch" ) ;
97
+ return Either :: Left ( empty ( ) ) ;
79
98
}
80
-
81
- Ok ( ( ) )
99
+ Either :: Right ( trxs. into_iter ( ) . filter_map ( move |t| {
100
+ match deserialize :: < CertifiedBlock < Unchecked > > ( t. payload ( ) ) {
101
+ Ok ( b) => {
102
+ let Some ( c) = cvec. get ( b. committee ( ) ) else {
103
+ warn ! (
104
+ node = %self . config. label,
105
+ height = %hdr. height( ) ,
106
+ committee = %b. committee( ) ,
107
+ "unknown committee"
108
+ ) ;
109
+ return None ;
110
+ } ;
111
+ if let Some ( b) = b. validated ( c) {
112
+ Some ( b. cert ( ) . data ( ) . num ( ) )
113
+ } else {
114
+ warn ! ( node = %self . config. label, height = %hdr. height( ) , "invalid block" ) ;
115
+ None
116
+ }
117
+ }
118
+ Err ( err) => {
119
+ warn ! (
120
+ node = %self . config. label,
121
+ nsid = %nsid,
122
+ height = %hdr. height( ) ,
123
+ err = %err,
124
+ "could not deserialize block"
125
+ ) ;
126
+ None
127
+ }
128
+ }
129
+ } ) )
82
130
}
83
131
84
132
async fn transactions < H , N > ( & self , height : H , nsid : N ) -> Result < TransactionsWithProof , Error >
@@ -116,7 +164,7 @@ impl Client {
116
164
match self . get ( url. clone ( ) ) . await {
117
165
Ok ( a) => return Ok ( a) ,
118
166
Err ( err) => {
119
- warn ! ( %url, %err, "failed to get response" ) ;
167
+ warn ! ( node = % self . config . label , %url, %err, "failed to get response" ) ;
120
168
sleep ( delay. next ( ) . expect ( "infinite delay sequence" ) ) . await ;
121
169
}
122
170
}
@@ -133,7 +181,7 @@ impl Client {
133
181
match self . post ( url. clone ( ) , a) . await {
134
182
Ok ( b) => return Ok ( b) ,
135
183
Err ( err) => {
136
- warn ! ( %url, %err, "failed to post request" ) ;
184
+ warn ! ( node = % self . config . label , %url, %err, "failed to post request" ) ;
137
185
sleep ( delay. next ( ) . expect ( "infinite delay sequence" ) ) . await ;
138
186
}
139
187
}
@@ -168,13 +216,6 @@ impl Client {
168
216
}
169
217
}
170
218
171
- fn matches ( a : & [ u8 ] , b : & CertifiedBlock ) -> bool {
172
- let Ok ( a) = deserialize :: < CertifiedBlock > ( a) else {
173
- return false ;
174
- } ;
175
- a. data ( ) . hash ( ) == b. data ( ) . hash ( ) && a. data ( ) . hash ( ) == b. cert ( ) . data ( ) . hash ( )
176
- }
177
-
178
219
/// Errors `Client` can not recover from.
179
220
#[ derive( Debug , thiserror:: Error ) ]
180
221
pub enum Error {
@@ -235,6 +276,9 @@ fn deserialize<T: DeserializeOwned>(d: &[u8]) -> Result<T, Error> {
235
276
236
277
#[ cfg( test) ]
237
278
mod tests {
279
+ use futures:: StreamExt ;
280
+ use tokio:: pin;
281
+
238
282
use super :: { Client , Config } ;
239
283
240
284
#[ tokio:: test]
@@ -248,11 +292,13 @@ mod tests {
248
292
. unwrap ( )
249
293
. wss_base_url ( "wss://query.decaf.testnet.espresso.network/v1/" )
250
294
. unwrap ( )
295
+ . label ( "decaf_smoke" )
251
296
. build ( ) ;
252
297
253
- let mut clt = Client :: new ( cfg. clone ( ) ) ;
298
+ let clt = Client :: new ( cfg. clone ( ) ) ;
254
299
let height = clt. height ( ) . await . unwrap ( ) ;
255
- let header = super :: watch ( & cfg, height, None ) . await . unwrap ( ) ;
256
- assert_eq ! ( u64 :: from( height) , header. height( ) ) ;
300
+ let headers = super :: watch ( & cfg, height, None ) . await . unwrap ( ) ;
301
+ pin ! ( headers) ;
302
+ assert_eq ! ( u64 :: from( height) , headers. next( ) . await . unwrap( ) . height( ) ) ;
257
303
}
258
304
}
0 commit comments