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