-
Notifications
You must be signed in to change notification settings - Fork 117
refactor: use ordered publisher list #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,10 @@ use { | |
| account_info::AccountInfo, | ||
| entrypoint::ProgramResult, | ||
| program_error::ProgramError, | ||
| program_memory::sol_memset, | ||
| program_memory::{ | ||
| sol_memcmp, | ||
| sol_memset, | ||
| }, | ||
| pubkey::Pubkey, | ||
| }, | ||
| std::mem::size_of, | ||
|
|
@@ -41,8 +44,7 @@ pub fn add_publisher( | |
| let cmd_args = load::<AddPublisherArgs>(instruction_data)?; | ||
|
|
||
| pyth_assert( | ||
| instruction_data.len() == size_of::<AddPublisherArgs>() | ||
| && cmd_args.publisher != Pubkey::default(), | ||
| instruction_data.len() == size_of::<AddPublisherArgs>(), | ||
| ProgramError::InvalidArgument, | ||
| )?; | ||
|
|
||
|
|
@@ -63,6 +65,14 @@ pub fn add_publisher( | |
|
|
||
| let mut price_data = load_checked::<PriceAccount>(price_account, cmd_args.header.version)?; | ||
|
|
||
| // Use the call with the default pubkey (000..) as a trigger to sort the publishers as a | ||
| // migration step from unsorted list to sorted list. | ||
| if cmd_args.publisher == Pubkey::default() { | ||
| let num_comps = try_convert::<u32, usize>(price_data.num_)?; | ||
| sort_price_comps(&mut price_data.comp_, num_comps)?; | ||
| return Ok(()); | ||
| } | ||
|
|
||
| if price_data.num_ >= PC_NUM_COMP { | ||
| return Err(ProgramError::InvalidArgument); | ||
| } | ||
|
|
@@ -73,14 +83,149 @@ pub fn add_publisher( | |
| } | ||
| } | ||
|
|
||
| let current_index: usize = try_convert(price_data.num_)?; | ||
| let mut current_index: usize = try_convert(price_data.num_)?; | ||
| sol_memset( | ||
| bytes_of_mut(&mut price_data.comp_[current_index]), | ||
| 0, | ||
| size_of::<PriceComponent>(), | ||
| ); | ||
| price_data.comp_[current_index].pub_ = cmd_args.publisher; | ||
|
|
||
| // Shift the element back to keep the publishers components sorted. | ||
| while current_index > 0 | ||
| && price_data.comp_[current_index].pub_ < price_data.comp_[current_index - 1].pub_ | ||
| { | ||
| price_data.comp_.swap(current_index, current_index - 1); | ||
| current_index -= 1; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor comment: imo, we should call
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (feel free to ignore this. I'm only saying it because it feels like the maximally defensive implementation)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. did that. removed the |
||
|
|
||
| price_data.num_ += 1; | ||
| price_data.header.size = try_convert::<_, u32>(PriceAccount::INITIAL_SIZE)?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// A copy of rust slice/sort.rs heapsort implementation which is small and fast. We couldn't use | ||
| /// the sort directly because it was only accessible behind a unstable feature flag at the time of | ||
| /// writing this code. | ||
| #[inline(always)] | ||
| fn heapsort(v: &mut [(Pubkey, usize)]) { | ||
| // This binary heap respects the invariant `parent >= child`. | ||
| let sift_down = |v: &mut [(Pubkey, usize)], mut node: usize| { | ||
| loop { | ||
| // Children of `node`. | ||
| let mut child = 2 * node + 1; | ||
| if child >= v.len() { | ||
| break; | ||
| } | ||
|
|
||
| // Choose the greater child. | ||
| if child + 1 < v.len() | ||
| && sol_memcmp(v[child].0.as_ref(), v[child + 1].0.as_ref(), 32) < 0 | ||
| { | ||
| child += 1; | ||
| } | ||
|
|
||
| // Stop if the invariant holds at `node`. | ||
| if sol_memcmp(v[node].0.as_ref(), v[child].0.as_ref(), 32) >= 0 { | ||
| break; | ||
| } | ||
|
|
||
| // Swap `node` with the greater child, move one step down, and continue sifting. | ||
| v.swap(node, child); | ||
| node = child; | ||
| } | ||
| }; | ||
|
|
||
| // Build the heap in linear time. | ||
| for i in (0..v.len() / 2).rev() { | ||
| sift_down(v, i); | ||
| } | ||
|
|
||
| // Pop maximal elements from the heap. | ||
| for i in (1..v.len()).rev() { | ||
| v.swap(0, i); | ||
| sift_down(&mut v[..i], 0); | ||
| } | ||
| } | ||
|
|
||
| /// Sort the publishers price component list in place by performing minimal swaps. | ||
| /// This code is inspired by the sort_by_cached_key implementation in the Rust stdlib. | ||
| /// The rust stdlib implementation is not used because it uses a fast sort variant that has | ||
| /// a large code size. | ||
| /// | ||
| /// num_publishers is the number of publishers in the list that should be sorted. It is explicitly | ||
| /// passed to avoid callers mistake of passing the full slice which may contain uninitialized values. | ||
| #[inline(always)] | ||
| fn sort_price_comps(comps: &mut [PriceComponent], num_comps: usize) -> Result<(), ProgramError> { | ||
| let comps = comps | ||
| .get_mut(..num_comps) | ||
| .ok_or(ProgramError::InvalidArgument)?; | ||
|
|
||
| let mut keys = comps | ||
| .iter() | ||
| .enumerate() | ||
| .map(|(i, x)| (x.pub_, i)) | ||
| .collect::<Vec<_>>(); | ||
|
|
||
| heapsort(&mut keys); | ||
|
|
||
| for i in 0..num_comps { | ||
| // We know that the publisher with key[i].0 should be at index i in the sorted array and | ||
| // want to swap them in-place in O(n). Normally, the publisher at key[i].0 should be at | ||
| // key[i].1 but if it is swapped, we need to find the correct index by following the chain | ||
| // of swaps. | ||
| let mut index = keys[i].1; | ||
|
|
||
| while index < i { | ||
| index = keys[index].1; | ||
| } | ||
| // Setting the final index here is important to make the code linear as we won't | ||
| // loop over from i to index again when we reach i again. | ||
| keys[i].1 = index; | ||
| comps.swap(i, index); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use { | ||
| super::*, | ||
| quickcheck_macros::quickcheck, | ||
| }; | ||
|
|
||
| #[quickcheck] | ||
| pub fn test_sort_price_comps(mut comps: Vec<PriceComponent>) { | ||
| let mut rust_std_sorted_comps = comps.clone(); | ||
| rust_std_sorted_comps.sort_by_key(|x| x.pub_); | ||
|
|
||
| let num_comps = comps.len(); | ||
| assert_eq!( | ||
| sort_price_comps(&mut comps, num_comps + 1), | ||
| Err(ProgramError::InvalidArgument) | ||
| ); | ||
|
|
||
| assert_eq!(sort_price_comps(&mut comps, num_comps), Ok(())); | ||
| assert_eq!(comps, rust_std_sorted_comps); | ||
| } | ||
|
|
||
| #[quickcheck] | ||
| pub fn test_sort_price_comps_smaller_slice( | ||
| mut comps: Vec<PriceComponent>, | ||
| mut num_comps: usize, | ||
| ) { | ||
| num_comps = if comps.is_empty() { | ||
| 0 | ||
| } else { | ||
| num_comps % comps.len() | ||
| }; | ||
|
|
||
| let mut rust_std_sorted_comps = comps.get(..num_comps).unwrap().to_vec(); | ||
| rust_std_sorted_comps.sort_by_key(|x| x.pub_); | ||
|
|
||
|
|
||
| assert_eq!(sort_price_comps(&mut comps, num_comps), Ok(())); | ||
| assert_eq!(comps.get(..num_comps).unwrap(), rust_std_sorted_comps); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.