24
24
join_all,
25
25
} ,
26
26
key_store:: KeyStore ,
27
- pyth_sdk:: {
28
- Identifier ,
29
- UnixTimestamp ,
30
- } ,
27
+ pyth_sdk:: Identifier ,
31
28
pyth_sdk_solana:: state:: PriceStatus ,
32
29
serde:: {
33
30
Deserialize ,
@@ -111,6 +108,10 @@ pub struct Config {
111
108
/// Age after which a price update is considered stale and not published
112
109
#[ serde( with = "humantime_serde" ) ]
113
110
pub staleness_threshold : Duration ,
111
+ /// Wait at least this long before publishing an unchanged price
112
+ /// state; unchanged price state means only timestamp has changed
113
+ /// with other state identical to last published state.
114
+ pub unchanged_publish_threshold : Duration ,
114
115
/// Maximum size of a batch
115
116
pub max_batch_size : usize ,
116
117
/// Capacity of the channel between the Exporter and the Transaction Monitor
@@ -131,6 +132,7 @@ impl Default for Config {
131
132
refresh_network_state_interval_duration : Duration :: from_millis ( 200 ) ,
132
133
publish_interval_duration : Duration :: from_secs ( 1 ) ,
133
134
staleness_threshold : Duration :: from_secs ( 5 ) ,
135
+ unchanged_publish_threshold : Duration :: from_secs ( 5 ) ,
134
136
max_batch_size : 12 ,
135
137
inflight_transactions_channel_capacity : 10000 ,
136
138
transaction_monitor : Default :: default ( ) ,
@@ -212,8 +214,10 @@ pub struct Exporter {
212
214
/// Channel on which to communicate with the local store
213
215
local_store_tx : Sender < store:: local:: Message > ,
214
216
215
- /// The last time an update was published for each price identifier
216
- last_published_at : HashMap < PriceIdentifier , UnixTimestamp > ,
217
+ /// The last state published for each price identifier. Used to
218
+ /// rule out stale data and prevent repetitive publishing of
219
+ /// unchanged prices.
220
+ last_published_state : HashMap < PriceIdentifier , PriceInfo > ,
217
221
218
222
/// Watch receiver channel to access the current network state
219
223
network_state_rx : watch:: Receiver < NetworkState > ,
@@ -252,7 +256,7 @@ impl Exporter {
252
256
publish_interval,
253
257
key_store,
254
258
local_store_tx,
255
- last_published_at : HashMap :: new ( ) ,
259
+ last_published_state : HashMap :: new ( ) ,
256
260
network_state_rx,
257
261
inflight_transactions_tx,
258
262
publisher_permissions_rx,
@@ -289,16 +293,38 @@ impl Exporter {
289
293
async fn publish_updates ( & mut self ) -> Result < ( ) > {
290
294
let local_store_contents = self . fetch_local_store_contents ( ) . await ?;
291
295
296
+ let now = Utc :: now ( ) . timestamp ( ) ;
297
+
292
298
// Filter the contents to only include information we haven't already sent,
293
299
// and to ignore stale information.
294
300
let fresh_updates = local_store_contents
295
301
. iter ( )
296
302
. filter ( |( identifier, info) | {
297
- * self . last_published_at . get ( identifier) . unwrap_or ( & 0 ) < info. timestamp
303
+ // Filter out timestamps older than what we already published
304
+ if let Some ( last_info) = self . last_published_state . get ( identifier) {
305
+ last_info. timestamp < info. timestamp
306
+ } else {
307
+ true // No prior data found, letting the price through
308
+ }
298
309
} )
299
310
. filter ( |( _identifier, info) | {
300
- ( Utc :: now ( ) . timestamp ( ) - info. timestamp )
301
- < self . config . staleness_threshold . as_secs ( ) as i64
311
+ // Filter out timestamps that are old
312
+ ( now - info. timestamp ) < self . config . staleness_threshold . as_secs ( ) as i64
313
+ } )
314
+ . filter ( |( identifier, info) | {
315
+ // Filter out unchanged price data if the max delay wasn't reached
316
+
317
+ if let Some ( last_info) = self . last_published_state . get ( identifier) {
318
+ if ( info. timestamp - last_info. timestamp )
319
+ > self . config . unchanged_publish_threshold . as_secs ( ) as i64
320
+ {
321
+ true // max delay since last published state reached, we publish anyway
322
+ } else {
323
+ !last_info. cmp_no_timestamp ( * info) // Filter out if data is unchanged
324
+ }
325
+ } else {
326
+ true // No prior data found, letting the price through
327
+ }
302
328
} )
303
329
. collect :: < Vec < _ > > ( ) ;
304
330
@@ -348,7 +374,7 @@ impl Exporter {
348
374
// Note: This message is not an error. Some
349
375
// publishers have different permissions on
350
376
// primary/secondary networks
351
- info ! (
377
+ debug ! (
352
378
self . logger,
353
379
"Exporter: Attempted to publish a price without permission, skipping" ;
354
380
"unpermissioned_price_account" => key_from_id. to_string( ) ,
@@ -373,13 +399,13 @@ impl Exporter {
373
399
. publish_interval_duration
374
400
. div_f64 ( num_batches as f64 ) ,
375
401
) ;
376
- let mut batch_timestamps = HashMap :: new ( ) ;
402
+ let mut batch_state = HashMap :: new ( ) ;
377
403
let mut batch_futures = vec ! [ ] ;
378
404
for batch in batches {
379
405
batch_futures. push ( self . publish_batch ( batch, & publish_keypair) ) ;
380
406
381
407
for ( identifier, info) in batch {
382
- batch_timestamps . insert ( * * identifier, info. timestamp ) ;
408
+ batch_state . insert ( * * identifier, ( * * info) . clone ( ) ) ;
383
409
}
384
410
385
411
batch_send_interval. tick ( ) . await ;
@@ -392,7 +418,7 @@ impl Exporter {
392
418
. into_iter ( )
393
419
. collect :: < Result < Vec < _ > > > ( ) ?;
394
420
395
- self . last_published_at . extend ( batch_timestamps ) ;
421
+ self . last_published_state . extend ( batch_state ) ;
396
422
397
423
Ok ( ( ) )
398
424
}
0 commit comments