p2w_autoattest.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. #!/usr/bin/env python3
  2. # This script sets up a simple loop for periodical attestation of Pyth data
  3. import json
  4. import logging
  5. import os
  6. import re
  7. import sys
  8. import threading
  9. import time
  10. from http.client import HTTPConnection
  11. from http.server import BaseHTTPRequestHandler, HTTPServer
  12. from pyth_utils import *
  13. logging.basicConfig(
  14. level=logging.DEBUG, format="%(asctime)s | %(module)s | %(levelname)s | %(message)s"
  15. )
  16. P2W_SOL_ADDRESS = os.environ.get(
  17. "P2W_SOL_ADDRESS", "P2WH424242424242424242424242424242424242424"
  18. )
  19. P2W_ATTEST_INTERVAL = float(os.environ.get("P2W_ATTEST_INTERVAL", 5))
  20. P2W_OWNER_KEYPAIR = os.environ.get(
  21. "P2W_OWNER_KEYPAIR", "/usr/src/solana/keys/p2w_owner.json"
  22. )
  23. P2W_ATTESTATIONS_PORT = int(os.environ.get("P2W_ATTESTATIONS_PORT", 4343))
  24. P2W_INITIALIZE_SOL_CONTRACT = os.environ.get("P2W_INITIALIZE_SOL_CONTRACT", None)
  25. PYTH_TEST_ACCOUNTS_HOST = "pyth"
  26. PYTH_TEST_ACCOUNTS_PORT = 4242
  27. P2W_ATTESTATION_CFG = os.environ.get("P2W_ATTESTATION_CFG", None)
  28. WORMHOLE_ADDRESS = os.environ.get(
  29. "WORMHOLE_ADDRESS", "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
  30. )
  31. ATTESTATIONS = {
  32. "pendingSeqnos": [],
  33. }
  34. class P2WAutoattestStatusEndpoint(BaseHTTPRequestHandler):
  35. """
  36. A dumb endpoint for last attested price metadata.
  37. """
  38. def do_GET(self):
  39. logging.info(f"Got path {self.path}")
  40. sys.stdout.flush()
  41. data = json.dumps(ATTESTATIONS).encode("utf-8")
  42. logging.debug(f"Sending: {data}")
  43. ATTESTATIONS["pendingSeqnos"] = []
  44. self.send_response(200)
  45. self.send_header("Content-Type", "application/json")
  46. self.send_header("Content-Length", str(len(data)))
  47. self.end_headers()
  48. self.wfile.write(data)
  49. self.wfile.flush()
  50. def serve_attestations():
  51. """
  52. Run a barebones HTTP server to share Pyth2wormhole attestation history
  53. """
  54. server_address = ("", P2W_ATTESTATIONS_PORT)
  55. httpd = HTTPServer(server_address, P2WAutoattestStatusEndpoint)
  56. httpd.serve_forever()
  57. if SOL_AIRDROP_AMT > 0:
  58. # Fund the p2w owner
  59. sol_run_or_die("airdrop", [
  60. str(SOL_AIRDROP_AMT),
  61. "--keypair", P2W_OWNER_KEYPAIR,
  62. "--commitment", "finalized",
  63. ])
  64. if P2W_INITIALIZE_SOL_CONTRACT is not None:
  65. # Get actor pubkeys
  66. P2W_OWNER_ADDRESS = sol_run_or_die(
  67. "address", ["--keypair", P2W_OWNER_KEYPAIR], capture_output=True
  68. ).stdout.strip()
  69. PYTH_OWNER_ADDRESS = sol_run_or_die(
  70. "address", ["--keypair", PYTH_PROGRAM_KEYPAIR], capture_output=True
  71. ).stdout.strip()
  72. init_result = run_or_die(
  73. [
  74. "pyth2wormhole-client",
  75. "--log-level",
  76. "4",
  77. "--p2w-addr",
  78. P2W_SOL_ADDRESS,
  79. "--rpc-url",
  80. SOL_RPC_URL,
  81. "--payer",
  82. P2W_OWNER_KEYPAIR,
  83. "init",
  84. "--wh-prog",
  85. WORMHOLE_ADDRESS,
  86. "--owner",
  87. P2W_OWNER_ADDRESS,
  88. "--pyth-owner",
  89. PYTH_OWNER_ADDRESS,
  90. ],
  91. capture_output=True,
  92. die=False,
  93. )
  94. if init_result.returncode != 0:
  95. logging.error(
  96. "NOTE: pyth2wormhole-client init failed, retrying with set_config"
  97. )
  98. run_or_die(
  99. [
  100. "pyth2wormhole-client",
  101. "--log-level",
  102. "4",
  103. "--p2w-addr",
  104. P2W_SOL_ADDRESS,
  105. "--rpc-url",
  106. SOL_RPC_URL,
  107. "--payer",
  108. P2W_OWNER_KEYPAIR,
  109. "set-config",
  110. "--owner",
  111. P2W_OWNER_KEYPAIR,
  112. "--new-owner",
  113. P2W_OWNER_ADDRESS,
  114. "--new-wh-prog",
  115. WORMHOLE_ADDRESS,
  116. "--new-pyth-owner",
  117. PYTH_OWNER_ADDRESS,
  118. ],
  119. capture_output=True,
  120. )
  121. # Retrieve available symbols from the test pyth publisher if not provided in envs
  122. if P2W_ATTESTATION_CFG is None:
  123. P2W_ATTESTATION_CFG = "./attestation_cfg_test.yaml"
  124. conn = HTTPConnection(PYTH_TEST_ACCOUNTS_HOST, PYTH_TEST_ACCOUNTS_PORT)
  125. conn.request("GET", "/")
  126. res = conn.getresponse()
  127. pyth_accounts = None
  128. if res.getheader("Content-Type") == "application/json":
  129. pyth_accounts = json.load(res)
  130. else:
  131. logging.error("Bad Content type")
  132. sys.exit(1)
  133. cfg_yaml = f"""
  134. ---
  135. symbols:"""
  136. logging.info(f"Retrieved {len(pyth_accounts)} Pyth accounts from endpoint: {pyth_accounts}")
  137. for acc in pyth_accounts:
  138. name = acc["name"]
  139. price = acc["price"]
  140. product = acc["product"]
  141. cfg_yaml += f"""
  142. - name: {name}
  143. price_addr: {price}
  144. product_addr: {product}"""
  145. with open(P2W_ATTESTATION_CFG, "w") as f:
  146. f.write(cfg_yaml)
  147. f.flush()
  148. attest_result = run_or_die(
  149. [
  150. "pyth2wormhole-client",
  151. "--log-level",
  152. "4",
  153. "--p2w-addr",
  154. P2W_SOL_ADDRESS,
  155. "--rpc-url",
  156. SOL_RPC_URL,
  157. "--payer",
  158. P2W_OWNER_KEYPAIR,
  159. "attest",
  160. "-f",
  161. P2W_ATTESTATION_CFG
  162. ],
  163. capture_output=True,
  164. )
  165. logging.info("p2w_autoattest ready to roll!")
  166. logging.info(f"Attest Interval: {P2W_ATTEST_INTERVAL}")
  167. # Serve p2w endpoint
  168. endpoint_thread = threading.Thread(target=serve_attestations, daemon=True)
  169. endpoint_thread.start()
  170. # Let k8s know the service is up
  171. readiness_thread = threading.Thread(target=readiness, daemon=True)
  172. readiness_thread.start()
  173. seqno_regex = re.compile(r"Sequence number: (\d+)")
  174. while True:
  175. matches = seqno_regex.findall(attest_result.stdout)
  176. seqnos = list(map(lambda m: int(m), matches))
  177. ATTESTATIONS["pendingSeqnos"] += seqnos
  178. logging.info(f"{len(seqnos)} batch seqno(s) received: {seqnos})")
  179. attest_result = run_or_die(
  180. [
  181. "pyth2wormhole-client",
  182. "--log-level",
  183. "4",
  184. "--p2w-addr",
  185. P2W_SOL_ADDRESS,
  186. "--rpc-url",
  187. SOL_RPC_URL,
  188. "--payer",
  189. P2W_OWNER_KEYPAIR,
  190. "attest",
  191. "-f",
  192. P2W_ATTESTATION_CFG
  193. ],
  194. capture_output=True,
  195. )
  196. time.sleep(P2W_ATTEST_INTERVAL)