1
1
import base64
2
2
import binascii
3
- import logging
4
3
from struct import unpack
5
- from typing import List
4
+ from typing import Any , Dict , List , Optional
6
5
7
6
from Crypto .Hash import keccak
7
+ from loguru import logger
8
8
9
9
P2W_FORMAT_MAGIC = "P2WH"
10
10
P2W_FORMAT_VER_MAJOR = 3
13
13
14
14
DEFAULT_VAA_ENCODING = "hex"
15
15
16
+ ACCUMULATOR_MAGIC = "504e4155"
17
+
16
18
17
19
class Price :
18
20
def __init__ (self , conf , expo , price , publish_time ):
@@ -110,7 +112,7 @@ def to_dict(self, verbose=False, vaa_format=DEFAULT_VAA_ENCODING):
110
112
return result
111
113
112
114
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
114
116
def encode_vaa_for_chain (vaa , vaa_format , buffer = False ):
115
117
# check if vaa is already in vaa_format
116
118
if isinstance (vaa , str ):
@@ -337,12 +339,9 @@ def parse_price_attestation(bytes_):
337
339
}
338
340
339
341
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
341
343
def vaa_to_price_infos (vaa , encoding = DEFAULT_VAA_ENCODING ) -> List [PriceInfo ]:
342
344
parsed_vaa = parse_vaa (vaa , encoding )
343
-
344
- # TODO: support accumulators
345
-
346
345
batch_attestation = parse_batch_price_attestation (parsed_vaa ["payload" ])
347
346
price_infos = []
348
347
for price_attestation in batch_attestation ["price_attestations" ]:
@@ -359,6 +358,8 @@ def vaa_to_price_infos(vaa, encoding=DEFAULT_VAA_ENCODING) -> List[PriceInfo]:
359
358
360
359
361
360
def 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 )
362
363
price_infos = vaa_to_price_infos (vaa , encoding )
363
364
for price_info in price_infos :
364
365
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
367
368
return None
368
369
369
370
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
371
372
def create_price_info (price_attestation , vaa , sequence , emitter_chain ):
372
373
price_feed = price_attestation_to_price_feed (price_attestation )
373
374
return PriceInfo (
@@ -407,3 +408,122 @@ def price_attestation_to_price_feed(price_attestation):
407
408
ema_price .publish_time = price_attestation ["prev_publish_time" ]
408
409
409
410
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