VerificationExperiments.t.sol 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. // SPDX-License-Identifier: Apache 2
  2. pragma solidity ^0.8.0;
  3. import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
  4. import "forge-std/Test.sol";
  5. import "../contracts/libraries/external/UnsafeBytesLib.sol";
  6. import "@pythnetwork/pyth-sdk-solidity/AbstractPyth.sol";
  7. import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
  8. import "@pythnetwork/pyth-sdk-solidity/PythErrors.sol";
  9. import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
  10. import "../contracts/pyth/PythInternalStructs.sol";
  11. import "./utils/WormholeTestUtils.t.sol";
  12. import "./utils/PythTestUtils.t.sol";
  13. import "./utils/RandTestUtils.t.sol";
  14. // Experiments to measure the gas usage of different ways of verifying prices in the EVM contract.
  15. contract VerificationExperiments is Test, PythTestUtils {
  16. // 19, current mainnet number of guardians, is used to have gas estimates
  17. // close to our mainnet transactions.
  18. uint8 constant NUM_GUARDIANS = 19;
  19. // 2/3 of the guardians should sign a message for a VAA which is 13 out of 19 guardians.
  20. // It is possible to have more signers but the median seems to be 13.
  21. uint8 constant NUM_GUARDIAN_SIGNERS = 13;
  22. // We use 5 prices to form a batch of 5 prices, close to our mainnet transactions.
  23. uint8 constant NUM_PRICES = 5;
  24. // Private key for the threshold signature
  25. uint256 THRESHOLD_KEY = 1234;
  26. // We will have less than 512 price for a foreseeable future.
  27. uint8 constant MERKLE_TREE_DEPTH = 9;
  28. PythExperimental public pyth;
  29. bytes32[] priceIds;
  30. // Cached prices are populated in the setUp
  31. PythStructs.Price[] cachedPrices;
  32. bytes[] cachedPricesUpdateData;
  33. uint cachedPricesUpdateFee;
  34. uint64[] cachedPricesPublishTimes;
  35. // Fresh prices are different prices that can be used
  36. // as a fresh price to update the prices
  37. PythStructs.Price[] freshPrices;
  38. bytes[] freshPricesUpdateData;
  39. uint freshPricesUpdateFee;
  40. uint64[] freshPricesPublishTimes;
  41. WormholeMerkleUpdate whMerkleUpdateDepth0;
  42. WormholeMerkleUpdate whMerkleUpdateDepth1;
  43. WormholeMerkleUpdate whMerkleUpdateDepth8;
  44. ThresholdMerkleUpdate thresholdMerkleUpdateDepth0;
  45. ThresholdMerkleUpdate thresholdMerkleUpdateDepth1;
  46. ThresholdMerkleUpdate thresholdMerkleUpdateDepth8;
  47. ThresholdUpdate thresholdUpdate;
  48. bytes nativeUpdate;
  49. uint64 sequence;
  50. function setUp() public {
  51. WormholeTestUtils wormholeTestUtils = new WormholeTestUtils(
  52. NUM_GUARDIAN_SIGNERS
  53. );
  54. // Just set it up for generating the VAA signatures.
  55. setUpPyth(wormholeTestUtils);
  56. address payable wormhole = payable(
  57. wormholeTestUtils.getWormholeReceiverAddr()
  58. );
  59. // Deploy experimental contract
  60. PythExperimental implementation = new PythExperimental();
  61. ERC1967Proxy proxy = new ERC1967Proxy(
  62. address(implementation),
  63. new bytes(0)
  64. );
  65. pyth = PythExperimental(address(proxy));
  66. uint16[] memory emitterChainIds = new uint16[](1);
  67. emitterChainIds[0] = PythTestUtils.SOURCE_EMITTER_CHAIN_ID;
  68. bytes32[] memory emitterAddresses = new bytes32[](1);
  69. emitterAddresses[0] = PythTestUtils.SOURCE_EMITTER_ADDRESS;
  70. pyth.initialize(
  71. wormhole,
  72. vm.addr(THRESHOLD_KEY),
  73. emitterChainIds,
  74. emitterAddresses,
  75. PythTestUtils.GOVERNANCE_EMITTER_CHAIN_ID,
  76. PythTestUtils.GOVERNANCE_EMITTER_ADDRESS,
  77. 0, // Initial governance sequence
  78. 60, // Valid time period in seconds
  79. 1 // single update fee in wei
  80. );
  81. priceIds = new bytes32[](NUM_PRICES);
  82. priceIds[0] = bytes32(
  83. 0x1000000000000000000000000000000000000000000000000000000000000f00
  84. );
  85. for (uint i = 1; i < NUM_PRICES; ++i) {
  86. priceIds[i] = bytes32(uint256(priceIds[i - 1]) + 1);
  87. }
  88. setRandSeed(12345);
  89. for (uint i = 0; i < NUM_PRICES; ++i) {
  90. uint64 publishTime = uint64(getRandUint() % 10) + 1; // to make sure prevPublishTime is >= 0
  91. cachedPrices.push(
  92. PythStructs.Price(
  93. int64(uint64(getRandUint() % 1000)), // Price
  94. uint64(getRandUint() % 100), // Confidence
  95. -5, // Expo
  96. publishTime
  97. )
  98. );
  99. cachedPricesPublishTimes.push(publishTime);
  100. publishTime += uint64(getRandUint() % 10);
  101. freshPrices.push(
  102. PythStructs.Price(
  103. int64(uint64(getRandUint() % 1000)), // Price
  104. uint64(getRandUint() % 100), // Confidence
  105. -5, // Expo
  106. publishTime
  107. )
  108. );
  109. freshPricesPublishTimes.push(publishTime);
  110. }
  111. // Populate the contract with the initial prices
  112. (
  113. cachedPricesUpdateData,
  114. cachedPricesUpdateFee
  115. ) = generateWormholeUpdateDataAndFee(cachedPrices);
  116. pyth.updatePriceFeeds{value: cachedPricesUpdateFee}(
  117. cachedPricesUpdateData
  118. );
  119. (
  120. freshPricesUpdateData,
  121. freshPricesUpdateFee
  122. ) = generateWormholeUpdateDataAndFee(freshPrices);
  123. // Generate the update payloads for the various verification systems
  124. whMerkleUpdateDepth0 = generateSingleWhMerkleUpdate(
  125. priceIds[0],
  126. freshPrices[0],
  127. 0
  128. );
  129. whMerkleUpdateDepth1 = generateSingleWhMerkleUpdate(
  130. priceIds[0],
  131. freshPrices[0],
  132. 1
  133. );
  134. whMerkleUpdateDepth8 = generateSingleWhMerkleUpdate(
  135. priceIds[0],
  136. freshPrices[0],
  137. 8
  138. );
  139. thresholdMerkleUpdateDepth0 = generateThresholdMerkleUpdate(
  140. priceIds[0],
  141. freshPrices[0],
  142. 0
  143. );
  144. thresholdMerkleUpdateDepth1 = generateThresholdMerkleUpdate(
  145. priceIds[0],
  146. freshPrices[0],
  147. 1
  148. );
  149. thresholdMerkleUpdateDepth8 = generateThresholdMerkleUpdate(
  150. priceIds[0],
  151. freshPrices[0],
  152. 8
  153. );
  154. thresholdUpdate = generateThresholdUpdate(priceIds[0], freshPrices[0]);
  155. nativeUpdate = generateMessagePayload(priceIds[0], freshPrices[0]);
  156. }
  157. // Get the payload for a wormhole batch price update
  158. function generateWormholeUpdateDataAndFee(
  159. PythStructs.Price[] memory prices
  160. ) internal returns (bytes[] memory updateData, uint updateFee) {
  161. bytes memory vaa = generateWhMerkleUpdate(
  162. pricesToPriceFeedMessages(priceIds, prices),
  163. MERKLE_TREE_DEPTH,
  164. NUM_GUARDIAN_SIGNERS
  165. );
  166. ++sequence;
  167. updateData = new bytes[](1);
  168. updateData[0] = vaa;
  169. updateFee = pyth.getUpdateFee(updateData);
  170. }
  171. // Helper function to serialize a single price update to bytes.
  172. // Returns a serialized PriceFeedMessage.
  173. function generateMessagePayload(
  174. bytes32 priceId,
  175. PythStructs.Price memory price
  176. ) internal returns (bytes memory data) {
  177. bytes32[] memory messagePriceIds = new bytes32[](1);
  178. messagePriceIds[0] = priceId;
  179. PythStructs.Price[] memory prices = new PythStructs.Price[](1);
  180. prices[0] = price;
  181. data = encodePriceFeedMessages(
  182. pricesToPriceFeedMessages(messagePriceIds, prices)
  183. )[0];
  184. return data;
  185. }
  186. // Helper function to generate a merkle proof of the given depth for the given price.
  187. // Note: this function assumes that the data is on the leftmost branch of the tree and
  188. // mocks the rest of the nodes (as the hash of the depth).
  189. function generateMerkleProof(
  190. bytes32 priceId,
  191. PythStructs.Price memory price,
  192. uint depth
  193. )
  194. internal
  195. returns (bytes32 root, bytes memory data, bytes32[] memory proof)
  196. {
  197. data = generateMessagePayload(priceId, price);
  198. bytes32 curNodeHash = keccak256(data);
  199. proof = new bytes32[](depth);
  200. for (uint i = 0; i < depth; ) {
  201. // pretend the ith sibling is just i
  202. proof[i] = keccak256(abi.encode(i));
  203. curNodeHash = keccak256(abi.encode(curNodeHash, proof[i]));
  204. unchecked {
  205. i++;
  206. }
  207. }
  208. root = curNodeHash;
  209. return (root, data, proof);
  210. }
  211. // Generate a wormhole-attested merkle proof with the given depth.
  212. function generateSingleWhMerkleUpdate(
  213. bytes32 priceId,
  214. PythStructs.Price memory price,
  215. uint depth
  216. ) internal returns (WormholeMerkleUpdate memory update) {
  217. (
  218. bytes32 root,
  219. bytes memory data,
  220. bytes32[] memory proof
  221. ) = generateMerkleProof(priceId, price, depth);
  222. bytes memory rootVaa = generateVaa(
  223. uint32(block.timestamp),
  224. PythTestUtils.SOURCE_EMITTER_CHAIN_ID,
  225. PythTestUtils.SOURCE_EMITTER_ADDRESS,
  226. sequence,
  227. bytes.concat(root),
  228. NUM_GUARDIAN_SIGNERS,
  229. false
  230. );
  231. ++sequence;
  232. return WormholeMerkleUpdate(rootVaa, data, proof);
  233. }
  234. // Generate a threshold-signed merkle proof with the given depth.
  235. function generateThresholdMerkleUpdate(
  236. bytes32 priceId,
  237. PythStructs.Price memory price,
  238. uint depth
  239. ) internal returns (ThresholdMerkleUpdate memory update) {
  240. (
  241. bytes32 root,
  242. bytes memory data,
  243. bytes32[] memory proof
  244. ) = generateMerkleProof(priceId, price, depth);
  245. data = generateMessagePayload(priceId, price);
  246. (uint8 v, bytes32 r, bytes32 s) = vm.sign(THRESHOLD_KEY, root);
  247. bytes memory signature = abi.encodePacked(r, s, v - 27);
  248. return ThresholdMerkleUpdate(signature, root, data, proof);
  249. }
  250. // Generate a threshold-signed price feed message.
  251. function generateThresholdUpdate(
  252. bytes32 priceId,
  253. PythStructs.Price memory price
  254. ) internal returns (ThresholdUpdate memory update) {
  255. bytes memory data = generateMessagePayload(priceId, price);
  256. bytes32 hash = keccak256(data);
  257. (uint8 v, bytes32 r, bytes32 s) = vm.sign(THRESHOLD_KEY, hash);
  258. bytes memory signature = abi.encodePacked(r, s, v - 27);
  259. return ThresholdUpdate(signature, data);
  260. }
  261. function testWhBatchUpdate() public {
  262. pyth.updatePriceFeeds{value: freshPricesUpdateFee}(
  263. freshPricesUpdateData
  264. );
  265. }
  266. function testUpdateWhMerkleProofDepth0() public {
  267. pyth.updatePriceFeedsWhMerkle(
  268. whMerkleUpdateDepth0.rootVaa,
  269. whMerkleUpdateDepth0.data,
  270. whMerkleUpdateDepth0.proof
  271. );
  272. }
  273. function testUpdateWhMerkleProofDepth1() public {
  274. pyth.updatePriceFeedsWhMerkle(
  275. whMerkleUpdateDepth1.rootVaa,
  276. whMerkleUpdateDepth1.data,
  277. whMerkleUpdateDepth1.proof
  278. );
  279. }
  280. function testUpdateWhMerkleProofDepth8() public {
  281. pyth.updatePriceFeedsWhMerkle(
  282. whMerkleUpdateDepth8.rootVaa,
  283. whMerkleUpdateDepth8.data,
  284. whMerkleUpdateDepth8.proof
  285. );
  286. }
  287. function testUpdateThresholdMerkleProofDepth0() public {
  288. pyth.updatePriceFeedsThresholdMerkle(
  289. thresholdMerkleUpdateDepth0.rootSignature,
  290. thresholdMerkleUpdateDepth0.rootHash,
  291. thresholdMerkleUpdateDepth0.data,
  292. thresholdMerkleUpdateDepth0.proof
  293. );
  294. }
  295. function testUpdateThresholdMerkleProofDepth1() public {
  296. pyth.updatePriceFeedsThresholdMerkle(
  297. thresholdMerkleUpdateDepth1.rootSignature,
  298. thresholdMerkleUpdateDepth1.rootHash,
  299. thresholdMerkleUpdateDepth1.data,
  300. thresholdMerkleUpdateDepth1.proof
  301. );
  302. }
  303. function testUpdateThresholdMerkleProofDepth8() public {
  304. pyth.updatePriceFeedsThresholdMerkle(
  305. thresholdMerkleUpdateDepth8.rootSignature,
  306. thresholdMerkleUpdateDepth8.rootHash,
  307. thresholdMerkleUpdateDepth8.data,
  308. thresholdMerkleUpdateDepth8.proof
  309. );
  310. }
  311. function testUpdateThreshold() public {
  312. pyth.updatePriceFeedsThreshold(
  313. thresholdUpdate.signature,
  314. thresholdUpdate.data
  315. );
  316. }
  317. function testUpdateNative() public {
  318. pyth.updatePriceFeedsNative(nativeUpdate);
  319. }
  320. }
  321. // Pyth contract extended with methods for other verification systems (merkle proofs / threshold signatures)
  322. contract PythExperimental is Pyth {
  323. address thresholdPublicKey;
  324. function initialize(
  325. address wormhole,
  326. address thresholdPublicKeyArg,
  327. uint16[] calldata dataSourceEmitterChainIds,
  328. bytes32[] calldata dataSourceEmitterAddresses,
  329. uint16 governanceEmitterChainId,
  330. bytes32 governanceEmitterAddress,
  331. uint64 governanceInitialSequence,
  332. uint validTimePeriodSeconds,
  333. uint singleUpdateFeeInWei
  334. ) public {
  335. thresholdPublicKey = thresholdPublicKeyArg;
  336. Pyth._initialize(
  337. wormhole,
  338. dataSourceEmitterChainIds,
  339. dataSourceEmitterAddresses,
  340. governanceEmitterChainId,
  341. governanceEmitterAddress,
  342. governanceInitialSequence,
  343. validTimePeriodSeconds,
  344. singleUpdateFeeInWei
  345. );
  346. }
  347. // Update a single price feed via a wormhole-attested merkle proof.
  348. // data is expected to be a serialized PriceFeedMessage
  349. function updatePriceFeedsWhMerkle(
  350. bytes calldata rootVaa,
  351. bytes memory data,
  352. bytes32[] memory proof
  353. ) public payable {
  354. IWormhole.VM memory vm = parseAndVerifyBatchMessageVM(rootVaa);
  355. assert(vm.payload.length == 32);
  356. bytes32 expectedRoot = UnsafeBytesLib.toBytes32(vm.payload, 0);
  357. bool validProof = isValidMerkleProof(expectedRoot, data, proof);
  358. if (!validProof) revert PythErrors.InvalidArgument();
  359. (
  360. PythInternalStructs.PriceInfo memory info,
  361. bytes32 priceId,
  362. ) = parsePriceFeedMessageFromMemory(data, 0);
  363. updateLatestPriceIfNecessary(priceId, info);
  364. }
  365. function parseAndVerifyBatchMessageVM(
  366. bytes calldata encodedVm
  367. ) internal view returns (IWormhole.VM memory vm) {
  368. {
  369. bool valid;
  370. (vm, valid, ) = wormhole().parseAndVerifyVM(encodedVm);
  371. if (!valid) revert PythErrors.InvalidWormholeVaa();
  372. }
  373. if (!verifyPythVM(vm)) revert PythErrors.InvalidUpdateDataSource();
  374. }
  375. function verifyPythVM(
  376. IWormhole.VM memory vm
  377. ) private view returns (bool valid) {
  378. return isValidDataSource(vm.emitterChainId, vm.emitterAddress);
  379. }
  380. // Update a single price feed via a threshold-signed merkle proof.
  381. // data is expected to be a serialized PriceFeedMessage
  382. function updatePriceFeedsThresholdMerkle(
  383. bytes memory rootSignature,
  384. bytes32 rootHash,
  385. bytes memory data,
  386. bytes32[] memory proof
  387. ) public payable {
  388. if (!verifySignature(rootHash, rootSignature, thresholdPublicKey))
  389. revert PythErrors.InvalidArgument();
  390. bool validProof = isValidMerkleProof(rootHash, data, proof);
  391. if (!validProof) revert PythErrors.InvalidArgument();
  392. (
  393. PythInternalStructs.PriceInfo memory info,
  394. bytes32 priceId,
  395. ) = parsePriceFeedMessageFromMemory(data, 0);
  396. updateLatestPriceIfNecessary(priceId, info);
  397. }
  398. // Update a single price feed via a threshold-signed price update.
  399. // data is expected to be a serialized PriceFeedMessage.
  400. function updatePriceFeedsThreshold(
  401. bytes memory signature,
  402. bytes memory data
  403. ) public payable {
  404. bytes32 hash = keccak256(data);
  405. if (!verifySignature(hash, signature, thresholdPublicKey))
  406. revert PythErrors.InvalidArgument();
  407. (
  408. PythInternalStructs.PriceInfo memory info,
  409. bytes32 priceId,
  410. ) = parsePriceFeedMessageFromMemory(data, 0);
  411. updateLatestPriceIfNecessary(priceId, info);
  412. }
  413. // Update a single price feed via a "native" price update (i.e., using the default ethereum tx signature for authentication).
  414. // data is expected to be a serialized PriceFeedMessage.
  415. // This function represents the lower bound on how much gas we can use.
  416. function updatePriceFeedsNative(bytes memory data) public payable {
  417. // TODO: this function should have a check on the sender.
  418. // I'm assuming that check is not very expensive here.
  419. (
  420. PythInternalStructs.PriceInfo memory info,
  421. bytes32 priceId,
  422. ) = parsePriceFeedMessageFromMemory(data, 0);
  423. updateLatestPriceIfNecessary(priceId, info);
  424. }
  425. function parsePriceFeedMessageFromMemory(
  426. bytes memory encodedPriceFeed,
  427. uint offset
  428. )
  429. internal
  430. pure
  431. returns (
  432. PythInternalStructs.PriceInfo memory priceInfo,
  433. bytes32 priceId,
  434. uint64 prevPublishTime
  435. )
  436. {
  437. unchecked {
  438. priceId = UnsafeBytesLib.toBytes32(encodedPriceFeed, offset);
  439. offset += 32;
  440. priceInfo.price = int64(
  441. UnsafeBytesLib.toUint64(encodedPriceFeed, offset)
  442. );
  443. offset += 8;
  444. priceInfo.conf = UnsafeBytesLib.toUint64(encodedPriceFeed, offset);
  445. offset += 8;
  446. priceInfo.expo = int32(
  447. UnsafeBytesLib.toUint32(encodedPriceFeed, offset)
  448. );
  449. offset += 4;
  450. // Publish time is i64 in some environments due to the standard in that
  451. // environment. This would not cause any problem because since the signed
  452. // integer is represented in two's complement, the value would be the same
  453. // in both cases (for a million year at least)
  454. priceInfo.publishTime = UnsafeBytesLib.toUint64(
  455. encodedPriceFeed,
  456. offset
  457. );
  458. offset += 8;
  459. // We do not store this field because it is not used on the latest feed queries.
  460. prevPublishTime = UnsafeBytesLib.toUint64(encodedPriceFeed, offset);
  461. offset += 8;
  462. priceInfo.emaPrice = int64(
  463. UnsafeBytesLib.toUint64(encodedPriceFeed, offset)
  464. );
  465. offset += 8;
  466. priceInfo.emaConf = UnsafeBytesLib.toUint64(
  467. encodedPriceFeed,
  468. offset
  469. );
  470. offset += 8;
  471. if (offset > encodedPriceFeed.length)
  472. revert PythErrors.InvalidUpdateData();
  473. }
  474. }
  475. // Verify that signature is a valid ECDSA signature of messageHash by signer.
  476. function verifySignature(
  477. bytes32 messageHash,
  478. bytes memory signature,
  479. address signer
  480. ) public pure returns (bool) {
  481. bytes32 r;
  482. bytes32 s;
  483. uint8 v;
  484. assembly {
  485. r := mload(add(signature, 32))
  486. s := mload(add(signature, 64))
  487. v := byte(0, mload(add(signature, 96)))
  488. }
  489. if (v < 27) {
  490. v += 27;
  491. }
  492. if (v != 27 && v != 28) {
  493. return false;
  494. }
  495. address recoveredAddress = ecrecover(messageHash, v, r, s);
  496. return (recoveredAddress == signer);
  497. }
  498. // Check that proof is a valid merkle proof of data. This function assumes that
  499. // data is the leftmost node of the tree.
  500. // TODO: need to encode left/right structure for proof nodes
  501. function isValidMerkleProof(
  502. bytes32 expectedRoot,
  503. bytes memory data,
  504. bytes32[] memory proof
  505. ) public pure returns (bool) {
  506. bytes32 curNodeHash = keccak256(data);
  507. for (uint i = 0; i < proof.length; ) {
  508. curNodeHash = keccak256(abi.encode(curNodeHash, proof[i]));
  509. unchecked {
  510. i++;
  511. }
  512. }
  513. return (expectedRoot == curNodeHash);
  514. }
  515. }
  516. // A merkle tree price update delivered via wormhole.
  517. // The update is valid if the data above hashed with the proof nodes sequentially
  518. // equals the root hash in the wormhole VAA.
  519. struct WormholeMerkleUpdate {
  520. // The serialized bytes of a wormhole VAA
  521. // The payload of this VAA is a single 32-byte root hash for the merkle tree.
  522. bytes rootVaa;
  523. // The serialized bytes of a PriceFeedMessage
  524. bytes data;
  525. // The chain of proof nodes.
  526. bytes32[] proof;
  527. }
  528. // A merkle tree price update delivered via threshold signature.
  529. // The update is valid if the data above hashed with the proof nodes sequentially
  530. // equals the root hash, and the signature is valid for rootHash.
  531. struct ThresholdMerkleUpdate {
  532. bytes rootSignature;
  533. bytes32 rootHash;
  534. // The serialized bytes of a PriceFeedMessage
  535. bytes data;
  536. // The chain of proof nodes.
  537. bytes32[] proof;
  538. }
  539. // A merkle tree price update delivered via threshold signature.
  540. // The update is valid if the data above hashed with the proof nodes sequentially
  541. // equals the root hash, and the signature is valid for rootHash.
  542. struct ThresholdUpdate {
  543. // Signature of the hash of the data.
  544. bytes signature;
  545. // The serialized bytes of a PriceFeedMessage
  546. bytes data;
  547. }