Skip to content

Commit a278681

Browse files
RiatecheReisen
andauthored
Support aggregation in validator (#410)
* feat(program): match version with pythnet * feat: validator access point (wip) * feat: add price account flags * feat(program): fill out validator aggregation code * feat: don't aggregate on v2, clear message buffer, report validator aggregation errors * feat: return price feed messages from validator aggregation * test: enable v2 aggregation * test: test aggregation toggle * fix: don't aggregate in validator if already aggregated in this slot * chore: reexport solana_program and add derive debug * chore: add comments * fix: don't revert if c_upd_aggregate returns false * fix: make update_price_cumulative infallible; don't generate v2 messages until v1 buffer is cleared * chore: remove publisher sorting hack * chore: remove AggregationOutcome * fix: remove debug_assert * fix: allow clearing message buffers regardless of message_sent_ flag * test: add test_upd_price_with_validator * chore: add missing imports and fix warnings * test: verify aggregation output * chore: bump version --------- Co-authored-by: Reisen <[email protected]>
1 parent f0e444d commit a278681

15 files changed

+1146
-315
lines changed

Cargo.lock

+228-163
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

program/rust/Cargo.toml

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-oracle"
3-
version = "2.26.0"
3+
version = "2.32.0"
44
edition = "2021"
55
license = "Apache 2.0"
66
publish = false
@@ -9,7 +9,7 @@ publish = false
99
bindgen = "0.60.1"
1010

1111
[dependencies]
12-
solana-program = "=1.13.3"
12+
solana-program = "=1.14.17"
1313
bytemuck = "1.11.0"
1414
thiserror = "1.0"
1515
num-derive = "0.3"
@@ -18,10 +18,12 @@ byteorder = "1.4.3"
1818
serde = { version = "1.0", features = ["derive"], optional = true }
1919
strum = { version = "0.24.1", features = ["derive"], optional = true }
2020
pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", rev="60144002053a93f424be70decd8a8ccb8d618d81"}
21+
solana-sdk = { version = "=1.14.17", optional = true }
22+
bitflags = { version = "2.6.0", features = ["bytemuck"] }
2123

2224
[dev-dependencies]
23-
solana-program-test = "=1.13.3"
24-
solana-sdk = "=1.13.3"
25+
solana-program-test = "=1.14.17"
26+
solana-sdk = "=1.14.17"
2527
tokio = "1.14.1"
2628
hex = "0.3.1"
2729
quickcheck = "1"
@@ -34,10 +36,15 @@ serde_json = "1.0"
3436
test-generator = "0.3.1"
3537
csv = "1.1"
3638

39+
# Downgrade to be compatible with Rust 1.60
40+
tracing-subscriber = "=0.3.0"
41+
time-macros = "=0.2.3"
42+
time = "=0.3.7"
43+
3744
[features]
3845
check = [] # Skips make build in build.rs, use with cargo-clippy and cargo-check
3946
debug = []
40-
library = []
47+
library = ["solana-sdk"]
4148

4249
[lib]
4350
crate-type = ["cdylib", "lib"]

program/rust/src/accounts.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ use {
3434
std::borrow::BorrowMut,
3535
};
3636

37-
3837
mod mapping;
3938
mod permission;
4039
mod price;
@@ -44,6 +43,8 @@ mod product;
4443
#[cfg(feature = "strum")]
4544
pub use price::MessageType;
4645
#[cfg(test)]
46+
pub use price::PriceCumulative;
47+
#[cfg(test)]
4748
pub use product::{
4849
account_has_key_values,
4950
create_pc_str_t,
@@ -53,14 +54,13 @@ pub use {
5354
permission::PermissionAccount,
5455
price::{
5556
PriceAccount,
57+
PriceAccountFlags,
5658
PriceComponent,
57-
PriceCumulative,
5859
PriceEma,
5960
PriceInfo,
6061
PythOracleSerialize,
6162
},
6263
product::{
63-
read_pc_str_t,
6464
update_product_metadata,
6565
ProductAccount,
6666
},

program/rust/src/accounts/price.rs

+20-12
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,12 @@ mod price_pythnet {
2525

2626
use {
2727
super::*,
28-
crate::{
29-
c_oracle_header::{
30-
PC_MAX_SEND_LATENCY,
31-
PC_NUM_COMP_PYTHNET,
32-
PC_STATUS_TRADING,
33-
},
34-
error::OracleError,
28+
crate::c_oracle_header::{
29+
PC_MAX_SEND_LATENCY,
30+
PC_NUM_COMP_PYTHNET,
31+
PC_STATUS_TRADING,
3532
},
33+
bitflags::bitflags,
3634
};
3735

3836
/// Pythnet-only extended price account format. This extension is
@@ -65,8 +63,9 @@ mod price_pythnet {
6563
pub message_sent_: u8,
6664
/// Configurable max latency in slots between send and receive
6765
pub max_latency_: u8,
66+
/// Various flags
67+
pub flags: PriceAccountFlags,
6868
/// Unused placeholder for alignment
69-
pub unused_2_: i8,
7069
pub unused_3_: i32,
7170
/// Corresponding product account
7271
pub product_account: Pubkey,
@@ -91,6 +90,18 @@ mod price_pythnet {
9190
pub price_cumulative: PriceCumulative,
9291
}
9392

93+
bitflags! {
94+
#[repr(C)]
95+
#[derive(Copy, Clone, Pod, Zeroable)]
96+
pub struct PriceAccountFlags: u8 {
97+
/// If set, the program doesn't do accumulation, but validator does.
98+
const ACCUMULATOR_V2 = 0b1;
99+
/// If unset, the program will remove old messages from its message buffer account
100+
/// and set this flag.
101+
const MESSAGE_BUFFER_CLEARED = 0b10;
102+
}
103+
}
104+
94105
impl PriceAccountPythnet {
95106
pub fn as_price_feed_message(&self, key: &Pubkey) -> PriceFeedMessage {
96107
let (price, conf, publish_time) = if self.agg_.status_ == PC_STATUS_TRADING {
@@ -111,17 +122,14 @@ mod price_pythnet {
111122
}
112123
}
113124
/// This function gets triggered when there's a succesful aggregation and updates the cumulative sums
114-
pub fn update_price_cumulative(&mut self) -> Result<(), OracleError> {
125+
pub fn update_price_cumulative(&mut self) {
115126
if self.agg_.status_ == PC_STATUS_TRADING {
116127
self.price_cumulative.update(
117128
self.agg_.price_,
118129
self.agg_.conf_,
119130
self.agg_.pub_slot_.saturating_sub(self.prev_slot_),
120131
self.max_latency_,
121132
); // pub_slot should always be >= prev_slot, but we protect ourselves against underflow just in case
122-
Ok(())
123-
} else {
124-
Err(OracleError::NeedsSuccesfulAggregation)
125133
}
126134
}
127135

program/rust/src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ mod instruction;
1010
mod processor;
1111
mod utils;
1212

13+
#[cfg(any(test, feature = "library"))]
14+
pub mod validator;
15+
16+
#[cfg(feature = "library")]
17+
pub use solana_program;
18+
1319
#[cfg(test)]
1420
mod tests;
1521

@@ -31,11 +37,13 @@ pub use accounts::{
3137
MappingAccount,
3238
PermissionAccount,
3339
PriceAccount,
40+
PriceAccountFlags,
3441
PriceComponent,
3542
PriceEma,
3643
PriceInfo,
3744
ProductAccount,
3845
PythAccount,
46+
PythOracleSerialize,
3947
};
4048
use {
4149
crate::error::OracleError,

program/rust/src/processor.rs

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ mod upd_permissions;
2727
mod upd_price;
2828
mod upd_product;
2929

30+
#[cfg(test)]
31+
pub use add_publisher::{
32+
DISABLE_ACCUMULATOR_V2,
33+
ENABLE_ACCUMULATOR_V2,
34+
};
3035
pub use {
3136
add_price::add_price,
3237
add_product::add_product,

program/rust/src/processor/add_publisher.rs

+19-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use {
22
crate::{
33
accounts::{
44
PriceAccount,
5+
PriceAccountFlags,
56
PriceComponent,
67
PythAccount,
78
},
@@ -33,6 +34,13 @@ use {
3334
std::mem::size_of,
3435
};
3536

37+
pub const ENABLE_ACCUMULATOR_V2: [u8; 32] = [
38+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
39+
];
40+
pub const DISABLE_ACCUMULATOR_V2: [u8; 32] = [
41+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
42+
];
43+
3644
/// Add publisher to symbol account
3745
// account[0] funding account [signer writable]
3846
// account[1] price account [signer writable]
@@ -62,14 +70,19 @@ pub fn add_publisher(
6270
&cmd_args.header,
6371
)?;
6472

65-
6673
let mut price_data = load_checked::<PriceAccount>(price_account, cmd_args.header.version)?;
6774

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)?;
75+
if cmd_args.publisher == Pubkey::from(ENABLE_ACCUMULATOR_V2) {
76+
// Hack: we use add_publisher instruction to configure the `ACCUMULATOR_V2` flag. Using a new
77+
// instruction would be cleaner but it would require more work in the tooling.
78+
// These special cases can be removed along with the v1 aggregation code once the transition
79+
// is complete.
80+
price_data.flags.insert(PriceAccountFlags::ACCUMULATOR_V2);
81+
return Ok(());
82+
} else if cmd_args.publisher == Pubkey::from(DISABLE_ACCUMULATOR_V2) {
83+
price_data
84+
.flags
85+
.remove(PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED);
7386
return Ok(());
7487
}
7588

@@ -224,7 +237,6 @@ mod test {
224237
let mut rust_std_sorted_comps = comps.get(..num_comps).unwrap().to_vec();
225238
rust_std_sorted_comps.sort_by_key(|x| x.pub_);
226239

227-
228240
assert_eq!(sort_price_comps(&mut comps, num_comps), Ok(()));
229241
assert_eq!(comps.get(..num_comps).unwrap(), rust_std_sorted_comps);
230242
}

0 commit comments

Comments
 (0)