Skip to content

Commit 0682cc9

Browse files
committed
fix(hermes): handle rare cases on update data
Fix hermes construct_update_data to support generating update_data for more than 255 messages. This is very unlikely to happen in normal cases but the way WS <> store interact can cause this to happen when a WS client subscribes to all price feeds.
1 parent 110caed commit 0682cc9

File tree

3 files changed

+137
-33
lines changed

3 files changed

+137
-33
lines changed

hermes/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.

hermes/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hermes"
3-
version = "0.1.10"
3+
version = "0.1.11"
44
edition = "2021"
55

66
[dependencies]

hermes/src/store/proof/wormhole_merkle.rs

Lines changed: 135 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ use {
2929
},
3030
};
3131

32+
// The number of messages in a single update data is defined as a
33+
// u8 in the wire format. So, we can't have more than 255 messages.
34+
pub const MAX_MESSAGE_IN_SINGLE_UPDATE_DATA: usize = 255;
35+
3236
#[derive(Clone, PartialEq, Debug)]
3337
pub struct WormholeMerkleState {
3438
pub root: WormholeMerkleRoot,
@@ -87,39 +91,139 @@ pub fn construct_message_states_proofs(
8791
}
8892

8993
pub fn construct_update_data(mut message_states: Vec<&MessageState>) -> Result<Vec<Vec<u8>>> {
90-
message_states.sort_by_key(
91-
|m| m.proof_set.wormhole_merkle_proof.vaa.clone(), // FIXME: This is not efficient
92-
);
94+
message_states.sort_by_key(|m| m.slot);
9395

9496
message_states
95-
.group_by(|a, b| {
96-
a.proof_set.wormhole_merkle_proof.vaa == b.proof_set.wormhole_merkle_proof.vaa
97-
})
98-
.map(|messages| {
99-
let vaa = messages
100-
.get(0)
101-
.ok_or(anyhow!("Empty message set"))?
102-
.proof_set
103-
.wormhole_merkle_proof
104-
.vaa
105-
.clone();
106-
107-
Ok(to_vec::<_, byteorder::BE>(&AccumulatorUpdateData::new(
108-
Proof::WormholeMerkle {
109-
vaa: vaa.into(),
110-
updates: messages
111-
.iter()
112-
.map(|message| {
113-
Ok(MerklePriceUpdate {
114-
message: to_vec::<_, byteorder::BE>(&message.message)
115-
.map_err(|e| anyhow!("Failed to serialize message: {}", e))?
116-
.into(),
117-
proof: message.proof_set.wormhole_merkle_proof.proof.clone(),
118-
})
119-
})
120-
.collect::<Result<_>>()?,
121-
},
122-
))?)
97+
.group_by(|a, b| a.slot == b.slot) // States on the same slot share the same merkle root
98+
.flat_map(|messages| {
99+
messages
100+
// Group messages by the number of messages in a single update data
101+
.chunks(MAX_MESSAGE_IN_SINGLE_UPDATE_DATA)
102+
.map(|messages| {
103+
let vaa = messages
104+
.get(0)
105+
.ok_or(anyhow!("Empty message set"))?
106+
.proof_set
107+
.wormhole_merkle_proof
108+
.vaa
109+
.clone();
110+
111+
Ok(to_vec::<_, byteorder::BE>(&AccumulatorUpdateData::new(
112+
Proof::WormholeMerkle {
113+
vaa: vaa.into(),
114+
updates: messages
115+
.iter()
116+
.map(|message| {
117+
Ok(MerklePriceUpdate {
118+
message: to_vec::<_, byteorder::BE>(&message.message)
119+
.map_err(|e| {
120+
anyhow!("Failed to serialize message: {}", e)
121+
})?
122+
.into(),
123+
proof: message
124+
.proof_set
125+
.wormhole_merkle_proof
126+
.proof
127+
.clone(),
128+
})
129+
})
130+
.collect::<Result<_>>()?,
131+
},
132+
))?)
133+
})
123134
})
124135
.collect::<Result<Vec<Vec<u8>>>>()
125136
}
137+
138+
#[cfg(test)]
139+
mod test {
140+
use {
141+
super::*,
142+
crate::store::types::ProofSet,
143+
pythnet_sdk::{
144+
messages::{
145+
Message,
146+
PriceFeedMessage,
147+
},
148+
wire::from_slice,
149+
},
150+
};
151+
152+
fn create_dummy_message_state(slot_and_pubtime: u64) -> MessageState {
153+
MessageState::new(
154+
Message::PriceFeedMessage(PriceFeedMessage {
155+
conf: 0,
156+
price: 0,
157+
feed_id: [0; 32],
158+
exponent: 0,
159+
ema_conf: 0,
160+
ema_price: 0,
161+
publish_time: slot_and_pubtime as i64,
162+
prev_publish_time: 0,
163+
}),
164+
vec![],
165+
ProofSet {
166+
wormhole_merkle_proof: WormholeMerkleMessageProof {
167+
vaa: vec![],
168+
proof: MerklePath::default(),
169+
},
170+
},
171+
slot_and_pubtime,
172+
0,
173+
)
174+
}
175+
176+
#[test]
177+
fn test_construct_update_data_works_on_mixed_slot_and_big_size() {
178+
let mut message_states = vec![];
179+
180+
// Messages slot and publish_time 11 share the same merkle root
181+
for i in 0..MAX_MESSAGE_IN_SINGLE_UPDATE_DATA * 2 - 10 {
182+
message_states.push(create_dummy_message_state(11));
183+
}
184+
185+
// Messages on slot and publish_time 10 that share different root from the messages above
186+
for i in 0..MAX_MESSAGE_IN_SINGLE_UPDATE_DATA * 2 - 10 {
187+
message_states.push(create_dummy_message_state(10));
188+
}
189+
190+
let update_data = construct_update_data(message_states.iter().collect()).unwrap();
191+
192+
assert_eq!(update_data.len(), 4);
193+
194+
// Construct method sorts the messages by slot. So, the first two update data should
195+
// contain messages on slot 10.
196+
for i in 0..4 {
197+
let update_data = &update_data[i];
198+
let update_data = AccumulatorUpdateData::try_from_slice(update_data).unwrap();
199+
let price_updates = match &update_data.proof {
200+
Proof::WormholeMerkle { updates, .. } => updates,
201+
};
202+
203+
let price_update_message = price_updates.first().unwrap().clone();
204+
let price_update_message: Vec<u8> = price_update_message.message.into();
205+
let price_update_message =
206+
from_slice::<byteorder::BE, Message>(price_update_message.as_ref()).unwrap();
207+
208+
match i {
209+
0 => {
210+
assert_eq!(price_updates.len(), MAX_MESSAGE_IN_SINGLE_UPDATE_DATA);
211+
assert_eq!(price_update_message.publish_time(), 10);
212+
}
213+
1 => {
214+
assert_eq!(price_updates.len(), MAX_MESSAGE_IN_SINGLE_UPDATE_DATA - 10);
215+
assert_eq!(price_update_message.publish_time(), 10);
216+
}
217+
2 => {
218+
assert_eq!(price_updates.len(), MAX_MESSAGE_IN_SINGLE_UPDATE_DATA);
219+
assert_eq!(price_update_message.publish_time(), 11);
220+
}
221+
3 => {
222+
assert_eq!(price_updates.len(), MAX_MESSAGE_IN_SINGLE_UPDATE_DATA - 10);
223+
assert_eq!(price_update_message.publish_time(), 11);
224+
}
225+
_ => panic!("Invalid index"),
226+
}
227+
}
228+
}
229+
}

0 commit comments

Comments
 (0)