Skip to content

Commit fdafbe3

Browse files
author
Stanisław Drozd
authored
Drozdziak1/p2w client use mapping crawl (#294)
* p2w-client: Add attestation config merging logic This change adds code to append new symbols to an existing attestation config. In conjunction with the mapping crawling logic, this will aid in hot reloading attestation configs in runtime * pyth2wormhole-client: Use the crawled mapping symbols
1 parent 4411171 commit fdafbe3

File tree

4 files changed

+128
-9
lines changed

4 files changed

+128
-9
lines changed

solana/pyth2wormhole/client/src/attestation_cfg.rs

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
use std::{
2-
collections::HashMap,
2+
collections::{
3+
HashMap,
4+
HashSet,
5+
},
6+
iter,
37
str::FromStr,
48
};
59

@@ -13,7 +17,7 @@ use serde::{
1317
use solana_program::pubkey::Pubkey;
1418

1519
/// Pyth2wormhole config specific to attestation requests
16-
#[derive(Debug, Deserialize, Serialize, PartialEq)]
20+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
1721
pub struct AttestationConfig {
1822
#[serde(default = "default_min_msg_reuse_interval_ms")]
1923
pub min_msg_reuse_interval_ms: u64,
@@ -28,7 +32,61 @@ pub struct AttestationConfig {
2832
pub symbol_groups: Vec<SymbolGroup>,
2933
}
3034

31-
#[derive(Debug, Deserialize, Serialize, PartialEq)]
35+
impl AttestationConfig {
36+
/// Merges new symbols into the attestation config. Pre-existing
37+
/// new symbols are ignored. The new_group_name group can already
38+
/// exist - symbols will be appended to `symbols` field.
39+
pub fn add_symbols(
40+
&mut self,
41+
mut new_symbols: HashMap<Pubkey, HashSet<Pubkey>>,
42+
group_name: String, // Which group is extended by the new symbols
43+
) {
44+
// Remove pre-existing symbols from the new symbols collection
45+
for existing_group in &self.symbol_groups {
46+
for existing_sym in &existing_group.symbols {
47+
// Check if new symbols mention this product
48+
if let Some(mut prices) = new_symbols.get_mut(&existing_sym.product_addr) {
49+
// Prune the price if exists
50+
prices.remove(&existing_sym.price_addr);
51+
}
52+
}
53+
}
54+
55+
// Turn the pruned symbols into P2WSymbol structs
56+
let mut new_symbols_vec = new_symbols
57+
.drain() // Makes us own the elements and lets us move them
58+
.map(|(prod, prices)| iter::zip(iter::repeat(prod), prices)) // Convert to iterator over flat (prod, price) tuples
59+
.flatten() // Flatten the tuple iterators
60+
.map(|(prod, price)| P2WSymbol {
61+
name: None,
62+
product_addr: prod,
63+
price_addr: price,
64+
})
65+
.collect::<Vec<P2WSymbol>>();
66+
67+
// Find and extend OR create the group of specified name
68+
match self
69+
.symbol_groups
70+
.iter_mut()
71+
.find(|g| g.group_name == group_name) // Advances the iterator and returns Some(item) on first hit
72+
{
73+
Some(mut existing_group) => existing_group.symbols.append(&mut new_symbols_vec),
74+
None if new_symbols_vec.len() != 0 => {
75+
// Group does not exist, assume defaults
76+
let new_group = SymbolGroup {
77+
group_name,
78+
conditions: Default::default(),
79+
symbols: new_symbols_vec,
80+
};
81+
82+
self.symbol_groups.push(new_group);
83+
}
84+
None => {}
85+
}
86+
}
87+
}
88+
89+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
3290
pub struct SymbolGroup {
3391
pub group_name: String,
3492
/// Attestation conditions applied to all symbols in this group
@@ -56,7 +114,7 @@ pub const fn default_max_batch_jobs() -> usize {
56114
/// of the active conditions is met. Option<> fields can be
57115
/// de-activated with None. All conditions are inactive by default,
58116
/// except for the non-Option ones.
59-
#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq)]
117+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
60118
pub struct AttestationConditions {
61119
/// Baseline, unconditional attestation interval. Attestation is triggered if the specified interval elapsed since last attestation.
62120
#[serde(default = "default_min_interval_secs")]
@@ -78,6 +136,17 @@ pub struct AttestationConditions {
78136
pub publish_time_min_delta_secs: Option<u64>,
79137
}
80138

139+
impl Default for AttestationConditions {
140+
fn default() -> Self {
141+
Self {
142+
min_interval_secs: default_min_interval_secs(),
143+
max_batch_jobs: default_max_batch_jobs(),
144+
price_changed_pct: None,
145+
publish_time_min_delta_secs: None,
146+
}
147+
}
148+
}
149+
81150
/// Config entry for a Pyth product + price pair
82151
#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
83152
pub struct P2WSymbol {
@@ -200,4 +269,45 @@ mod tests {
200269

201270
Ok(())
202271
}
272+
273+
#[test]
274+
fn test_add_symbols_works() -> Result<(), ErrBox> {
275+
let empty_config = AttestationConfig {
276+
min_msg_reuse_interval_ms: 1000,
277+
max_msg_accounts: 100,
278+
mapping_addr: None,
279+
symbol_groups: vec![],
280+
};
281+
282+
let mock_new_symbols = (0..255)
283+
.map(|sym_idx| {
284+
let mut mock_prod_bytes = [0u8; 32];
285+
mock_prod_bytes[31] = sym_idx;
286+
287+
let mut mock_prices = HashSet::new();
288+
for px_idx in 1..=5 {
289+
let mut mock_price_bytes = [0u8; 32];
290+
mock_price_bytes[31] = sym_idx;
291+
mock_prices.insert(Pubkey::new_from_array(mock_price_bytes));
292+
}
293+
294+
(Pubkey::new_from_array(mock_prod_bytes), mock_prices)
295+
})
296+
.collect::<HashMap<Pubkey, HashSet<Pubkey>>>();
297+
298+
let mut config1 = empty_config.clone();
299+
300+
config1.add_symbols(mock_new_symbols.clone(), "default".to_owned());
301+
302+
let mut config2 = config1.clone();
303+
304+
// Should not be created because there's no new symbols to add
305+
// (we're adding identical mock_new_symbols again)
306+
config2.add_symbols(mock_new_symbols.clone(), "default2".to_owned());
307+
308+
assert_ne!(config1, empty_config); // Check that config grows from empty
309+
assert_eq!(config1, config2); // Check that no changes are made if all symbols are already in there
310+
311+
Ok(())
312+
}
203313
}

solana/pyth2wormhole/client/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,13 @@ async fn main() -> Result<(), ErrBox> {
173173
daemon,
174174
} => {
175175
// Load the attestation config yaml
176-
let attestation_cfg: AttestationConfig =
176+
let mut attestation_cfg: AttestationConfig =
177177
serde_yaml::from_reader(File::open(attestation_cfg)?)?;
178178

179179
if let Some(mapping_addr) = attestation_cfg.mapping_addr.as_ref() {
180180
let additional_accounts = crawl_pyth_mapping(&rpc_client, mapping_addr).await?;
181181
info!("Additional mapping accounts:\n{:#?}", additional_accounts);
182+
attestation_cfg.add_symbols(additional_accounts, "mapping".to_owned());
182183
}
183184

184185
handle_attest(

solana/pyth2wormhole/program/src/message.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct P2WMessageDrvData {
2929
/// The key owning this message account
3030
pub message_owner: Pubkey,
3131
/// Size of the batch. It is important that all messages have the same size
32-
///
32+
///
3333
/// NOTE: 2022-09-05
3434
/// Currently wormhole does not resize accounts if they have different
3535
/// payload sizes; this (along with versioning the seed literal below) is

third_party/pyth/p2w_autoattest.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,9 @@ def find_and_log_seqnos(s):
198198
# integer-divide the symbols in ~half for two test
199199
# groups. Assumes arr[:idx] is exclusive, and arr[idx:] is
200200
# inclusive
201-
half_len = len(pyth_accounts) // 2;
201+
third_len = len(pyth_accounts) // 3;
202202

203-
for thing in pyth_accounts[:half_len]:
203+
for thing in pyth_accounts[:third_len]:
204204
name = thing["name"]
205205
price = thing["price"]
206206
product = thing["product"]
@@ -218,7 +218,7 @@ def find_and_log_seqnos(s):
218218
symbols:
219219
"""
220220

221-
for stuff in pyth_accounts[half_len:]:
221+
for stuff in pyth_accounts[third_len:-third_len]:
222222
name = stuff["name"]
223223
price = stuff["price"]
224224
product = stuff["product"]
@@ -228,6 +228,14 @@ def find_and_log_seqnos(s):
228228
price_addr: {price}
229229
product_addr: {product}"""
230230

231+
cfg_yaml += f"""
232+
- group_name: mapping
233+
conditions:
234+
min_interval_secs: 30
235+
price_changed_pct: 5
236+
symbols: []
237+
"""
238+
231239
with open(P2W_ATTESTATION_CFG, "w") as f:
232240
f.write(cfg_yaml)
233241
f.flush()

0 commit comments

Comments
 (0)