Skip to content

Commit 0fb5ef4

Browse files
committed
feat: batch publish
1 parent 4b32928 commit 0fb5ef4

File tree

7 files changed

+485
-17
lines changed

7 files changed

+485
-17
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

runtime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ num-traits = { version = "0.2" }
3535
num_cpus = "1.13.1"
3636
once_cell = "1.12.0"
3737
ouroboros = "0.15.0"
38-
pyth-oracle = { git = "https://github.com/pyth-network/pyth-client", tag = "oracle-v2.33.0", features = ["library"] }
38+
pyth-oracle = { git = "https://github.com/pyth-network/pyth-client", branch = "batch-publish", features = ["library"] }
3939
pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", version = "1.13.6", rev = "33f901aa45f4f0005aa5a84a1479b78ca9033074" }
4040
rand = "0.7.0"
4141
rayon = "1.5.3"

runtime/src/bank.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ mod sysvar_cache;
199199
mod transaction_account_state_info;
200200

201201
pub mod pyth_accumulator;
202+
mod pyth_batch_publish;
202203

203204
#[cfg(test)]
204205
mod pyth_accumulator_tests;

runtime/src/bank/pyth_accumulator.rs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use {
2-
super::Bank,
2+
super::{pyth_batch_publish, Bank},
33
crate::accounts_index::{IndexKey, ScanConfig, ScanError},
44
byteorder::{LittleEndian, ReadBytesExt},
55
log::*,
@@ -17,7 +17,10 @@ use {
1717
hash::hashv,
1818
pubkey::Pubkey,
1919
},
20-
std::env::{self, VarError},
20+
std::{
21+
collections::HashMap,
22+
env::{self, VarError},
23+
},
2124
};
2225

2326
pub const ACCUMULATOR_RING_SIZE: u32 = 10_000;
@@ -51,6 +54,13 @@ lazy_static! {
5154
.parse()
5255
.unwrap(),
5356
);
57+
pub static ref BATCH_PUBLISH_PID: Pubkey = env_pubkey_or(
58+
"BATCH_PUBLISH_PID",
59+
// TODO: replace with real program id
60+
"FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epA"
61+
.parse()
62+
.unwrap(),
63+
);
5464
}
5565

5666
/// Accumulator specific error type. It would be nice to use `transaction::Error` but it does
@@ -132,6 +142,7 @@ pub fn get_accumulator_keys() -> Vec<(
132142
"STAKE_CAPS_PARAMETERS_ADDR",
133143
Ok(*STAKE_CAPS_PARAMETERS_ADDR),
134144
),
145+
("BATCH_PUBLISH_PID", Ok(*BATCH_PUBLISH_PID)),
135146
]
136147
}
137148

@@ -427,21 +438,34 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV
427438
v2_messages.push(publisher_stake_caps_message);
428439
}
429440

430-
let mut measure = Measure::start("update_v2_aggregate_price");
441+
let new_prices = pyth_batch_publish::extract_batch_publish_prices(bank).unwrap_or_else(|err| {
442+
warn!("extract_batch_publish_prices failed: {}", err);
443+
HashMap::new()
444+
});
431445

446+
let mut measure = Measure::start("update_v2_aggregate_price");
432447
for (pubkey, mut account) in accounts {
433448
let mut price_account_data = account.data().to_owned();
449+
let price_account = if let Ok(data) =
450+
pyth_oracle::validator::validate_price_account(&mut price_account_data)
451+
{
452+
data
453+
} else {
454+
continue; // Not a price account.
455+
};
456+
457+
let mut need_save =
458+
pyth_batch_publish::apply_published_prices(price_account, &new_prices, bank.slot());
434459

435460
// Perform Accumulation
436461
match pyth_oracle::validator::aggregate_price(
437462
bank.slot(),
438463
bank.clock().unix_timestamp,
439464
&pubkey.to_bytes().into(),
440-
&mut price_account_data,
465+
price_account,
441466
) {
442467
Ok(messages) => {
443-
account.set_data(price_account_data);
444-
bank.store_account_and_update_capitalization(&pubkey, &account);
468+
need_save = true;
445469
v2_messages.extend(messages);
446470
}
447471
Err(err) => match err {
@@ -451,6 +475,10 @@ pub fn update_v2(bank: &Bank) -> std::result::Result<(), AccumulatorUpdateErrorV
451475
}
452476
},
453477
}
478+
if need_save {
479+
account.set_data(price_account_data);
480+
bank.store_account_and_update_capitalization(&pubkey, &account);
481+
}
454482
}
455483

