11import base64
22import binascii
3- import logging
43from struct import unpack
5- from typing import List
4+ from typing import Any , Dict , List , Optional
65
76from Crypto .Hash import keccak
7+ from loguru import logger
88
99P2W_FORMAT_MAGIC = "P2WH"
1010P2W_FORMAT_VER_MAJOR = 3
1313
1414DEFAULT_VAA_ENCODING = "hex"
1515
16+ ACCUMULATOR_MAGIC = "504e4155"
17+
1618
1719class Price :
1820 def __init__ (self , conf , expo , price , publish_time ):
@@ -110,7 +112,7 @@ def to_dict(self, verbose=False, vaa_format=DEFAULT_VAA_ENCODING):
110112 return result
111113
112114
113- # Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main /price_service/server/src/encoding.ts#L24
115+ # Referenced from https://github.com/pyth-network/pyth-crosschain/blob/110caed6be3be7885773d2f6070b143cc13fb0ee /price_service/server/src/encoding.ts#L24
114116def encode_vaa_for_chain (vaa , vaa_format , buffer = False ):
115117 # check if vaa is already in vaa_format
116118 if isinstance (vaa , str ):
@@ -337,12 +339,9 @@ def parse_price_attestation(bytes_):
337339 }
338340
339341
340- # Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main /price_service/server/src/rest.ts#L139
342+ # Referenced from https://github.com/pyth-network/pyth-crosschain/blob/110caed6be3be7885773d2f6070b143cc13fb0ee /price_service/server/src/rest.ts#L139
341343def vaa_to_price_infos (vaa , encoding = DEFAULT_VAA_ENCODING ) -> List [PriceInfo ]:
342344 parsed_vaa = parse_vaa (vaa , encoding )
343-
344- # TODO: support accumulators
345-
346345 batch_attestation = parse_batch_price_attestation (parsed_vaa ["payload" ])
347346 price_infos = []
348347 for price_attestation in batch_attestation ["price_attestations" ]:
@@ -359,6 +358,8 @@ def vaa_to_price_infos(vaa, encoding=DEFAULT_VAA_ENCODING) -> List[PriceInfo]:
359358
360359
361360def vaa_to_price_info (price_feed_id , vaa , encoding = DEFAULT_VAA_ENCODING ) -> PriceInfo :
361+ if encode_vaa_for_chain (vaa , encoding , buffer = True )[:4 ].hex () == ACCUMULATOR_MAGIC :
362+ return extract_price_info_from_accumulator_update (price_feed_id , vaa , encoding )
362363 price_infos = vaa_to_price_infos (vaa , encoding )
363364 for price_info in price_infos :
364365 if price_info .price_feed .id == price_feed_id :
@@ -367,7 +368,7 @@ def vaa_to_price_info(price_feed_id, vaa, encoding=DEFAULT_VAA_ENCODING) -> Pric
367368 return None
368369
369370
370- # Referenced from https://github.com/pyth-network/pyth-crosschain/blob/main /price_service/server/src/listen.ts#L37
371+ # Referenced from https://github.com/pyth-network/pyth-crosschain/blob/110caed6be3be7885773d2f6070b143cc13fb0ee /price_service/server/src/listen.ts#L37
371372def create_price_info (price_attestation , vaa , sequence , emitter_chain ):
372373 price_feed = price_attestation_to_price_feed (price_attestation )
373374 return PriceInfo (
@@ -407,3 +408,122 @@ def price_attestation_to_price_feed(price_attestation):
407408 ema_price .publish_time = price_attestation ["prev_publish_time" ]
408409
409410 return PriceUpdate (ema_price , price_attestation ["price_id" ], price )
411+
412+
413+ # Referenced from https://github.com/pyth-network/pyth-crosschain/blob/1a00598334e52fc5faf967eb1170d7fc23ad828b/price_service/server/src/rest.ts#L137
414+ def extract_price_info_from_accumulator_update (
415+ price_feed_id , update_data , encoding
416+ ) -> Optional [Dict [str , Any ]]:
417+ encoded_update_data = encode_vaa_for_chain (update_data , encoding , buffer = True )
418+ offset = 0
419+ offset += 4 # magic
420+ offset += 1 # major version
421+ offset += 1 # minor version
422+
423+ trailing_header_size = encoded_update_data [offset ]
424+ offset += 1 + trailing_header_size
425+
426+ update_type = encoded_update_data [offset ]
427+ offset += 1
428+
429+ if update_type != 0 :
430+ logger .info (f"Invalid accumulator update type: { update_type } " )
431+ return None
432+
433+ vaa_length = int .from_bytes (
434+ encoded_update_data [offset : offset + 2 ], byteorder = "big"
435+ )
436+ offset += 2
437+
438+ vaa_buffer = encoded_update_data [offset : offset + vaa_length ]
439+ # convert vaa_buffer to string based on encoding
440+ if encoding == "hex" :
441+ vaa_str = vaa_buffer .hex ()
442+ elif encoding == "base64" :
443+ vaa_str = base64 .b64encode (vaa_buffer ).decode ("ascii" )
444+ parsed_vaa = parse_vaa (vaa_str , encoding )
445+ offset += vaa_length
446+
447+ num_updates = encoded_update_data [offset ]
448+ offset += 1
449+
450+ for _ in range (num_updates ):
451+ message_length = int .from_bytes (
452+ encoded_update_data [offset : offset + 2 ], byteorder = "big"
453+ )
454+ offset += 2
455+
456+ message = encoded_update_data [offset : offset + message_length ]
457+ offset += message_length
458+
459+ proof_length = encoded_update_data [offset ]
460+ offset += 1
461+ offset += proof_length # ignore proofs
462+
463+ message_offset = 0
464+ message_type = message [message_offset ]
465+ message_offset += 1
466+
467+ # Message Type 0 is a price update and we ignore the rest
468+ if message_type != 0 :
469+ continue
470+
471+ price_id = message [message_offset : message_offset + 32 ].hex ()
472+ message_offset += 32
473+
474+ if price_id != price_feed_id :
475+ continue
476+
477+ price = int .from_bytes (
478+ message [message_offset : message_offset + 8 ], byteorder = "big" , signed = True
479+ )
480+ message_offset += 8
481+ conf = int .from_bytes (
482+ message [message_offset : message_offset + 8 ], byteorder = "big" , signed = False
483+ )
484+ message_offset += 8
485+ expo = int .from_bytes (
486+ message [message_offset : message_offset + 4 ], byteorder = "big" , signed = True
487+ )
488+ message_offset += 4
489+ publish_time = int .from_bytes (
490+ message [message_offset : message_offset + 8 ], byteorder = "big" , signed = True
491+ )
492+ message_offset += 8
493+ prev_publish_time = int .from_bytes (
494+ message [message_offset : message_offset + 8 ], byteorder = "big" , signed = True
495+ )
496+ message_offset += 8
497+ ema_price = int .from_bytes (
498+ message [message_offset : message_offset + 8 ], byteorder = "big" , signed = True
499+ )
500+ message_offset += 8
501+ ema_conf = int .from_bytes (
502+ message [message_offset : message_offset + 8 ], byteorder = "big" , signed = False
503+ )
504+
505+ return PriceInfo (
506+ seq_num = parsed_vaa ["sequence" ],
507+ vaa = update_data ,
508+ publish_time = publish_time ,
509+ attestation_time = publish_time ,
510+ last_attested_publish_time = prev_publish_time ,
511+ price_feed = PriceUpdate (
512+ ema_price = Price (
513+ price = str (ema_price ),
514+ conf = str (ema_conf ),
515+ expo = expo ,
516+ publish_time = publish_time ,
517+ ),
518+ price_id = price_id ,
519+ price = Price (
520+ price = str (price ),
521+ conf = str (conf ),
522+ expo = expo ,
523+ publish_time = publish_time ,
524+ ),
525+ ),
526+ emitter_chain_id = parsed_vaa ["emitter_chain" ],
527+ )
528+
529+ return None
0 commit comments