index.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
  2. import {PythImplementation__factory} from "./ethers-contracts";
  3. import * as http from "http";
  4. import * as net from "net";
  5. import fs from "fs";
  6. import {ethers} from "ethers";
  7. import {getSignedAttestation, parseBatchAttestation, p2w_core, sol_addr2buf} from "@certusone/p2w-sdk";
  8. import {setDefaultWasm, importCoreWasm} from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
  9. interface NewAttestationsResponse {
  10. pendingSeqnos: Array<number>,
  11. }
  12. async function readinessProbeRoutine(port: number) {
  13. let srv = net.createServer();
  14. return await srv.listen(port);
  15. }
  16. (async () => {
  17. // p2w-attest exposes an HTTP endpoint that shares the currently pending sequence numbers
  18. const P2W_ATTESTATIONS_HOST = process.env.P2W_ATTESTATIONS_HOST || "p2w-attest";
  19. const P2W_ATTESTATIONS_PORT = Number(process.env.P2W_ATTESTATIONS_PORT || "4343");
  20. const P2W_ATTESTATIONS_POLL_INTERVAL_MS = Number(process.env.P2W_ATTESTATIONS_POLL_INTERVAL_MS || "5000");
  21. const P2W_SOL_ADDRESS = process.env.P2W_SOL_ADDRESS || "P2WH424242424242424242424242424242424242424";
  22. const READINESS_PROBE_PORT = Number(process.env.READINESS_PROBE_PORT || "2000");
  23. const P2W_RELAY_RETRY_COUNT = Number(process.env.P2W_RELAY_RETRY_COUNT || "3");
  24. // ETH node connection details; Currently, we expect to read BIP44
  25. // wallet recovery mnemonics from a text file.
  26. const ETH_NODE_URL = process.env.ETH_NODE_URL || "ws://eth-devnet:8545";
  27. const ETH_P2W_CONTRACT = process.env.ETH_P2W_CONTRACT || "0xA94B7f0465E98609391C623d0560C5720a3f2D33";
  28. const ETH_MNEMONIC_FILE = process.env.ETH_MNEMONIC_FILE || "../../../ethereum/devnet_mnemonic.txt";
  29. const ETH_HD_WALLET_PATH = process.env.ETH_HD_WALLET_PATH || "m/44'/60'/0'/0/0";
  30. // Public RPC address for use with signed attestation queries
  31. const GUARDIAN_RPC_HOST_PORT = process.env.GUARDIAN_RPC_HOST_PORT || "http://guardian:7071";
  32. let readinessProbe = null;
  33. let seqnoPool: Map<number, number> = new Map();
  34. console.log(`Polling attestations endpoint every ${P2W_ATTESTATIONS_POLL_INTERVAL_MS / 1000} seconds`);
  35. setDefaultWasm("node");
  36. const {parse_vaa} = await importCoreWasm();
  37. let p2w_eth: any;
  38. // Connect to ETH
  39. try {
  40. let provider = new ethers.providers.WebSocketProvider(ETH_NODE_URL);
  41. let mnemonic: string = fs.readFileSync(ETH_MNEMONIC_FILE).toString("utf-8").trim();
  42. let wallet = ethers.Wallet.fromMnemonic(mnemonic, ETH_HD_WALLET_PATH);
  43. console.log(`Using ETH wallet pubkey: ${wallet.publicKey}`);
  44. let signer = new ethers.Wallet(wallet.privateKey, provider);
  45. let balance = await signer.getBalance();
  46. console.log(`Account balance is ${balance}`);
  47. let factory = new PythImplementation__factory(signer);
  48. p2w_eth = factory.attach(ETH_P2W_CONTRACT);
  49. }
  50. catch(e) {
  51. console.error(`Error: Could not instantiate ETH contract:`, e);
  52. throw e;
  53. }
  54. while (true) {
  55. http.get({
  56. hostname: P2W_ATTESTATIONS_HOST,
  57. port: P2W_ATTESTATIONS_PORT,
  58. path: "/",
  59. agent: false
  60. }, (res) => {
  61. if (res.statusCode != 200) {
  62. console.error("Could not reach attestations endpoint", res);
  63. } else {
  64. let chunks: string[] = [];
  65. res.setEncoding("utf-8");
  66. res.on('data', (chunk) => {
  67. chunks.push(chunk);
  68. });
  69. res.on('end', () => {
  70. let body = chunks.join('');
  71. let response: NewAttestationsResponse = JSON.parse(body);
  72. console.log(`Got ${response.pendingSeqnos.length} new seqnos: ${response.pendingSeqnos}`);
  73. for (let seqno of response.pendingSeqnos) {
  74. seqnoPool.set(seqno, 0);
  75. }
  76. });
  77. }
  78. }).on('error', (e) => {
  79. console.error(`Got error: ${e.message}`);
  80. });
  81. console.log("Processing seqnos:", seqnoPool);
  82. for (let poolEntry of seqnoPool) {
  83. let seqno = poolEntry[0];
  84. let attempts = poolEntry[1];
  85. if (attempts >= P2W_RELAY_RETRY_COUNT) {
  86. console.warn(`[seqno ${poolEntry}] Exceeded retry count, removing from list`);
  87. seqnoPool.delete(seqno);
  88. continue;
  89. }
  90. let vaaResponse: any;
  91. try {
  92. vaaResponse = await getSignedAttestation(
  93. GUARDIAN_RPC_HOST_PORT,
  94. P2W_SOL_ADDRESS,
  95. seqno,
  96. {
  97. transport: NodeHttpTransport()
  98. }
  99. );
  100. }
  101. catch(e) {
  102. console.error(`[seqno ${poolEntry}] Error: Could not call getSignedAttestation:`, e);
  103. seqnoPool.set(seqno, attempts + 1);
  104. continue;
  105. }
  106. console.log(`[seqno ${poolEntry}] Price attestation VAA bytes:\n`, vaaResponse.vaaBytes);
  107. let parsedVaa = parse_vaa(vaaResponse.vaaBytes);
  108. console.log(`[seqno ${poolEntry}] Parsed VAA:\n`, parsedVaa);
  109. let parsedAttestations = await parseBatchAttestation(parsedVaa.payload);
  110. console.log(`[seqno ${poolEntry}] Parsed ${parsedAttestations.price_attestations.length} price attestations:\n`, parsedAttestations);
  111. // try {
  112. // let tx = await p2w_eth.attestPrice(vaaResponse.vaaBytes, {gasLimit: 1000000});
  113. // let retval = await tx.wait();
  114. // console.log(`[seqno ${poolEntry}] attestPrice() output:\n`, retval);
  115. // } catch(e) {
  116. // console.error(`[seqno ${poolEntry}, {parsedAttestations.length} symbols] Error: Could not call attestPrice() on ETH:`, e);
  117. // seqnoPool.set(seqno, attempts + 1);
  118. // continue;
  119. // }
  120. console.warn("TODO: implement relayer ETH call");
  121. // for (let att of parsedAttestations) {
  122. // let product_id = att.product_id;
  123. // let price_type = att.price_type == "Price" ? 1 : 0;
  124. // let latest_attestation: any;
  125. // try {
  126. // let p2w = await p2w_core();
  127. // console.log(`Looking up latestAttestation for `, product_id, price_type);
  128. // latest_attestation = await p2w_eth.latestAttestation(product_id, price_type);
  129. // } catch(e) {
  130. // console.error(`[seqno ${poolEntry}] Error: Could not call latestAttestation() on ETH:`, e);
  131. // seqnoPool.set(seqno, attempts + 1);
  132. // continue;
  133. // }
  134. // console.log(`[seqno ${poolEntry}] Latest price type ${price_type} attestation of ${product_id} is ${latest_attestation}`);
  135. // }
  136. if (!readinessProbe) {
  137. console.log(`[seqno ${poolEntry}] Attestation successful. Starting readiness probe.`);
  138. readinessProbe = readinessProbeRoutine(READINESS_PROBE_PORT);
  139. }
  140. seqnoPool.delete(seqno); // Everything went well, seqno no longer pending.
  141. }
  142. await new Promise(f => {setTimeout(f, P2W_ATTESTATIONS_POLL_INTERVAL_MS);});
  143. }
  144. })();