456484
measure.stop();

runtime/src/bank/pyth_accumulator_tests.rs

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ use {
77
},
88
bank::{
99
pyth_accumulator::{
10-
get_accumulator_keys, ACCUMULATOR_RING_SIZE, ORACLE_PID, STAKE_CAPS_PARAMETERS_ADDR,
10+
get_accumulator_keys, ACCUMULATOR_RING_SIZE, BATCH_PUBLISH_PID, ORACLE_PID,
11+
STAKE_CAPS_PARAMETERS_ADDR,
1112
},
13+
pyth_batch_publish::publisher_prices_account::{self, PublisherPrice},
1214
Bank,
1315
},
1416
genesis_utils::{create_genesis_config_with_leader, GenesisConfigInfo},
1517
},
18+
bytemuck::{cast_slice, checked::from_bytes},
1619
byteorder::{ByteOrder, LittleEndian, ReadBytesExt},
1720
itertools::Itertools,
1821
pyth_oracle::{
@@ -48,7 +51,9 @@ fn create_new_bank_for_tests_with_index(genesis_config: &GenesisConfig) -> Bank
4851
AccountSecondaryIndexes {
4952
keys: Some(AccountSecondaryIndexesIncludeExclude {
5053
exclude: false,
51-
keys: [*ORACLE_PID, *MESSAGE_BUFFER_PID].into_iter().collect(),
54+
keys: [*ORACLE_PID, *MESSAGE_BUFFER_PID, *BATCH_PUBLISH_PID]
55+
.into_iter()
56+
.collect(),
5257
}),
5358
indexes: [AccountIndex::ProgramId].into_iter().collect(),
5459
},
@@ -1005,8 +1010,6 @@ fn test_publisher_stake_caps() {
10051010
],
10061011
),
10071012
generate_price(&bank, b"seeds_5", false, &[]),
1008-
1009-
10101013
];
10111014

