24
24
account_info:: AccountInfo ,
25
25
entrypoint:: ProgramResult ,
26
26
program_error:: ProgramError ,
27
- program_memory:: sol_memset,
27
+ program_memory:: {
28
+ sol_memcmp,
29
+ sol_memset,
30
+ } ,
28
31
pubkey:: Pubkey ,
29
32
} ,
30
33
std:: mem:: size_of,
@@ -41,8 +44,7 @@ pub fn add_publisher(
41
44
let cmd_args = load :: < AddPublisherArgs > ( instruction_data) ?;
42
45
43
46
pyth_assert (
44
- instruction_data. len ( ) == size_of :: < AddPublisherArgs > ( )
45
- && cmd_args. publisher != Pubkey :: default ( ) ,
47
+ instruction_data. len ( ) == size_of :: < AddPublisherArgs > ( ) ,
46
48
ProgramError :: InvalidArgument ,
47
49
) ?;
48
50
@@ -63,6 +65,14 @@ pub fn add_publisher(
63
65
64
66
let mut price_data = load_checked :: < PriceAccount > ( price_account, cmd_args. header . version ) ?;
65
67
68
+ // Use the call with the default pubkey (000..) as a trigger to sort the publishers as a
69
+ // migration step from unsorted list to sorted list.
70
+ if cmd_args. publisher == Pubkey :: default ( ) {
71
+ let num_comps = try_convert :: < u32 , usize > ( price_data. num_ ) ?;
72
+ sort_price_comps ( & mut price_data. comp_ , num_comps) ?;
73
+ return Ok ( ( ) ) ;
74
+ }
75
+
66
76
if price_data. num_ >= PC_NUM_COMP {
67
77
return Err ( ProgramError :: InvalidArgument ) ;
68
78
}
@@ -81,6 +91,141 @@ pub fn add_publisher(
81
91
) ;
82
92
price_data. comp_ [ current_index] . pub_ = cmd_args. publisher ;
83
93
price_data. num_ += 1 ;
94
+
95
+ // Sort the publishers in the list
96
+ {
97
+ let num_comps = try_convert :: < u32 , usize > ( price_data. num_ ) ?;
98
+ sort_price_comps ( & mut price_data. comp_ , num_comps) ?;
99
+ }
100
+
84
101
price_data. header . size = try_convert :: < _ , u32 > ( PriceAccount :: INITIAL_SIZE ) ?;
85
102
Ok ( ( ) )
86
103
}
104
+
105
+ /// A copy of rust slice/sort.rs heapsort implementation which is small and fast. We couldn't use
106
+ /// the sort directly because it was only accessible behind a unstable feature flag at the time of
107
+ /// writing this code.
108
+ fn heapsort ( v : & mut [ ( Pubkey , usize ) ] ) {
109
+ // This binary heap respects the invariant `parent >= child`.
110
+ let sift_down = |v : & mut [ ( Pubkey , usize ) ] , mut node : usize | {
111
+ loop {
112
+ // Children of `node`.
113
+ let mut child = 2 * node + 1 ;
114
+ if child >= v. len ( ) {
115
+ break ;
116
+ }
117
+
118
+ // Choose the greater child.
119
+ if child + 1 < v. len ( )
120
+ && sol_memcmp ( v[ child] . 0 . as_ref ( ) , v[ child + 1 ] . 0 . as_ref ( ) , 32 ) < 0
121
+ {
122
+ child += 1 ;
123
+ }
124
+
125
+ // Stop if the invariant holds at `node`.
126
+ if sol_memcmp ( v[ node] . 0 . as_ref ( ) , v[ child] . 0 . as_ref ( ) , 32 ) >= 0 {
127
+ break ;
128
+ }
129
+
130
+ // Swap `node` with the greater child, move one step down, and continue sifting.
131
+ v. swap ( node, child) ;
132
+ node = child;
133
+ }
134
+ } ;
135
+
136
+ // Build the heap in linear time.
137
+ for i in ( 0 ..v. len ( ) / 2 ) . rev ( ) {
138
+ sift_down ( v, i) ;
139
+ }
140
+
141
+ // Pop maximal elements from the heap.
142
+ for i in ( 1 ..v. len ( ) ) . rev ( ) {
143
+ v. swap ( 0 , i) ;
144
+ sift_down ( & mut v[ ..i] , 0 ) ;
145
+ }
146
+ }
147
+
148
+ /// Sort the publishers price component list in place by performing minimal swaps.
149
+ /// This code is inspired by the sort_by_cached_key implementation in the Rust stdlib.
150
+ /// The rust stdlib implementation is not used because it uses a fast sort variant that has
151
+ /// a large code size.
152
+ ///
153
+ /// num_publishers is the number of publishers in the list that should be sorted. It is explicitly
154
+ /// passed to avoid callers mistake of passing the full slice which may contain uninitialized values.
155
+ fn sort_price_comps ( comps : & mut [ PriceComponent ] , num_comps : usize ) -> Result < ( ) , ProgramError > {
156
+ let comps = comps
157
+ . get_mut ( ..num_comps)
158
+ . ok_or ( ProgramError :: InvalidArgument ) ?;
159
+
160
+ // Publishers are likely sorted in ascending order but
161
+ // heapsorts creates a max-heap so we reverse the order
162
+ // of the keys to make the heapify step faster.
163
+ let mut keys = comps
164
+ . iter ( )
165
+ . enumerate ( )
166
+ . map ( |( i, x) | ( x. pub_ , i) )
167
+ . rev ( )
168
+ . collect :: < Vec < _ > > ( ) ;
169
+
170
+ heapsort ( & mut keys) ;
171
+
172
+ for i in 0 ..num_comps {
173
+ // We know that the publisher with key[i].0 should be at index i in the sorted array and
174
+ // want to swap them in-place in O(n). Normally, the publisher at key[i].0 should be at
175
+ // key[i].1 but if it is swapped, we need to find the correct index by following the chain
176
+ // of swaps.
177
+ let mut index = keys[ i] . 1 ;
178
+
179
+ while index < i {
180
+ index = keys[ index] . 1 ;
181
+ }
182
+ // Setting the final index here is important to make the code linear as we won't
183
+ // loop over from i to index again when we reach i again.
184
+ keys[ i] . 1 = index;
185
+ comps. swap ( i, index) ;
186
+ }
187
+
188
+ Ok ( ( ) )
189
+ }
190
+
191
+ #[ cfg( test) ]
192
+ mod test {
193
+ use {
194
+ super :: * ,
195
+ quickcheck_macros:: quickcheck,
196
+ } ;
197
+
198
+ #[ quickcheck]
199
+ pub fn test_sort_price_comps ( mut comps : Vec < PriceComponent > ) {
200
+ let mut rust_std_sorted_comps = comps. clone ( ) ;
201
+ rust_std_sorted_comps. sort_by_key ( |x| x. pub_ ) ;
202
+
203
+ let num_comps = comps. len ( ) ;
204
+ assert_eq ! (
205
+ sort_price_comps( & mut comps, num_comps + 1 ) ,
206
+ Err ( ProgramError :: InvalidArgument )
207
+ ) ;
208
+
209
+ assert_eq ! ( sort_price_comps( & mut comps, num_comps) , Ok ( ( ) ) ) ;
210
+ assert_eq ! ( comps, rust_std_sorted_comps) ;
211
+ }
212
+
213
+ #[ quickcheck]
214
+ pub fn test_sort_price_comps_smaller_slice (
215
+ mut comps : Vec < PriceComponent > ,
216
+ mut num_comps : usize ,
217
+ ) {
218
+ num_comps = if comps. is_empty ( ) {
219
+ 0
220
+ } else {
221
+ num_comps % comps. len ( )
222
+ } ;
223
+
224
+ let mut rust_std_sorted_comps = comps. get ( ..num_comps) . unwrap ( ) . to_vec ( ) ;
225
+ rust_std_sorted_comps. sort_by_key ( |x| x. pub_ ) ;
226
+
227
+
228
+ assert_eq ! ( sort_price_comps( & mut comps, num_comps) , Ok ( ( ) ) ) ;
229
+ assert_eq ! ( comps. get( ..num_comps) . unwrap( ) , rust_std_sorted_comps) ;
230
+ }
231
+ }
0 commit comments