@@ -7,6 +7,8 @@ use std::{
7
7
str:: FromStr ,
8
8
} ;
9
9
10
+ use log:: info;
11
+
10
12
use serde:: {
11
13
de:: Error ,
12
14
Deserialize ,
@@ -16,8 +18,10 @@ use serde::{
16
18
} ;
17
19
use solana_program:: pubkey:: Pubkey ;
18
20
21
+ use crate :: BatchState ;
22
+
19
23
/// Pyth2wormhole config specific to attestation requests
20
- #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
24
+ #[ derive( Clone , Debug , Hash , Deserialize , Serialize , PartialEq ) ]
21
25
pub struct AttestationConfig {
22
26
#[ serde( default = "default_min_msg_reuse_interval_ms" ) ]
23
27
pub min_msg_reuse_interval_ms : u64 ,
@@ -30,6 +34,15 @@ pub struct AttestationConfig {
30
34
default // Uses Option::default() which is None
31
35
) ]
32
36
pub mapping_addr : Option < Pubkey > ,
37
+ /// The known symbol list will be reloaded based off this
38
+ /// interval, to account for mapping changes. Note: This interval
39
+ /// will only work if the mapping address is defined. Whenever
40
+ /// it's time to look up the mapping, new attestation jobs are
41
+ /// started lazily, only if mapping contents affected the known
42
+ /// symbol list, and before stopping the pre-existing obsolete
43
+ /// jobs to maintain uninterrupted cranking.
44
+ #[ serde( default = "default_mapping_reload_interval_mins" ) ]
45
+ pub mapping_reload_interval_mins : u64 ,
33
46
#[ serde( default = "default_min_rpc_interval_ms" ) ]
34
47
/// Rate-limiting minimum delay between RPC requests in milliseconds"
35
48
pub min_rpc_interval_ms : u64 ,
@@ -49,7 +62,7 @@ impl AttestationConfig {
49
62
for existing_group in & self . symbol_groups {
50
63
for existing_sym in & existing_group. symbols {
51
64
// Check if new symbols mention this product
52
- if let Some ( mut prices) = new_symbols. get_mut ( & existing_sym. product_addr ) {
65
+ if let Some ( prices) = new_symbols. get_mut ( & existing_sym. product_addr ) {
53
66
// Prune the price if exists
54
67
prices. remove ( & existing_sym. price_addr ) ;
55
68
}
@@ -74,7 +87,7 @@ impl AttestationConfig {
74
87
. iter_mut ( )
75
88
. find ( |g| g. group_name == group_name) // Advances the iterator and returns Some(item) on first hit
76
89
{
77
- Some ( mut existing_group) => existing_group. symbols . append ( & mut new_symbols_vec) ,
90
+ Some ( existing_group) => existing_group. symbols . append ( & mut new_symbols_vec) ,
78
91
None if new_symbols_vec. len ( ) != 0 => {
79
92
// Group does not exist, assume defaults
80
93
let new_group = SymbolGroup {
@@ -88,9 +101,30 @@ impl AttestationConfig {
88
101
None => { }
89
102
}
90
103
}
104
+
105
+ pub fn as_batches ( & self , max_batch_size : usize ) -> Vec < BatchState > {
106
+ self . symbol_groups
107
+ . iter ( )
108
+ . map ( move |g| {
109
+ let conditions4closure = g. conditions . clone ( ) ;
110
+ let name4closure = g. group_name . clone ( ) ;
111
+
112
+ info ! ( "Group {:?}, {} symbols" , g. group_name, g. symbols. len( ) , ) ;
113
+
114
+ // Divide group into batches
115
+ g. symbols
116
+ . as_slice ( )
117
+ . chunks ( max_batch_size. clone ( ) )
118
+ . map ( move |symbols| {
119
+ BatchState :: new ( name4closure. clone ( ) , symbols, conditions4closure. clone ( ) )
120
+ } )
121
+ } )
122
+ . flatten ( )
123
+ . collect ( )
124
+ }
91
125
}
92
126
93
- #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
127
+ #[ derive( Clone , Debug , Hash , Deserialize , Serialize , PartialEq ) ]
94
128
pub struct SymbolGroup {
95
129
pub group_name : String ,
96
130
/// Attestation conditions applied to all symbols in this group
@@ -106,6 +140,10 @@ pub const fn default_min_msg_reuse_interval_ms() -> u64 {
106
140
10_000 // 10s
107
141
}
108
142
143
+ pub const fn default_mapping_reload_interval_mins ( ) -> u64 {
144
+ 15
145
+ }
146
+
109
147
pub const fn default_min_rpc_interval_ms ( ) -> u64 {
110
148
150
111
149
}
@@ -122,7 +160,7 @@ pub const fn default_max_batch_jobs() -> usize {
122
160
/// of the active conditions is met. Option<> fields can be
123
161
/// de-activated with None. All conditions are inactive by default,
124
162
/// except for the non-Option ones.
125
- #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
163
+ #[ derive( Clone , Debug , Hash , Deserialize , Serialize , PartialEq ) ]
126
164
pub struct AttestationConditions {
127
165
/// Baseline, unconditional attestation interval. Attestation is triggered if the specified interval elapsed since last attestation.
128
166
#[ serde( default = "default_min_interval_secs" ) ]
@@ -134,9 +172,10 @@ pub struct AttestationConditions {
134
172
#[ serde( default = "default_max_batch_jobs" ) ]
135
173
pub max_batch_jobs : usize ,
136
174
137
- /// Trigger attestation if price changes by the specified percentage.
175
+ /// Trigger attestation if price changes by the specified
176
+ /// percentage, expressed in integer basis points (1bps = 0.01%)
138
177
#[ serde( default ) ]
139
- pub price_changed_pct : Option < f64 > ,
178
+ pub price_changed_bps : Option < u64 > ,
140
179
141
180
/// Trigger attestation if publish_time advances at least the
142
181
/// specified amount.
@@ -152,11 +191,11 @@ impl AttestationConditions {
152
191
let AttestationConditions {
153
192
min_interval_secs : _min_interval_secs,
154
193
max_batch_jobs : _max_batch_jobs,
155
- price_changed_pct ,
194
+ price_changed_bps ,
156
195
publish_time_min_delta_secs,
157
196
} = self ;
158
197
159
- price_changed_pct . is_some ( ) || publish_time_min_delta_secs. is_some ( )
198
+ price_changed_bps . is_some ( ) || publish_time_min_delta_secs. is_some ( )
160
199
}
161
200
}
162
201
@@ -165,14 +204,14 @@ impl Default for AttestationConditions {
165
204
Self {
166
205
min_interval_secs : default_min_interval_secs ( ) ,
167
206
max_batch_jobs : default_max_batch_jobs ( ) ,
168
- price_changed_pct : None ,
207
+ price_changed_bps : None ,
169
208
publish_time_min_delta_secs : None ,
170
209
}
171
210
}
172
211
}
173
212
174
213
/// Config entry for a Pyth product + price pair
175
- #[ derive( Clone , Default , Debug , Deserialize , Serialize , PartialEq , Eq ) ]
214
+ #[ derive( Clone , Default , Debug , Hash , Deserialize , Serialize , PartialEq , Eq ) ]
176
215
pub struct P2WSymbol {
177
216
/// User-defined human-readable name
178
217
pub name : Option < String > ,
@@ -283,6 +322,7 @@ mod tests {
283
322
max_msg_accounts : 100_000 ,
284
323
min_rpc_interval_ms : 2123 ,
285
324
mapping_addr : None ,
325
+ mapping_reload_interval_mins : 42 ,
286
326
symbol_groups : vec ! [ fastbois, slowbois] ,
287
327
} ;
288
328
@@ -302,6 +342,7 @@ mod tests {
302
342
max_msg_accounts : 100 ,
303
343
min_rpc_interval_ms : 42422 ,
304
344
mapping_addr : None ,
345
+ mapping_reload_interval_mins : 42 ,
305
346
symbol_groups : vec ! [ ] ,
306
347
} ;
307
348
0 commit comments