55from pythclient .pythaccounts import PythPriceStatus
66from pythclient .solana import SolanaPublicKey
77
8+ PUBLISHER_EXCLUSION_DISTANCE = 25
9+
810
911@dataclass
1012class PublisherState :
13+ publisher_name : str
1114 symbol : str
1215 public_key : SolanaPublicKey
1316 status : PythPriceStatus
17+ aggregate_status : PythPriceStatus
1418 slot : int
15- slot_aggregate : int
19+ aggregate_slot : int
20+ latest_block_slot : int
1621 price : float
1722 price_aggregate : float
1823 confidence_interval : float
@@ -33,11 +38,11 @@ def state(self) -> PublisherState:
3338 def run (self ) -> bool :
3439 ...
3540
36- def error_message (self , publishers : Dict [ str , str ] ) -> str :
41+ def error_message (self ) -> str :
3742 ...
3843
3944
40- class PublisherAggregateCheck (PublisherCheck ):
45+ class PublisherWithinAggregateConfidenceCheck (PublisherCheck ):
4146 def __init__ (self , state : PublisherState , config : PublisherCheckConfig ):
4247 self .__state = state
4348 self .__max_interval_distance : int = int (config ["max_interval_distance" ])
@@ -46,10 +51,23 @@ def state(self) -> PublisherState:
4651 return self .__state
4752
4853 def run (self ) -> bool :
54+ # Skip if not trading
55+ if self .__state .status != PythPriceStatus .TRADING :
56+ return True
57+
58+ # Skip if aggregate is not trading
59+ if self .__state .aggregate_status != PythPriceStatus .TRADING :
60+ return True
61+
4962 # Skip if confidence interval is zero
5063 if self .__state .confidence_interval == 0 :
5164 return True
5265
66+ # Pass if publisher slot is far from aggregate slot
67+ distance = abs (self .__state .slot - self .__state .aggregate_slot )
68+ if distance > PUBLISHER_EXCLUSION_DISTANCE :
69+ return True
70+
5371 diff = self .__state .price - self .__state .price_aggregate
5472 intervals_away = abs (diff / self .__state .confidence_interval_aggregate )
5573
@@ -60,11 +78,16 @@ def run(self) -> bool:
6078 # Fail
6179 return False
6280
63- def error_message (self , publishers ) -> str :
81+ def error_message (self ) -> str :
82+ diff = self .__state .price - self .__state .price_aggregate
83+ intervals_away = abs (diff / self .__state .confidence_interval_aggregate )
84+
6485 return dedent (
6586 f"""
66- { publishers [self .__state .public_key .key ]} price is too far from aggregate.
87+ { self .__state .publisher_name } price not within aggregate confidence.
88+ It is { intervals_away } times away from confidence.
6789
90+ Symbol: { self .__state .symbol }
6891 Publisher price: { self .__state .price } ± { self .__state .confidence_interval }
6992 Aggregate price: { self .__state .price_aggregate } ± { self .__state .confidence_interval_aggregate }
7093 """
@@ -81,7 +104,12 @@ def state(self) -> PublisherState:
81104
82105 def run (self ) -> bool :
83106 # Skip if not trading
84- if not self .__state .status == PythPriceStatus .TRADING :
107+ if self .__state .status != PythPriceStatus .TRADING :
108+ return True
109+
110+ # Pass if publisher slot is far from aggregate slot
111+ distance = abs (self .__state .slot - self .__state .aggregate_slot )
112+ if distance > PUBLISHER_EXCLUSION_DISTANCE :
85113 return True
86114
87115 # Pass if confidence interval is greater than min_confidence_interval
@@ -91,11 +119,12 @@ def run(self) -> bool:
91119 # Fail
92120 return False
93121
94- def error_message (self , publishers ) -> str :
122+ def error_message (self ) -> str :
95123 return dedent (
96124 f"""
97- { publishers [ self .__state .public_key . key ] } confidence interval is too tight.
125+ { self .__state .publisher_name } confidence interval is too tight.
98126
127+ Symbol: { self .__state .symbol }
99128 Price: { self .__state .price }
100129 Confidence interval: { self .__state .confidence_interval }
101130 """
@@ -106,27 +135,34 @@ class PublisherOfflineCheck(PublisherCheck):
106135 def __init__ (self , state : PublisherState , config : PublisherCheckConfig ):
107136 self .__state = state
108137 self .__max_slot_distance : int = int (config ["max_slot_distance" ])
138+ self .__abandoned_slot_distance : int = int (config ["abandoned_slot_distance" ])
109139
110140 def state (self ) -> PublisherState :
111141 return self .__state
112142
113143 def run (self ) -> bool :
114- distance = abs ( self .__state .slot - self .__state .slot_aggregate )
144+ distance = self .__state .latest_block_slot - self .__state .slot
115145
116146 # Pass if publisher slot is not too far from aggregate slot
117- if distance < 25 :
147+ if distance < self .__max_slot_distance :
148+ return True
149+
150+ # Pass if publisher has been inactive for a long time
151+ if distance > self .__abandoned_slot_distance :
118152 return True
119153
120154 # Fail
121- return True
155+ return False
122156
123- def error_message (self , publishers ) -> str :
157+ def error_message (self ) -> str :
158+ distance = self .__state .latest_block_slot - self .__state .slot
124159 return dedent (
125160 f"""
126- { publishers [ self .__state .public_key . key ] } hasn't published recently.
161+ { self .__state .publisher_name } hasn't published recently for { distance } slots .
127162
163+ Symbol: { self .__state .symbol }
128164 Publisher slot: { self .__state .slot }
129- Aggregate slot: { self .__state .slot_aggregate }
165+ Aggregate slot: { self .__state .aggregate_slot }
130166 """
131167 ).strip ()
132168
@@ -141,47 +177,51 @@ def state(self) -> PublisherState:
141177 return self .__state
142178
143179 def run (self ) -> bool :
144- price_diff = abs (self .__state .price - self .__state .price_aggregate )
145- slot_diff = abs (self .__state .slot - self .__state .slot_aggregate )
180+ # Skip if aggregate status is not trading
181+ if self .__state .aggregate_status != PythPriceStatus .TRADING :
182+ return True
146183
147184 # Skip if not trading
148185 if self .__state .status != PythPriceStatus .TRADING :
149186 return True
150187
151188 # Skip if publisher is too far behind
189+ slot_diff = abs (self .__state .slot - self .__state .aggregate_slot )
152190 if slot_diff > self .__max_slot_distance :
153191 return True
154192
155- # Skip if no aggregate
156- if self .__state .price_aggregate == 0 :
193+ # Skip if published price is zero
194+ if self .__state .price == 0 :
157195 return True
158196
159- distance = (price_diff / self .__state .price_aggregate ) * 100
197+ price_diff = abs (self .__state .price - self .__state .price_aggregate )
198+ deviation = (price_diff / self .__state .price_aggregate ) * 100
160199
161200 # Pass if deviation is less than max distance
162- if distance <= self .__max_aggregate_distance :
201+ if deviation <= self .__max_aggregate_distance :
163202 return True
164203
165204 # Fail
166205 return False
167206
168- def error_message (self , publishers ) -> str :
207+ def error_message (self ) -> str :
169208 price_diff = abs (self .__state .price - self .__state .price_aggregate )
170- distance = (price_diff / self .__state .price_aggregate ) * 100
209+ deviation = (price_diff / self .__state .price_aggregate ) * 100
171210
172211 return dedent (
173212 f"""
174- { publishers [ self .__state .public_key . key ] } price is too far from aggregate.
213+ { self .__state .publisher_name } price is too far from aggregate price .
175214
215+ Symbol: { self .__state .symbol }
176216 Publisher price: { self .__state .price }
177217 Aggregate price: { self .__state .price_aggregate }
178- Distance : { distance } %
218+ Deviation : { deviation } %
179219 """
180220 ).strip ()
181221
182222
183223PUBLISHER_CHECKS = [
184- PublisherAggregateCheck ,
224+ PublisherWithinAggregateConfidenceCheck ,
185225 PublisherConfidenceIntervalCheck ,
186226 PublisherOfflineCheck ,
187227 PublisherPriceCheck ,
0 commit comments