Skip to content

Commit 23aed30

Browse files
themacexpertdawnkelly09eshaben
authored
Add Queries Section (#11)
* fresh slate * rev * rev * add * grammarly etc * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * rev * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * rev * rev * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * rev * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * remove we reference * rev' ' * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * center content and headings in table * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * rev * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * rev * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * rev * rev * rev * rev * rev * rev * rev * rev * rev * rev * rev * rev * rev * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> * prettier * prettier * rev * rev * revise * revise * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/getting-started.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * revise * revise * revise * revise * revise * revise * revise * revise * revise * revise * revise * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * revise * merge * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * revise * revise * revise * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/overview.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * rev * Update build/build-multichain-applications/queries/hands-on-with-queries.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/hands-on-with-queries.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/faqs.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * Update build/build-multichain-applications/queries/hands-on-with-queries.md Co-authored-by: Erin Shaben <eshaben@icloud.com> * rev * Update .snippets/code/build/build-multichain-applications/queries/hands-on-with-queries/test-full.jsx Co-authored-by: Erin Shaben <eshaben@icloud.com> * rev * rev * rev * rev * rev * rev * relocate and rename everything to build apps * change from paragraph to bullet format * changes surfaced in Vale screen * Vale screen and some additional edits * rev --------- Co-authored-by: Dawn Kelly <83190195+dawnkelly09@users.noreply.github.com> Co-authored-by: Erin Shaben <eshaben@icloud.com> Co-authored-by: DAWN KELLY <dawnkelly09@gmail.com>
1 parent fa3bff5 commit 23aed30

File tree

13 files changed

+554
-0
lines changed

13 files changed

+554
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
curl https://ethereum.publicnode.com -X POST --data '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2","data":"0x18160ddd"},"latest"],"id":1}'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"jsonrpc":"2.0",
3+
"id":1,
4+
"result":"0x000000000000000000000000000000000000000000029fd3d129b582d7949e71"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// contracts/query/QueryDemo.sol
2+
// SPDX-License-Identifier: Apache 2
3+
4+
pragma solidity ^0.8.0;
5+
6+
import "wormhole-solidity-sdk/libraries/BytesParsing.sol";
7+
import "wormhole-solidity-sdk/interfaces/IWormhole.sol";
8+
import "wormhole-solidity-sdk/QueryResponse.sol";
9+
10+
error InvalidOwner();
11+
// @dev for the onlyOwner modifier
12+
error InvalidCaller();
13+
error InvalidCalldata();
14+
error InvalidForeignChainID();
15+
error ObsoleteUpdate();
16+
error StaleUpdate();
17+
error UnexpectedResultLength();
18+
error UnexpectedResultMismatch();
19+
20+
/// @dev QueryDemo is an example of using the QueryResponse library to parse and verify Cross Chain Query (CCQ) responses.
21+
contract QueryDemo is QueryResponse {
22+
using BytesParsing for bytes;
23+
24+
struct ChainEntry {
25+
uint16 chainID;
26+
address contractAddress;
27+
uint256 counter;
28+
uint256 blockNum;
29+
uint256 blockTime;
30+
}
31+
32+
address private immutable owner;
33+
uint16 private immutable myChainID;
34+
mapping(uint16 => ChainEntry) private counters;
35+
uint16[] private foreignChainIDs;
36+
37+
bytes4 public GetMyCounter = bytes4(hex"916d5743");
38+
39+
constructor(address _owner, address _wormhole, uint16 _myChainID) QueryResponse(_wormhole) {
40+
if (_owner == address(0)) {
41+
revert InvalidOwner();
42+
}
43+
owner = _owner;
44+
45+
myChainID = _myChainID;
46+
counters[_myChainID] = ChainEntry(_myChainID, address(this), 0, 0, 0);
47+
}
48+
49+
// updateRegistration should be used to add the other chains and to set / update contract addresses.
50+
function updateRegistration(uint16 _chainID, address _contractAddress) public onlyOwner {
51+
if (counters[_chainID].chainID == 0) {
52+
foreignChainIDs.push(_chainID);
53+
counters[_chainID].chainID = _chainID;
54+
}
55+
56+
counters[_chainID].contractAddress = _contractAddress;
57+
}
58+
59+
// getMyCounter (call signature 916d5743) returns the counter value for this chain. It is meant to be used in a cross chain query.
60+
function getMyCounter() public view returns (uint256) {
61+
return counters[myChainID].counter;
62+
}
63+
64+
// getState() returns this chain's view of all the counters. It is meant to be used in the front end.
65+
function getState() public view returns (ChainEntry[] memory) {
66+
ChainEntry[] memory ret = new ChainEntry[](foreignChainIDs.length + 1);
67+
ret[0] = counters[myChainID];
68+
uint256 length = foreignChainIDs.length;
69+
70+
for (uint256 i = 0; i < length;) {
71+
ret[i + 1] = counters[foreignChainIDs[i]];
72+
unchecked {
73+
++i;
74+
}
75+
}
76+
77+
return ret;
78+
}
79+
80+
// @notice Takes the cross chain query response for the other counters, stores the results for the other chains, and updates the counter for this chain.
81+
function updateCounters(bytes memory response, IWormhole.Signature[] memory signatures) public {
82+
ParsedQueryResponse memory r = parseAndVerifyQueryResponse(response, signatures);
83+
uint256 numResponses = r.responses.length;
84+
if (numResponses != foreignChainIDs.length) {
85+
revert UnexpectedResultLength();
86+
}
87+
88+
for (uint256 i = 0; i < numResponses;) {
89+
// Create a storage pointer for frequently read and updated data stored on the blockchain
90+
ChainEntry storage chainEntry = counters[r.responses[i].chainId];
91+
if (chainEntry.chainID != foreignChainIDs[i]) {
92+
revert InvalidForeignChainID();
93+
}
94+
95+
EthCallQueryResponse memory eqr = parseEthCallQueryResponse(r.responses[i]);
96+
97+
// Validate that update is not obsolete
98+
validateBlockNum(eqr.blockNum, chainEntry.blockNum);
99+
100+
// Validate that update is not stale
101+
validateBlockTime(eqr.blockTime, block.timestamp - 300);
102+
103+
if (eqr.result.length != 1) {
104+
revert UnexpectedResultMismatch();
105+
}
106+
107+
// Validate addresses and function signatures
108+
address[] memory validAddresses = new address[](1);
109+
bytes4[] memory validFunctionSignatures = new bytes4[](1);
110+
validAddresses[0] = chainEntry.contractAddress;
111+
validFunctionSignatures[0] = GetMyCounter;
112+
113+
validateMultipleEthCallData(eqr.result, validAddresses, validFunctionSignatures);
114+
115+
require(eqr.result[0].result.length == 32, "result is not a uint256");
116+
117+
chainEntry.blockNum = eqr.blockNum;
118+
chainEntry.blockTime = eqr.blockTime / 1_000_000;
119+
chainEntry.counter = abi.decode(eqr.result[0].result, (uint256));
120+
121+
unchecked {
122+
++i;
123+
}
124+
}
125+
126+
counters[myChainID].blockNum = block.number;
127+
counters[myChainID].blockTime = block.timestamp;
128+
counters[myChainID].counter += 1;
129+
}
130+
131+
modifier onlyOwner() {
132+
if (owner != msg.sender) {
133+
revert InvalidOwner();
134+
}
135+
_;
136+
}
137+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const tx = await contract.updateCounters(
2+
`0x${response.data.bytes}`,
3+
signaturesToEvmStruct(response.data.signatures)
4+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const serialized = request.serialize();
2+
const proxyResponse = (await axios.post)<QueryProxyQueryResponse>(
3+
QUERY_URL,
4+
{
5+
bytes: Buffer.from(serialized).toString('hex'),
6+
},
7+
{ headers: { 'X-API-Key': YOUR_API_KEY } }
8+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {
2+
EthCallData,
3+
EthCallQueryRequest,
4+
EthCallQueryResponse,
5+
PerChainQueryRequest,
6+
QueryProxyMock,
7+
QueryRequest,
8+
QueryResponse,
9+
} from '@wormhole-foundation/wormhole-query-sdk';
10+
import axios from 'axios';
11+
12+
const rpc = 'https://ethereum.publicnode.com';
13+
const callData: EthCallData = {
14+
to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
15+
data: '0x18160ddd', // web3.eth.abi.encodeFunctionSignature("totalSupply()")
16+
};
17+
18+
(async () => {
19+
const latestBlock: string = (
20+
await axios.post(rpc, {
21+
method: 'eth_getBlockByNumber',
22+
params: ['latest', false],
23+
id: 1,
24+
jsonrpc: '2.0',
25+
})
26+
).data?.result?.number;
27+
if (!latestBlock) {
28+
console.error(`❌ Invalid block returned`);
29+
return;
30+
}
31+
console.log('Latest Block: ', latestBlock, `(${BigInt(latestBlock)})`);
32+
const targetResponse = await axios.post(rpc, {
33+
method: 'eth_call',
34+
params: [callData, latestBlock],
35+
id: 1,
36+
jsonrpc: '2.0',
37+
});
38+
// console.log(finalizedResponse.data);
39+
if (targetResponse.data.error) {
40+
console.error(`❌ ${targetResponse.data.error.message}`);
41+
}
42+
const targetResult = targetResponse.data?.result;
43+
console.log('Target Result: ', targetResult, `(${BigInt(targetResult)})`);
44+
// Form the query request
45+
const request = new QueryRequest(
46+
0, // Nonce
47+
[
48+
new PerChainQueryRequest(
49+
2, // Ethereum Wormhole Chain ID
50+
new EthCallQueryRequest(latestBlock, [callData])
51+
),
52+
]
53+
);
54+
console.log(JSON.stringify(request, undefined, 2));
55+
const mock = new QueryProxyMock({ 2: rpc });
56+
const mockData = await mock.mock(request);
57+
console.log(mockData);
58+
const mockQueryResponse = QueryResponse.from(mockData.bytes);
59+
const mockQueryResult = (
60+
mockQueryResponse.responses[0].response as EthCallQueryResponse
61+
).results[0];
62+
console.log(
63+
`Mock Query Result: ${mockQueryResult} (${BigInt(mockQueryResult)})`
64+
);
65+
})();

build/build-apps/.pages

+1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ title: Build Apps
22
nav:
33
- index.md
44
- 'Wormhole SDK': 'wormhole-sdk.md'
5+
- queries
56
- connect

build/build-apps/queries/.pages

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
title: Queries
2+
nav:
3+
- index.md
4+
- 'Overview': overview.md
5+
- 'Hands on with Queries': hands-on-with-queries.md
6+
- 'FAQs': faqs.md

build/build-apps/queries/faqs.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
title: FAQs about Queries
3+
description: Explore frequently asked questions about Wormhole Queries, which offer on-demand access to guardian-attested on-chain data via a REST API endpoint.
4+
---
5+
6+
# FAQs about Queries
7+
8+
## What Libraries Are Available to Handle Queries?
9+
10+
- The [Query TypeScript SDK](https://npmjs.com/package/@wormhole-foundation/wormhole-query-sdk){target=\_blank} can be used to create query requests, mock query responses for testing, and parse query responses. The SDK also includes utilities for posting query responses
11+
12+
- The [Solidity `QueryResponse` abstract contract](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/QueryResponse.sol){target=\_blank} can be used to parse and verify query responses on EVM chains. See the [Solana Stake Pool](https://github.com/wormholelabs-xyz/example-queries-solana-stake-pool){target=\_blank} repository as an example use case
13+
14+
- [`QueryTest.sol`](https://github.com/wormhole-foundation/wormhole-solidity-sdk/blob/main/src/testing/helpers/QueryTest.sol){target=\_blank} can be used for mocking query requests and responses in Forge tests
15+
16+
- The [Go query package](https://github.com/wormhole-foundation/wormhole/tree/main/node/pkg/query){target=\_blank} can also be used to create query requests and parse query responses
17+
18+
!!! note
19+
A Rust SDK for Solana is being actively investigated by the Wormhole contributors. See the [Solana Queries Verification](https://github.com/wormholelabs-xyz/example-queries-solana-verify){target=\_blank} repository as a proof of concept.
20+
21+
## Are There Any Query Examples?
22+
23+
Certainly. You can find a complete guide on the [Hands on with Queries page](/build/build-apps/queries/hands-on-with-queries). Additionally, you can find full code examples in available in the following repositories:
24+
25+
- [Basic Example Query Demo](https://github.com/wormholelabs-xyz/example-queries-demo/){target=\_blank}
26+
- [Solana Stake Pool Example Query](https://github.com/wormholelabs-xyz/example-queries-solana-stake-pool){target=\_blank}
27+
- [Solana PDA / Token Account Balance Example Query](https://github.com/wormholelabs-xyz/example-queries-solana-pda){target=\_blank}
28+
- [Solana Queries Verification Example](https://github.com/wormholelabs-xyz/example-queries-solana-verify){target=\_blank}
29+
30+
## What Is the Format of the Response Signature?
31+
32+
The Guardian node calculates an ECDSA signature using [`Sign` function of the crypto package](https://pkg.go.dev/github.com/ethereum/go-ethereum@v1.10.21/crypto#Sign){target=\_blank} where the digest hash is:
33+
34+
`keccak256("query_response_0000000000000000000|"+keccak256(responseBytes))`
35+
36+
See the [Guardian Key Usage](https://github.com/wormhole-foundation/wormhole/blob/main/whitepapers/0009_guardian_key.md){target=\_blank} white paper for more background. Once this signature is created, the Guardian's index in the Guardian set is appended to the end.
37+
38+
!!! note
39+
If you are used to `ecrecover` you will notice that the `v` byte is `0` or `1` as opposed to `27` or `28`. The `signaturesToEvmStruct` method in the [Query TypeScript SDK](https://npmjs.com/package/@wormhole-foundation/wormhole-query-sdk){target=\_blank} accounts for this as well as structuring the signatures into an `IWormhole.SignatureStruct[]`.
40+
41+
## Can Anyone Run a Query Proxy Server?
42+
43+
Permissions for Query Proxy are managed by the Guardians. The Guardian nodes are configured to only listen to a set of allow-listed proxies. However, it is possible that this restriction may be lifted in the future and/or more proxies could be added.
44+
45+
It is also important to note that the proxies don't impact the verifiability of the request or result, i.e., their role in the process is trustless.

0 commit comments

Comments
 (0)