10121015
// Publishers are sorted in the publisher stake caps message so we sort them here too
@@ -1072,9 +1075,7 @@ fn test_publisher_stake_caps() {
10721075
// We add some badly formatted stake cap parameters
10731076
let mut stake_cap_parameters_account =
10741077
AccountSharedData::new(42, size_of::<StakeCapParameters>(), &ORACLE_PID);
1075-
stake_cap_parameters_account.set_data(
1076-
vec![1,2,3,4],
1077-
);
1078+
stake_cap_parameters_account.set_data(vec![1, 2, 3, 4]);
10781079
bank.store_account(&STAKE_CAPS_PARAMETERS_ADDR, &stake_cap_parameters_account);
10791080

10801081
// Nothing should change as the stake cap parameters are invalid
@@ -1144,3 +1145,128 @@ fn test_get_accumulator_keys() {
11441145
];
11451146
assert_eq!(accumulator_keys, expected_pyth_keys);
11461147
}
1148+
1149+
#[test]
1150+
fn test_batch_publish() {
1151+
let leader_pubkey = solana_sdk::pubkey::new_rand();
1152+
let GenesisConfigInfo {
1153+
mut genesis_config, ..
1154+
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
1155+
1156+
// Set epoch length to 32 so we can advance epochs quickly. We also skip past slot 0 here
1157+
// due to slot 0 having special handling.
1158+
let slots_in_epoch = 32;
1159+
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);
1160+
let mut bank = create_new_bank_for_tests_with_index(&genesis_config);
1161+
1162+
let generate_publisher = |seed, new_prices| {
1163+
let publisher1_key = keypair_from_seed(seed).unwrap();
1164+
1165+
let (publisher1_prices_key, _bump) = Pubkey::find_program_address(
1166+
// TODO: real seed
1167+
&[
1168+
b"PUBLISHER_PRICES_ACCOUNT",
1169+
&publisher1_key.pubkey().to_bytes(),
1170+
],
1171+
&BATCH_PUBLISH_PID,
1172+
);
1173+
let mut publisher1_prices_account =
1174+
AccountSharedData::new(42, publisher_prices_account::size(100), &BATCH_PUBLISH_PID);
1175+
{
1176+
let (header, prices) = publisher_prices_account::create(
1177+
publisher1_prices_account.data_mut(),
1178+
publisher1_key.pubkey().to_bytes(),
1179+
)
1180+
.unwrap();
1181+
publisher_prices_account::extend(header, prices, cast_slice(new_prices)).unwrap();
1182+
}
1183+
bank.store_account(&publisher1_prices_key, &publisher1_prices_account);
1184+
1185+
publisher1_key
1186+
};
1187+
1188+
let publishers = [
1189+
generate_publisher(
1190+
&[1u8; 32],
1191+
&[
1192+
PublisherPrice::new(1, 1, 10, 2).unwrap(),
1193+
PublisherPrice::new(2, 1, 20, 3).unwrap(),
1194+
],
1195+
),
1196+
generate_publisher(
1197+
&[2u8; 32],
1198+
&[
1199+
PublisherPrice::new(1, 1, 15, 2).unwrap(),
1200+
PublisherPrice::new(2, 1, 25, 3).unwrap(),
1201+
],
1202+
),
1203+
];
1204+
1205+
let generate_price = |seeds, index| {
1206+
let (price_feed_key, _bump) = Pubkey::find_program_address(&[seeds], &ORACLE_PID);
1207+
let mut price_feed_account =
1208+
AccountSharedData::new(42, size_of::<PriceAccount>(), &ORACLE_PID);
1209+
1210+
let messages = {
1211+
let price_feed_info_key = &price_feed_key.to_bytes().into();
1212+
let price_feed_info_lamports = &mut 0;
1213+
let price_feed_info_owner = &ORACLE_PID.to_bytes().into();
1214+
let price_feed_info_data = price_feed_account.data_mut();
1215+
let price_feed_info = AccountInfo::new(
1216+
price_feed_info_key,
1217+
false,
1218+
true,
1219+
price_feed_info_lamports,
1220+
price_feed_info_data,
1221+
price_feed_info_owner,
1222+
false,
1223+
Epoch::default(),
1224+
);
1225+
1226+
let mut price_account = PriceAccount::initialize(&price_feed_info, 0).unwrap();
1227+
price_account.flags.insert(
1228+
PriceAccountFlags::ACCUMULATOR_V2 | PriceAccountFlags::MESSAGE_BUFFER_CLEARED,
1229+
);
1230+
price_account.unused_3_ = index;
1231+
price_account.comp_[0].pub_ = publishers[0].pubkey().to_bytes().into();
1232+
price_account.comp_[1].pub_ = publishers[1].pubkey().to_bytes().into();
1233+
price_account.num_ = 2;
1234+
};
1235+
1236+
bank.store_account(&price_feed_key, &price_feed_account);
1237+
(price_feed_key, messages)
1238+
};
1239+
1240+
assert!(bank
1241+
.feature_set
1242+
.is_active(&feature_set::enable_accumulator_sysvar::id()));
1243+
assert!(bank
1244+
.feature_set
1245+
.is_active(&feature_set::move_accumulator_to_end_of_block::id()));
1246+
assert!(bank
1247+
.feature_set
1248+
.is_active(&feature_set::undo_move_accumulator_to_end_of_block::id()));
1249+
assert!(bank
1250+
.feature_set
1251+
.is_active(&feature_set::redo_move_accumulator_to_end_of_block::id()));
1252+
1253+
let prices_with_messages = [
1254+
generate_price(b"seeds_1", 1),
1255+
generate_price(b"seeds_2", 2),
1256+
generate_price(b"seeds_3", 3),
1257+
generate_price(b"seeds_4", 4),
1258+
];
1259+
1260+
bank = new_from_parent(&Arc::new(bank)); // Advance slot 1.
1261+
bank = new_from_parent(&Arc::new(bank)); // Advance slot 2.
1262+
1263+
let new_price_feed1_account = bank.get_account(&prices_with_messages[0].0).unwrap();
1264+
let new_price_feed1_data: &PriceAccount = from_bytes(new_price_feed1_account.data());
1265+
assert_eq!(new_price_feed1_data.comp_[0].latest_.price_, 10);
1266+
assert_eq!(new_price_feed1_data.comp_[1].latest_.price_, 15);
1267+
1268+
let new_price_feed2_account = bank.get_account(&prices_with_messages[1].0).unwrap();
1269+
let new_price_feed2_data: &PriceAccount = from_bytes(new_price_feed2_account.data());
1270+
assert_eq!(new_price_feed2_data.comp_[0].latest_.price_, 20);
1271+
assert_eq!(new_price_feed2_data.comp_[1].latest_.price_, 25);
1272+
}

0 commit comments

Comments
 (0)