1
- use std:: collections:: { BTreeMap , HashMap , HashSet } ;
1
+ use std:: {
2
+ collections:: { BTreeMap , HashMap , HashSet } ,
3
+ sync:: {
4
+ Arc ,
5
+ atomic:: { AtomicU64 , Ordering } ,
6
+ } ,
7
+ } ;
2
8
3
9
use crate :: { Config , Height , Watcher } ;
4
10
use espresso_types:: { Header , NamespaceId } ;
5
11
use futures:: { StreamExt , stream:: SelectAll } ;
6
- use tokio:: { spawn, sync:: mpsc, task:: JoinHandle } ;
12
+ use tokio:: {
13
+ spawn,
14
+ sync:: { Barrier , mpsc} ,
15
+ task:: JoinHandle ,
16
+ } ;
7
17
use tokio_stream:: wrappers:: ReceiverStream ;
8
18
use tracing:: { debug, warn} ;
9
19
10
20
#[ derive( Debug ) ]
11
21
pub struct Multiwatcher {
12
22
threshold : usize ,
23
+ lower_bound : Arc < AtomicU64 > ,
13
24
watchers : Vec < JoinHandle < ( ) > > ,
14
25
headers : BTreeMap < Height , HashMap < Header , HashSet < Id > > > ,
15
26
stream : SelectAll < ReceiverStream < ( Id , Header ) > > ,
@@ -35,22 +46,47 @@ impl Multiwatcher {
35
46
{
36
47
let height = height. into ( ) ;
37
48
let nsid = nsid. into ( ) ;
49
+
50
+ // We require `threshold` watchers to deliver the next header.
51
+ // Adversaries may produce headers in quick succession, causing
52
+ // excessive memory usage.
53
+ let barrier = Arc :: new ( Barrier :: new ( threshold) ) ;
54
+
55
+ // We track the last delivered height as a lower bound.
56
+ // Watchers skip over headers up to and including that height.
57
+ let lower_bound = Arc :: new ( AtomicU64 :: new ( height. into ( ) ) ) ;
58
+
38
59
let mut stream = SelectAll :: new ( ) ;
39
60
let mut watchers = Vec :: new ( ) ;
61
+
40
62
for ( i, c) in configs. into_iter ( ) . enumerate ( ) {
41
- let ( tx, rx) = mpsc:: channel ( 32 ) ;
63
+ let ( tx, rx) = mpsc:: channel ( 10 ) ;
42
64
stream. push ( ReceiverStream :: new ( rx) ) ;
65
+ let barrier = barrier. clone ( ) ;
66
+ let lower_bound = lower_bound. clone ( ) ;
43
67
watchers. push ( spawn ( async move {
44
- let id = Id ( i) ;
68
+ let i = Id ( i) ;
45
69
let mut w = Watcher :: new ( c, height, nsid) ;
46
- while tx. send ( ( id, w. next ( ) . await ) ) . await . is_ok ( ) { }
70
+ loop {
71
+ let h = w. next ( ) . await ;
72
+ if h. height ( ) <= lower_bound. load ( Ordering :: Relaxed ) {
73
+ continue ;
74
+ }
75
+ if tx. send ( ( i, h) ) . await . is_err ( ) {
76
+ break ;
77
+ }
78
+ barrier. wait ( ) . await ;
79
+ }
47
80
} ) ) ;
48
81
}
82
+
49
83
assert ! ( !watchers. is_empty( ) ) ;
84
+
50
85
Self {
51
86
threshold,
52
87
stream,
53
88
watchers,
89
+ lower_bound,
54
90
headers : BTreeMap :: from_iter ( [ ( height, HashMap :: new ( ) ) ] ) ,
55
91
}
56
92
}
@@ -71,6 +107,7 @@ impl Multiwatcher {
71
107
let votes = counter. get ( & hdr) . map ( |ids| ids. len ( ) ) . unwrap_or ( 0 ) + 1 ;
72
108
if votes >= self . threshold {
73
109
self . headers . retain ( |k, _| * k > h) ;
110
+ self . lower_bound . store ( h. into ( ) , Ordering :: Relaxed ) ;
74
111
debug ! ( height = %h, "header available" ) ;
75
112
return hdr;
76
113
}
0 commit comments